diff --git a/.clang-format b/.clang-format
index 4c76b9c80..26899f6fa 100644
--- a/.clang-format
+++ b/.clang-format
@@ -7,9 +7,10 @@ AlignConsecutiveDeclarations: false
AlwaysBreakBeforeMultilineStrings: false
AllowShortFunctionsOnASingleLine: false
KeepEmptyLinesAtTheStartOfBlocks: true
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
---
Language: Cpp
-AllowShortFunctionsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: false
---
Language: Proto
...
diff --git a/Makefile b/Makefile
index c5b9f6f16..534291f3f 100644
--- a/Makefile
+++ b/Makefile
@@ -60,7 +60,7 @@
#
# build/config.mk
-SHELL = dash
+SHELL = /bin/sh
HOSTS ?= freebsd openbsd alpine
.SUFFIXES:
@@ -69,8 +69,7 @@ HOSTS ?= freebsd openbsd alpine
.PHONY: all o bins check test depend tags
all: o
-o: o/libc \
- o/$(MODE)/ape \
+o: o/$(MODE)/ape \
o/$(MODE)/dsp \
o/$(MODE)/net \
o/$(MODE)/libc \
@@ -81,6 +80,7 @@ o: o/libc \
PKGS =
+-include ~/.cosmo.mk #──No.1
include build/functions.mk #─┐
include build/definitions.mk # ├──meta
include build/config.mk # │
@@ -89,6 +89,7 @@ include build/online.mk # │
include libc/stubs/stubs.mk #─┘
include libc/nexgen32e/nexgen32e.mk #─┐
include libc/intrin/intrin.mk # │
+include libc/linux/linux.mk # │
include libc/math/math.mk # ├──metal
include libc/tinymath/tinymath.mk # │
include third_party/compiler_rt/compiler_rt.mk # │
@@ -106,6 +107,7 @@ include libc/fmt/fmt.mk # │
include libc/rand/rand.mk #─┘
include libc/calls/calls.mk #─┐
include libc/runtime/runtime.mk # ├──systems
+include libc/crt/crt.mk # │
include libc/unicode/unicode.mk # │
include third_party/dlmalloc/dlmalloc.mk # │
include libc/mem/mem.mk # │
@@ -140,9 +142,9 @@ include third_party/editline/editline.mk
include third_party/duktape/duktape.mk
include third_party/regex/regex.mk
include third_party/avir/avir.mk
+include third_party/ctags/ctags.mk
include third_party/third_party.mk
include libc/testlib/testlib.mk
-include libc/crt/crt.mk
include tool/viz/lib/vizlib.mk
include examples/examples.mk
include third_party/lex/lex.mk
@@ -150,6 +152,8 @@ include third_party/m4/m4.mk
include third_party/lz4cli/lz4cli.mk
include third_party/bzip2/bzip2.mk
include tool/build/lib/buildlib.mk
+include tool/build/emucrt/emucrt.mk
+include tool/build/emubin/emubin.mk
include tool/build/build.mk
include tool/debug/debug.mk
include tool/decode/lib/decodelib.mk
@@ -213,7 +217,7 @@ depend: o/$(MODE)/depend
tags: TAGS HTAGS
o/$(MODE)/.x:
- @$(MKDIR) $(dir $@) && touch $@
+ @$(MKDIR) $(@D) && touch $@
o/$(MODE)/srcs.txt: o/$(MODE)/.x $(MAKEFILES) $(call uniq,$(foreach x,$(SRCS),$(dir $(x))))
$(file >$@) $(foreach x,$(SRCS),$(file >>$@,$(x)))
@@ -236,6 +240,71 @@ loc: o/$(MODE)/tool/build/summy.com
find -name \*.h -or -name \*.c -or -name \*.S | \
$(XARGS) wc -l | grep total | awk '{print $$1}' | $<
+COSMOPOLITAN_OBJECTS = \
+ APE_LIB \
+ LIBC \
+ LIBC_ALG \
+ LIBC_BITS \
+ LIBC_CALLS \
+ LIBC_CALLS_HEFTY \
+ LIBC_CONV \
+ LIBC_CRYPTO \
+ LIBC_DNS \
+ LIBC_FMT \
+ LIBC_ELF \
+ LIBC_LOG \
+ LIBC_MEM \
+ LIBC_NEXGEN32E \
+ LIBC_NT \
+ LIBC_OHMYPLUS \
+ LIBC_RAND \
+ LIBC_RUNTIME \
+ LIBC_SOCK \
+ LIBC_STDIO \
+ LIBC_STR \
+ LIBC_STUBS \
+ LIBC_SYSV \
+ LIBC_TIME \
+ LIBC_TINYMATH \
+ LIBC_UNICODE \
+ LIBC_ZIPOS \
+ THIRD_PARTY_DLMALLOC \
+ THIRD_PARTY_DTOA \
+ THIRD_PARTY_GETOPT \
+ THIRD_PARTY_MUSL \
+ THIRD_PARTY_REGEX
+
+COSMOPOLITAN_HEADERS = \
+ LIBC \
+ LIBC_CALLS \
+ LIBC_CONV \
+ LIBC_CRYPTO \
+ LIBC_DNS \
+ LIBC_FMT \
+ LIBC_MEM \
+ LIBC_RAND \
+ LIBC_RUNTIME \
+ LIBC_SOCK \
+ LIBC_STDIO \
+ LIBC_STR \
+ LIBC_TIME \
+ LIBC_UNICODE \
+ LIBC_ZIPOS \
+ LIBC_SYSV \
+ LIBC_NT \
+ THIRD_PARTY_DLMALLOC \
+ THIRD_PARTY_DTOA \
+ THIRD_PARTY_GETOPT \
+ THIRD_PARTY_MUSL \
+ THIRD_PARTY_REGEX
+
+o/$(MODE)/cosmopolitan.a: $(filter-out o/libc/stubs/exit11.o,$(foreach x,$(COSMOPOLITAN_OBJECTS),$($(x)_OBJS)))
+o/$(MODE)/.cosmopolitan.h: $(foreach x,$(COSMOPOLITAN_HEADERS),$($(x)_HDRS))
+ build/rollup $^ >$@
+o/$(MODE)/cosmopolitan.h: o/$(MODE)/.cosmopolitan.h
+ build/compile $(PREPROCESS) -P $(OUTPUT_OPTION) $<
+ clang-format-10 -i $@
+
# UNSPECIFIED PREREQUISITES TUTORIAL
#
# A build rule must exist for all files that make needs to consider in
@@ -255,11 +324,12 @@ loc: o/$(MODE)/tool/build/summy.com
# never get executed since they're not members of the transitive closure
# of `make all`. In that case the build config could be improved.
%.mk:
+~/.cosmo.mk:
$(SRCS):
$(HDRS):
.DEFAULT:
@echo >&2
- @echo NOTE: deleting o/$(MODE)/depend due to unspecified prerequisite: $@ >&2
+ @echo NOTE: deleting o/$(MODE)/depend because of an unspecified prerequisite: $@ >&2
@echo >&2
rm -f o/$(MODE)/depend
diff --git a/ape/ape.S b/ape/ape.S
index b92ed5fe8..3f4e49eba 100644
--- a/ape/ape.S
+++ b/ape/ape.S
@@ -44,6 +44,7 @@
#include "libc/nexgen32e/uart.h"
#include "libc/nexgen32e/vidya.h"
#include "libc/nt/pedef.h"
+#include "libc/nexgen32e/vidya.h"
#include "libc/sysv/consts/prot.h"
.source "NOTICE"
@@ -136,43 +137,24 @@ ape.mz: .ascii "MZ" # Mark 'Zibo' Joseph Zbikowski
.short 0 # MZ: OEM information
.org 0x40-4 # MZ: bytes reserved for you
.long RVA(ape.pe) # PE: the new technology
- .endobj ape.mz,globl,hidden
+ .endfn ape.mz,globl,hidden
/ Disk Operating System Stub
/ @noreturn
.org 0x40 # mz/elf header length
stub: mov $0x40,%dl # *literally* dos
jmp 1f # good bios skips here
-1: jmp pc
+1: jmp pc # thus avoiding heroics
nop # system five bootpoint
- .org 0x48,0x90 # ⌂ELF → JNLE 47
- jmp 3f
-2: push %rdx # don't move or shell script breaks
- xor %edx,%edx # Z in MZ ate BIOS drive letter :(
-3: .byte 0xbd,0,0 # mov $0x????0000,%[e]bp
- jmp pc
- jmp ape.hop # already in userspace
+ .org 0x48,0x90 # note ⌂ELF means JG 47
+ jmp 3f # MZ also means pop r10
+2: sub $8,%rsp # a.k.a. dec %ax sub %sp
+ xor %edx,%edx # MZ ate BIOS drive code
+3: .byte 0xbd,0,0 # a.k.a. mov imm,%bp
+ jmp pc # real mode, is real
+ jmp _start # surprise it's unix
.endfn stub
-/ Mitigate incidental quotation marks.
- .real
-ape.hop:pop %rdx
- push %r10 # MZ → pop %r10 w/ NexGen32e
- .weak __imp_GetStartupInfoW
- ezlea __imp_GetStartupInfoW,ax
- test %rax,%rax
- jz 0f
- .weak KernelBase.GetStartupInfo
- test %rax,%rax
-/ TODO(jart)
-/ cmpq $RVA(KernelBase.GetStartupInfo),(%rax)
- jz 0f
- jmp WinMain
-0: .weak _start
- jmp _start
- .endfn ape.hop
- .previous
-
/*─────────────────────────────────────────────────────────────────────────────╗
│ αcτµαlly pδrταblε εxεcµταblε § ibm personal computer │
╚──────────────────────────────────────────────────────────────────────────────┘
@@ -835,9 +817,9 @@ ape.pe: .ascin "PE",4
.short v_ntsubsystem # Subsystem: 0=Neutral,2=GUI,3=Console
.short .LDLLEXE # DllCharacteristics
.quad 0x0000000000100000 # StackReserve
- .quad 0x0000000000030000 # StackCommit (64kb [goog] + arg + env)
+ .quad 0x0000000000100000 # StackCommit
.quad 0x0000000000080000 # HeapReserve
- .quad 0x0000000000001000 # HeapCommit (we make our own heap)
+ .quad 0x0000000000001000 # HeapCommit
.long 0x00000000 # LoaderFlags
.long 16 # NumberOfDirectoryEntries
.long 0,0 # ExportsDirectory
@@ -1933,5 +1915,10 @@ __data_start:
.type __piro_start,@object
.hidden __piro_start
+ .type __ubsan_data_start,@object
+ .type __ubsan_data_end,@object
+ .type __ubsan_types_start,@object
+ .type __ubsan_types_end,@object
+
.end
\ No newline at end of file
diff --git a/ape/ape.lds b/ape/ape.lds
index d864c7e12..d8857c563 100644
--- a/ape/ape.lds
+++ b/ape/ape.lds
@@ -435,36 +435,36 @@ SECTIONS {
/*END: linux addressability guarantee */
/*END: xnu addressability guarantee */
- .stab 0 : { *(.stab) }
- .stabstr 0 : { *(.stabstr) }
- .stab.excl 0 : { *(.stab.excl) }
- .stab.exclstr 0 : { *(.stab.exclstr) }
- .stab.index 0 : { *(.stab.index) }
- .stab.indexstr 0 : { *(.stab.indexstr) }
- .comment 0 : { *(.comment) }
- .debug 0 : { *(.debug) }
- .line 0 : { *(.line) }
- .debug_srcinfo 0 : { *(.debug_srcinfo) }
- .debug_sfnames 0 : { *(.debug_sfnames) }
- .debug_aranges 0 : { *(.debug_aranges) }
- .debug_pubnames 0 : { *(.debug_pubnames) }
- .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
- .debug_abbrev 0 : { *(.debug_abbrev) }
- .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) }
- .debug_frame 0 : { *(.debug_frame) }
- .debug_str 0 : { *(.debug_str) }
- .debug_loc 0 : { *(.debug_loc) }
- .debug_macinfo 0 : { *(.debug_macinfo) }
- .debug_weaknames 0 : { *(.debug_weaknames) }
- .debug_funcnames 0 : { *(.debug_funcnames) }
- .debug_typenames 0 : { *(.debug_typenames) }
- .debug_varnames 0 : { *(.debug_varnames) }
- .debug_pubtypes 0 : { *(.debug_pubtypes) }
- .debug_ranges 0 : { *(.debug_ranges) }
- .debug_macro 0 : { *(.debug_macro) }
- .debug_addr 0 : { *(.debug_addr) }
- .gnu.attributes 0 : { KEEP(*(.gnu.attributes)) }
- .GCC.command.line 0 : { *(.GCC.command.line) }
+ .stab 0 : { *(.stab) }
+ .stabstr 0 : { *(.stabstr) }
+ .stab.excl 0 : { *(.stab.excl) }
+ .stab.exclstr 0 : { *(.stab.exclstr) }
+ .stab.index 0 : { *(.stab.index) }
+ .stab.indexstr 0 : { *(.stab.indexstr) }
+ .comment 0 : { *(.comment) }
+ .debug 0 : { *(.debug) }
+ .line 0 : { *(.line) }
+ .debug_srcinfo 0 : { *(.debug_srcinfo) }
+ .debug_sfnames 0 : { *(.debug_sfnames) }
+ .debug_aranges 0 : { *(.debug_aranges) }
+ .debug_pubnames 0 : { *(.debug_pubnames) }
+ .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
+ .debug_abbrev 0 : { *(.debug_abbrev) }
+ .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) }
+ .debug_frame 0 : { *(.debug_frame) }
+ .debug_str 0 : { *(.debug_str) }
+ .debug_loc 0 : { *(.debug_loc) }
+ .debug_macinfo 0 : { *(.debug_macinfo) }
+ .debug_weaknames 0 : { *(.debug_weaknames) }
+ .debug_funcnames 0 : { *(.debug_funcnames) }
+ .debug_typenames 0 : { *(.debug_typenames) }
+ .debug_varnames 0 : { *(.debug_varnames) }
+ .debug_pubtypes 0 : { *(.debug_pubtypes) }
+ .debug_ranges 0 : { *(.debug_ranges) }
+ .debug_macro 0 : { *(.debug_macro) }
+ .debug_addr 0 : { *(.debug_addr) }
+ .gnu.attributes 0 : { KEEP(*(.gnu.attributes)) }
+ .GCC.command.line 0 : { *(.GCC.command.line) }
/DISCARD/ : {
*(.discard)
diff --git a/ape/ape.mk b/ape/ape.mk
index 06fc9d5cf..fdf6e1445 100644
--- a/ape/ape.mk
+++ b/ape/ape.mk
@@ -21,8 +21,7 @@ APE = $(APE_DEPS) \
APELINK = \
ACTION=LINK.ape \
- $(MKDIR) \
- $(dir $@) && \
+ $(MKDIR) $(@D) && \
$(LINK) \
$(LINKARGS) \
$(OUTPUT_OPTION) && \
@@ -32,18 +31,6 @@ APELINK = \
$(ZFLAGS) \
-f $@.map
-APECOPY = \
- ACTION=OBJCOPY.ape \
- TARGET=$@ \
- build/do \
- $(OBJCOPY) \
- -SO binary \
- $< \
- $@
-
-DEFAULT_COPTS += -mno-red-zone
-DEFAULT_LDFLAGS += -z max-page-size=0x1000
-
APE_FILES := $(wildcard ape/*.*)
APE_HDRS = $(filter %.h,$(APE_FILES))
APE_SRCS = $(filter %.S,$(APE_FILES))
@@ -51,9 +38,6 @@ APE_OBJS = $(APE_SRCS:%.S=o/$(MODE)/%.o)
APE_DEPS = $(APE_LIB)
APE_CHECKS = $(APE_HDRS:%=o/%.ok)
-o/%.com: o/%.com.dbg
- @$(APECOPY)
-
o/ape/idata.inc: \
ape/idata.h \
ape/relocations.h
diff --git a/ape/idata.h b/ape/idata.h
index 096f383f6..89ba31920 100644
--- a/ape/idata.h
+++ b/ape/idata.h
@@ -32,39 +32,35 @@
/ @see libc/nt/master.sh
/ @see ape/ape.lds
/ @see winimp
-.macro .imp dll:req fn:req actual hint
+.macro .imp dll:req fn:req actual:req hint
.dll \dll
- .section .piro.data.sort.iat.2.\dll\().2.\fn,"aw",@progbits
+ .section .piro.data.sort.iat.2.\dll\().2.\actual,"aw",@progbits
.type \fn,@object
.align __SIZEOF_POINTER__
-\fn: .quad RVA((.L\dll\().\fn))
+\fn: .quad RVA((\dll\().\actual))
.size \fn,.-\fn
.globl \fn
.hidden \fn
.previous
- .section .idata.ro.ilt.\dll\().2.\fn,"a",@progbits
-.Lidata.ilt.\dll\().\fn:
- .quad RVA((.L\dll\().\fn))
- .type .Lidata.ilt..L\dll\().\fn,@object
- .size .Lidata.ilt..L\dll\().\fn,.-.Lidata.ilt.\dll\().\fn
+ .section .idata.ro.ilt.\dll\().2.\actual,"a",@progbits
+.Lidata.ilt.\dll\().\actual:
+ .quad RVA((\dll\().\actual))
+ .type .Lidata.ilt.\dll\().\actual,@object
+ .size .Lidata.ilt.\dll\().\actual,.-.Lidata.ilt.\dll\().\actual
.previous
- .section .idata.ro.hnt.\dll\().2.\fn,"a",@progbits
-.L\dll\().\fn:
+ .section .idata.ro.hnt.\dll\().2.\actual,"a",@progbits
+\dll\().\actual:
.ifnb \hint # hint i.e. guess function ordinal
.short \hint
.else
.short 0
.endif
- .ifnb \actual # name
.asciz "\actual"
- .else
- .asciz "\fn"
- .endif
.align 2 # documented requirement
-/ .globl .L\dll\().\fn
-/ .hidden .L\dll\().\fn
- .type .L\dll\().\fn,@object
- .size .L\dll\().\fn,.-.L\dll\().\fn
+ .globl \dll\().\actual
+ .hidden \dll\().\actual
+ .type \dll\().\actual,@object
+ .size \dll\().\actual,.-\dll\().\actual
.previous
.endm
diff --git a/ape/lib/pc.h b/ape/lib/pc.h
index cc4b38bd3..b8a4fc638 100644
--- a/ape/lib/pc.h
+++ b/ape/lib/pc.h
@@ -52,11 +52,13 @@
SF: Stack Fault ────────────────┐││││││
ES: Exception Summary Status ──┐│││││││
C0-3: Condition Codes ──┬────┐ ││││││││
- Top of Stack Pointer ─────┐ │ ││││││││
+ TOP of Stack Pointer ─────┐ │ ││││││││
B: FPU Busy ───────────┐│ │ │ ││││││││
││┌┴┐┌┼┐││││││││
│↓│ │↓↓↓││││││││*/
+#define FPU_IE 0b0000000000100000000000001
#define FPU_ZE 0b0000000000100000000000100
+#define FPU_SF 0b0000000000000000001000000
#define FPU_C0 0b0000000000000000100000000
#define FPU_C1 0b0000000000000001000000000
#define FPU_C2 0b0000000000000010000000000
@@ -151,6 +153,7 @@
#define PAGE_V /* */ 0b000000001
#define PAGE_RW /* */ 0b000000010
#define PAGE_U /* */ 0b000000100
+#define PAGE_4KB /* */ 0b010000000
#define PAGE_2MB /* */ 0b110000000
#define PAGE_1GB /* */ 0b110000000
#define PAGE_TA 0b11111111111111111111111111111111111111000000000000
diff --git a/build/archive b/build/archive
index 663b6bf7e..1ba83e1d3 100755
--- a/build/archive
+++ b/build/archive
@@ -48,6 +48,7 @@ for x; do
done
set -- "$AR" "$ARFLAGS" "$OUT" "$@"
+printf %s\\n "$*" >"$OUT.cmd"
OUTDIR="${OUT%/*}"
if [ "$OUTDIR" != "$OUT" ] && [ ! -d "$OUTDIR" ]; then
diff --git a/build/bootstrap/mkdeps.com b/build/bootstrap/mkdeps.com
index 00b8bc39a..946514027 100755
Binary files a/build/bootstrap/mkdeps.com and b/build/bootstrap/mkdeps.com differ
diff --git a/build/compile b/build/compile
index a40cf8f5a..6989f2257 100755
--- a/build/compile
+++ b/build/compile
@@ -151,9 +151,11 @@ if [ "${PLAT#*clang}" != "${PLAT}" ]; then
FIRST=0
continue
fi
+ TRAPV= # clang handles -f{,no-}{trap,wrap}v weird
# removes flags clang whines about
case "$x" in
-gstabs) ;;
+ -ftrapv) ;;
-ffixed-*) ;;
-fcall-saved*) ;;
-fsignaling-nans) ;;
@@ -161,6 +163,7 @@ if [ "${PLAT#*clang}" != "${PLAT}" ]; then
-fno-fp-int-builtin-inexact) ;;
-Wno-unused-but-set-variable) ;;
-Wunsafe-loop-optimizations) ;;
+ -mdispatch-scheduler) ;;
-ftracer) ;;
-frounding-math) ;;
-fmerge-constants) ;;
@@ -189,6 +192,7 @@ if [ "${PLAT#*clang}" != "${PLAT}" ]; then
-fschedule-insns) ;;
-fno-semantic-interposition) ;;
-mno-fentry) ;;
+ -f*shrink-wrap) ;;
-f*schedule-insns2) ;;
-fvect-cost-model=*) ;;
-fsimd-cost-model=*) ;;
@@ -228,6 +232,7 @@ if [ "${PLAT#*clang}" != "${PLAT}" ]; then
;;
esac
done
+ set -- "$@" -fno-stack-protector
else
# removes flags only clang supports
FIRST=1
diff --git a/build/config.mk b/build/config.mk
index c21e6283a..352e74fad 100644
--- a/build/config.mk
+++ b/build/config.mk
@@ -17,8 +17,11 @@ CONFIG_CCFLAGS += \
$(FTRACE) \
-Og
+CONFIG_COPTS += \
+ -ftrapv
+
TARGET_ARCH ?= \
- -msse3
+ -march=k8-sse3
RAGELFLAGS ?= -G2
@@ -71,10 +74,7 @@ CONFIG_CPPFLAGS += \
CONFIG_CCFLAGS += \
$(BACKTRACES) \
- -O3
-
-#TARGET_ARCH ?= \
- -msse3
+ -O2
RAGELFLAGS = -G2
@@ -103,6 +103,12 @@ CONFIG_COPTS += \
$(SECURITY_BLANKETS) \
$(SANITIZER)
+CONFIG_COPTS += \
+ -ftrapv
+
+TARGET_ARCH ?= \
+ -march=k8-sse3
+
OVERRIDE_CCFLAGS += \
-fno-pie
@@ -135,7 +141,7 @@ CONFIG_CCFLAGS += \
-fno-align-loops
TARGET_ARCH ?= \
- -msse3
+ -march=k8-sse3
endif
@@ -172,6 +178,8 @@ endif
ifeq ($(MODE), ansi)
CONFIG_CFLAGS += -std=c11
+#CONFIG_CPPFLAGS += -ansi
CONFIG_CXXFLAGS += -std=c++11
+TARGET_ARCH ?= -march=k8-sse3
endif
diff --git a/build/definitions.mk b/build/definitions.mk
index 60680845a..bab54a17d 100644
--- a/build/definitions.mk
+++ b/build/definitions.mk
@@ -45,7 +45,6 @@
# ASFLAGS assembler flags (don't use -Wa, frontend prefix)
# TARGET_ARCH microarchitecture flags (e.g. -march=native)
-SHELL = /bin/sh
DD ?= /bin/dd
CP ?= /bin/cp -f
RM ?= /bin/rm -f
@@ -53,15 +52,14 @@ SED ?= /bin/sed
MKDIR ?= /bin/mkdir -p
TAGS ?= /usr/bin/ctags # emacs source builds or something breaks it
ARFLAGS = rcsD
-TAGSFLAGS ?= -e -a --if0=no --langmap=c:.c.h.i --line-directives=yes
SILENT ?= 1
-ZFLAGS ?= -4 --rsyncable
+ZFLAGS ?=
XARGS ?= xargs -P4 -rs8000
NICE ?= build/actuallynice
RAGEL ?= ragel
DOT ?= dot
GZ ?= gzip
-CLANG = clang-11
+CLANG = clang-10
FC = gfortran #/opt/cross9f/bin/x86_64-linux-musl-gfortran
# see build/compile, etc. which run third_party/gcc/unbundle.sh
@@ -108,8 +106,8 @@ FTRACE = \
-pg
SANITIZER = \
- -fsanitize=undefined \
-fsanitize=leak \
+ -fsanitize=undefined \
-fsanitize=implicit-signed-integer-truncation \
-fsanitize=implicit-integer-sign-change
@@ -139,6 +137,7 @@ DEFAULT_OFLAGS = \
-gdescribe-dies
DEFAULT_COPTS = \
+ -mno-red-zone \
-fno-math-errno \
-fno-trapping-math \
-fno-fp-int-builtin-inexact \
@@ -149,9 +148,13 @@ DEFAULT_COPTS = \
-fstrict-aliasing \
-fstrict-overflow \
-fno-omit-frame-pointer \
- -fno-optimize-sibling-calls \
+ -fno-semantic-interposition \
-mno-omit-leaf-frame-pointer
+MATHEMATICAL = \
+ -O3 \
+ -fwrapv
+
DEFAULT_CPPFLAGS = \
-DIMAGE_BASE_VIRTUAL=$(IMAGE_BASE_VIRTUAL) \
-nostdinc \
@@ -176,15 +179,14 @@ DEFAULT_ASFLAGS = \
--noexecstack
DEFAULT_LDFLAGS = \
- -h \
-static \
- --relax \
-nostdlib \
-m elf_x86_64 \
--gc-sections \
--build-id=none \
--cref -Map=$@.map \
--no-dynamic-linker \
+ -z max-page-size=0x1000 \
-Ttext-segment=$(IMAGE_BASE_VIRTUAL)
ASONLYFLAGS = \
@@ -270,8 +272,8 @@ COMPILE.F.flags = $(cc.flags) $(cpp.flags) $(copt.flags) $(f.flags)
COMPILE.i.flags = $(cc.flags) $(copt.flags) $(c.flags)
COMPILE.ii.flags = $(cc.flags) $(copt.flags) $(cxx.flags)
LINK.flags = $(DEFAULT_LDFLAGS) $(CONFIG_LDFLAGS) $(LDFLAGS)
-OBJECTIFY.c.flags = $(OBJECTIFY.S.flags) $(copt.flags) $(c.flags)
-OBJECTIFY.cxx.flags = $(OBJECTIFY.S.flags) $(copt.flags) $(cxx.flags)
+OBJECTIFY.c.flags = $(OBJECTIFY.S.flags) $(c.flags)
+OBJECTIFY.cxx.flags = $(OBJECTIFY.S.flags) $(cxx.flags)
OBJECTIFY.s.flags = $(ASONLYFLAGS) $(s.flags)
OBJECTIFY.S.flags = $(copt.flags) $(cc.flags) $(o.flags) $(cpp.flags) $(S.flags)
OBJECTIFY.f.flags = $(copt.flags) $(cc.flags) $(o.flags) $(copt.flags) $(S.flags) $(f.flags)
@@ -339,7 +341,6 @@ OBJECTIFY.ncabi.c = \
-fno-stack-protector \
-fno-instrument-functions \
-fno-optimize-sibling-calls \
- -mpreferred-stack-boundary=3 \
-fno-sanitize=all \
-fcall-saved-rcx \
-fcall-saved-rdx \
@@ -352,10 +353,26 @@ OBJECTIFY.ncabi.c = \
-c \
-xc
-BUILD_SRCS = \
- build/definitions.mk \
- build/rules.mk \
- build/compile \
- build/link \
- build/lolsan \
- build/remote
+# Initializer ABI
+#
+# Doesn't clobber RDI and RSI.
+OBJECTIFY.initabi.c = \
+ $(GCC) \
+ $(OBJECTIFY.c.flags) \
+ -mno-fentry \
+ -fno-stack-protector \
+ -fno-instrument-functions \
+ -fno-optimize-sibling-calls \
+ -fno-sanitize=all \
+ -fcall-saved-rdi \
+ -fcall-saved-rsi \
+ -c
+
+TAGSFLAGS = \
+ -e \
+ -a \
+ --if0=no \
+ --langmap=c:.c.h.i \
+ --line-directives=yes \
+ --exclude=libc/nt/struct/imagefileheader.h \
+ --exclude=libc/nt/struct/filesegmentelement.h
diff --git a/build/htags b/build/htags
index 8b68d6139..d8266fa19 100755
--- a/build/htags
+++ b/build/htags
@@ -54,4 +54,17 @@ set -- --regex-c='/^[_[:alpha:]][_[:alnum:]]*[ *][ *]*\([_[:alpha:]][_[:alnum:]]
# extern int32_t (*const SetEvent)(int64_t hEvent) wincall;
set -- --regex-c='/^extern [^(]*(\*const \([^)]*\))(/\1/b' "$@"
-exec ${TAGS:-ctags} -e --langmap=c:.c.h "$@"
+# ctags doesn't understand forward declarations, e.g.
+# struct WorstSoftwareEver;
+set -- --regex-c='/^struct.*;$/uehocruehcroue/b' "$@"
+
+exec ${TAGS:-ctags} \
+ -e \
+ --langmap=c:.c.h \
+ --exclude=libc/nt/struct/imagefileheader.h \
+ --exclude=libc/nt/struct/imageseparatedebugheader.h \
+ --exclude=libc/nt/struct/importobjectheader.h \
+ --exclude=libc/nt/struct/nonpageddebuginfo.h \
+ --exclude=libc/nt/struct/ansistring.h \
+ --exclude=libc/nt/struct/filesegmentelement.h \
+ "$@"
diff --git a/build/rollup b/build/rollup
new file mode 100755
index 000000000..c9882a7e8
--- /dev/null
+++ b/build/rollup
@@ -0,0 +1,6 @@
+#-*-mode:sh;indent-tabs-mode:nil;tab-width:2;coding:utf-8-*-┐
+#───vi: set net ft=sh ts=2 sts=2 fenc=utf-8 :vi─────────────┘
+
+for x; do
+ printf '#include "%s"\n' "$x"
+done
diff --git a/build/rules.mk b/build/rules.mk
index 570df8948..07badffb6 100644
--- a/build/rules.mk
+++ b/build/rules.mk
@@ -37,6 +37,7 @@ o/%.greg.o: %.greg.c; @ACTION=OBJECTIFY.greg build/compile $(OBJECTIFY.greg.c) $
o/%.zip.o: o/%; @build/zipobj $(OUTPUT_OPTION) $<
o/$(MODE)/%.a:; @$(ARCHIVE) $@ $^
+o/$(MODE)/%: o/$(MODE)/%.dbg; @ACTION=OBJCOPY TARGET=$@ build/do $(OBJCOPY) -SO binary $< $@
o/$(MODE)/%.o: %.s; @TARGET=$@ build/assemble $(OBJECTIFY.s) $(OUTPUT_OPTION) $<
o/$(MODE)/%.o: o/$(MODE)/%.s; @TARGET=$@ build/assemble $(OBJECTIFY.s) $(OUTPUT_OPTION) $<
o/$(MODE)/%.s: %.S; @ACTION=PREPROCESS build/compile $(PREPROCESS) $(OUTPUT_OPTION) $<
@@ -75,15 +76,15 @@ o/$(MODE)/%.runs: o/$(MODE)/%; @ACTION=CHECK.runs TARGET=$< build/runcom $< $(TE
o/$(MODE)/%.pkg:; @build/package $(OUTPUT_OPTION) $(addprefix -d,$(filter %.pkg,$^)) $(filter %.o,$^)
o/$(MODE)/%.zip.o: %; @build/zipobj $(OUTPUT_OPTION) $<
-o/$(MODE)/%-gcc.asm: %.c; @ACTION=COMPILE.c build/compile $(COMPILE.c) $(OUTPUT_OPTION) $<
+o/$(MODE)/%-gcc.asm: %.c; @ACTION=OBJECTIFY.c build/compile $(OBJECTIFY.c) -S -g0 $(OUTPUT_OPTION) $<
o/$(MODE)/%-clang.asm: CC = $(CLANG)
-o/$(MODE)/%-clang.asm: %.c; @ACTION=COMPILE.c build/compile $(COMPILE.c) $(OUTPUT_OPTION) $< || echo / need $(CLANG) >$@
-o/$(MODE)/%-gcc.asm: %.f; @ACTION=COMPILE.f build/compile $(COMPILE.f) $(OUTPUT_OPTION) $<
+o/$(MODE)/%-clang.asm: %.c; @ACTION=OBJECTIFY.c build/compile $(OBJECTIFY.c) -S -g0 $(OUTPUT_OPTION) $< || echo / need $(CLANG) >$@
+o/$(MODE)/%-gcc.asm: %.f; @ACTION=OBJECTIFY.f build/compile $(OBJECTIFY.f) -S -g0 $(OUTPUT_OPTION) $<
o/$(MODE)/%-clang.asm: CC = $(CLANG)
-o/$(MODE)/%-clang.asm: %.f; @ACTION=COMPILE.f build/compile $(COMPILE.f) $(OUTPUT_OPTION) $< || echo / need $(CLANG) >$@
-o/$(MODE)/%-gcc.asm: %.F; @ACTION=COMPILE.F build/compile $(COMPILE.F) $(OUTPUT_OPTION) $<
+o/$(MODE)/%-clang.asm: %.f; @ACTION=OBJECTIFY.f build/compile $(OBJECTIFY.f) -S -g0 $(OUTPUT_OPTION) $< || echo / need $(CLANG) >$@
+o/$(MODE)/%-gcc.asm: %.F; @ACTION=OBJECTIFY.F build/compile $(OBJECTIFY.F) -S -g0 $(OUTPUT_OPTION) $<
o/$(MODE)/%-clang.asm: CC = $(CLANG)
-o/$(MODE)/%-clang.asm: %.F; @ACTION=COMPILE.F build/compile $(COMPILE.F) $(OUTPUT_OPTION) $< || echo / need $(CLANG) >$@
+o/$(MODE)/%-clang.asm: %.F; @ACTION=OBJECTIFY.F build/compile $(OBJECTIFY.F) -S -g0 $(OUTPUT_OPTION) $< || echo / need $(CLANG) >$@
# ragel state machine compiler
.PRECIOUS: build/bootstrap/%.c.gz
diff --git a/dsp/mpeg/mpeg1.c b/dsp/mpeg/mpeg1.c
index fa708c196..968ccb382 100644
--- a/dsp/mpeg/mpeg1.c
+++ b/dsp/mpeg/mpeg1.c
@@ -32,6 +32,7 @@
#include "dsp/mpeg/idct.h"
#include "dsp/mpeg/mpeg.h"
#include "dsp/mpeg/video.h"
+#include "libc/bits/initializer.h"
#include "libc/conv/conv.h"
#include "libc/log/log.h"
#include "libc/macros.h"
@@ -455,18 +456,6 @@ long plmpegdecode_latency_;
static plm_vlc_t *PLM_VIDEO_MACROBLOCK_TYPE[4];
static plm_vlc_t *PLM_VIDEO_DCT_SIZE[3];
-static textstartup void __init_mpeg1(void) {
- PLM_VIDEO_MACROBLOCK_TYPE[0] = NULL;
- PLM_VIDEO_MACROBLOCK_TYPE[1] = PLM_VIDEO_MACROBLOCK_TYPE_INTRA;
- PLM_VIDEO_MACROBLOCK_TYPE[2] = PLM_VIDEO_MACROBLOCK_TYPE_PREDICTIVE,
- PLM_VIDEO_MACROBLOCK_TYPE[3] = PLM_VIDEO_MACROBLOCK_TYPE_B;
- PLM_VIDEO_DCT_SIZE[0] = PLM_VIDEO_DCT_SIZE_LUMINANCE;
- PLM_VIDEO_DCT_SIZE[1] = PLM_VIDEO_DCT_SIZE_CHROMINANCE;
- PLM_VIDEO_DCT_SIZE[2] = PLM_VIDEO_DCT_SIZE_CHROMINANCE;
-}
-
-INITIALIZER(300, _init_mpeg1, __init_mpeg1());
-
#define plm_clamp(n) MIN(255, MAX(0, n))
void plm_video_destroy(plm_video_t *self) {
@@ -1113,3 +1102,15 @@ plm_video_t *plm_video_create_with_buffer(plm_buffer_t *buffer,
}
return self;
}
+
+static textstartup void plm_video_init(void) {
+ PLM_VIDEO_MACROBLOCK_TYPE[0] = NULL;
+ PLM_VIDEO_MACROBLOCK_TYPE[1] = PLM_VIDEO_MACROBLOCK_TYPE_INTRA;
+ PLM_VIDEO_MACROBLOCK_TYPE[2] = PLM_VIDEO_MACROBLOCK_TYPE_PREDICTIVE,
+ PLM_VIDEO_MACROBLOCK_TYPE[3] = PLM_VIDEO_MACROBLOCK_TYPE_B;
+ PLM_VIDEO_DCT_SIZE[0] = PLM_VIDEO_DCT_SIZE_LUMINANCE;
+ PLM_VIDEO_DCT_SIZE[1] = PLM_VIDEO_DCT_SIZE_CHROMINANCE;
+ PLM_VIDEO_DCT_SIZE[2] = PLM_VIDEO_DCT_SIZE_CHROMINANCE;
+}
+
+const void *const plm_video_init_ctor[] initarray = {plm_video_init};
diff --git a/dsp/mpeg/ycbcrio.h b/dsp/mpeg/ycbcrio.h
index aff709854..c2a787bbe 100644
--- a/dsp/mpeg/ycbcrio.h
+++ b/dsp/mpeg/ycbcrio.h
@@ -1,7 +1,7 @@
#ifndef COSMOPOLITAN_DSP_MPEG_YCBCRIO_H_
#define COSMOPOLITAN_DSP_MPEG_YCBCRIO_H_
#include "dsp/mpeg/mpeg.h"
-#include "libc/bits/bits.h"
+#include "libc/bits/bswap.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
diff --git a/dsp/scale/gyarados.c b/dsp/scale/gyarados.c
index dbc4f6d0f..a2a01854b 100644
--- a/dsp/scale/gyarados.c
+++ b/dsp/scale/gyarados.c
@@ -30,7 +30,6 @@
#include "libc/mem/mem.h"
#include "libc/nexgen32e/bsr.h"
#include "libc/runtime/gc.h"
-#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
@@ -44,7 +43,7 @@
* @see Magikarp
*/
-#define M 14
+#define M 15
#define SQR(X) ((X) * (X))
struct SamplingSolution {
@@ -148,9 +147,9 @@ static int Sharpen(int ax, int bx, int cx) {
static void GyaradosImpl(long dyw, long dxw, int dst[dyw][dxw], long syw,
long sxw, const int src[syw][sxw], long dyn, long dxn,
- long syn, long sxn, int tmp0[restrict dyn][sxn],
- int tmp1[restrict dyn][sxn],
- int tmp2[restrict dyn][dxn], long yfn, long xfn,
+ long syn, long sxn, short tmp0[restrict dyn][sxn],
+ short tmp1[restrict dyn][sxn],
+ short tmp2[restrict dyn][dxn], long yfn, long xfn,
const short fyi[dyn][yfn], const short fyw[dyn][yfn],
const short fxi[dxn][xfn], const short fxw[dxn][xfn],
bool sharpen) {
@@ -165,7 +164,6 @@ static void GyaradosImpl(long dyw, long dxw, int dst[dyw][dxw], long syw,
}
}
for (dy = 0; dy < dyn; ++dy) {
- /* TODO: pmulhrsw() would probably make this much faster */
for (sx = 0; sx < sxn; ++sx) {
tmp1[dy][sx] = sharpen ? Sharpen(tmp0[MIN(dyn - 1, MAX(0, dy - 1))][sx],
tmp0[dy][sx],
@@ -218,9 +216,9 @@ void *Gyarados(long dyw, long dxw, int dst[dyw][dxw], long syw, long sxw,
CHECK_LE(syn, 0x7fff);
CHECK_LE(sxn, 0x7fff);
GyaradosImpl(dyw, dxw, dst, syw, sxw, src, dyn, dxn, syn, sxn,
- gc(xmemalign(64, sizeof(int) * dyn * sxn)),
- gc(xmemalign(64, sizeof(int) * dyn * sxn)),
- gc(xmemalign(64, sizeof(int) * dyn * dxn)), cy->s, cx->s,
+ gc(xmemalign(64, sizeof(short) * dyn * sxn)),
+ gc(xmemalign(64, sizeof(short) * dyn * sxn)),
+ gc(xmemalign(64, sizeof(short) * dyn * dxn)), cy->s, cx->s,
cy->indices, cy->weights, cx->indices, cx->weights, sharpen);
} else {
ZeroMatrix(dyw, dxw, dst, dyn, dxn);
diff --git a/dsp/scale/scale.mk b/dsp/scale/scale.mk
index 6c9035eb7..a342e4237 100644
--- a/dsp/scale/scale.mk
+++ b/dsp/scale/scale.mk
@@ -47,6 +47,13 @@ $(DSP_SCALE_A).pkg: \
$(DSP_SCALE_A_OBJS) \
$(foreach x,$(DSP_SCALE_A_DIRECTDEPS),$($(x)_A).pkg)
+o/$(MODE)/dsp/scale/cdecimate2xuint8x8.o \
+o/$(MODE)/dsp/scale/gyarados.o \
+o/$(MODE)/dsp/scale/magikarp.o \
+o/$(MODE)/dsp/scale/scale.o: \
+ OVERRIDE_CFLAGS += \
+ $(MATHEMATICAL)
+
DSP_SCALE_LIBS = $(foreach x,$(DSP_SCALE_ARTIFACTS),$($(x)))
DSP_SCALE_SRCS = $(foreach x,$(DSP_SCALE_ARTIFACTS),$($(x)_SRCS))
DSP_SCALE_HDRS = $(foreach x,$(DSP_SCALE_ARTIFACTS),$($(x)_HDRS))
diff --git a/dsp/tty/ident.c b/dsp/tty/ident.c
index 3560c270f..ef840b40b 100644
--- a/dsp/tty/ident.c
+++ b/dsp/tty/ident.c
@@ -18,8 +18,8 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "dsp/tty/tty.h"
-#include "libc/bits/bits.h"
#include "libc/bits/safemacros.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/termios.h"
#include "libc/fmt/fmt.h"
diff --git a/dsp/tty/itoa8.c b/dsp/tty/itoa8.c
index c7ecffb15..1cfc40695 100644
--- a/dsp/tty/itoa8.c
+++ b/dsp/tty/itoa8.c
@@ -19,15 +19,15 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "dsp/tty/itoa8.h"
#include "libc/bits/bits.h"
+#include "libc/bits/initializer.h"
#include "libc/str/str.h"
struct Itoa8 kItoa8;
-static nooptimize textstartup void itoa8init(void) {
- size_t i;
+static textstartup void itoa8_init(void) {
+ int i;
uint8_t z;
char p[4];
- /*102*/
for (i = 0; i < 256; ++i) {
memset(p, 0, sizeof(p));
if (i < 10) {
@@ -48,4 +48,4 @@ static nooptimize textstartup void itoa8init(void) {
}
}
-INITIALIZER(301, _init_itoa8, itoa8init());
+const void *const itoa8_init_ctor[] initarray = {itoa8_init};
diff --git a/dsp/tty/quant.h b/dsp/tty/quant.h
index 9d08bf0eb..ea6ccf4ca 100644
--- a/dsp/tty/quant.h
+++ b/dsp/tty/quant.h
@@ -64,9 +64,10 @@ extern double g_xterm256_gamma;
extern struct TtyRgb g_ansi2rgb_[256];
extern struct TtyQuant g_ttyquant_;
extern const uint8_t kXtermXlat[2][256];
+extern const uint8_t kXtermCube[6];
-void ttyquantinit(enum TtyQuantizationAlgorithm, enum TtyQuantizationChannels,
- enum TtyBlocksSelection);
+void ttyquantsetup(enum TtyQuantizationAlgorithm, enum TtyQuantizationChannels,
+ enum TtyBlocksSelection);
extern char *ttyraster(char *, const struct TtyRgb *, size_t, size_t,
struct TtyRgb, struct TtyRgb);
diff --git a/dsp/tty/rgb2ansi.c b/dsp/tty/rgb2ansi.c
index f418d0ecc..e0b3de016 100644
--- a/dsp/tty/rgb2ansi.c
+++ b/dsp/tty/rgb2ansi.c
@@ -20,6 +20,7 @@
#include "dsp/core/core.h"
#include "dsp/tty/quant.h"
#include "libc/assert.h"
+#include "libc/bits/initializer.h"
#include "libc/limits.h"
#include "libc/log/log.h"
#include "libc/macros.h"
@@ -32,12 +33,8 @@
#define SQR(X) ((X) * (X))
#define SUM(X, Y, Z) ((X) + (Y) + (Z))
-static const uint8_t kXtermCube[] = {0, 0137, 0207, 0257, 0327, 0377};
+const uint8_t kXtermCube[6] = {0, 0137, 0207, 0257, 0327, 0377};
struct TtyRgb g_ansi2rgb_[256];
-static uint8_t g_quant[256];
-static uint8_t g_rindex[256];
-static uint8_t g_gindex[256];
-static uint8_t g_bindex[256];
double g_xterm256_gamma;
struct TtyRgb tty2rgb_(struct TtyRgb rgbxt) {
@@ -101,17 +98,10 @@ static int uncube(int x) {
return x < 48 ? 0 : x < 115 ? 1 : (x - 35) / 40;
}
-static optimizesize textstartup void xterm2rgbsetup_(void) {
+static textstartup void rgb2ansi_init(void) {
uint8_t c, y;
uint32_t i, j;
memcpy(g_ansi2rgb_, &kCgaPalette, sizeof(kCgaPalette));
- for (i = 0; i < 256; ++i) {
- j = uncube(i);
- g_quant[i] = kXtermCube[j];
- g_rindex[i] = j * 36;
- g_gindex[i] = j * 6;
- g_bindex[i] = j + 16;
- }
for (i = 16; i < 232; ++i) {
g_ansi2rgb_[i].r = kXtermCube[((i - 020) / 044) % 06];
g_ansi2rgb_[i].g = kXtermCube[((i - 020) / 06) % 06];
@@ -126,4 +116,4 @@ static optimizesize textstartup void xterm2rgbsetup_(void) {
}
}
-INITIALIZER(301, _init_ansi2rgb, xterm2rgbsetup_());
+const void *const rgb2ansi_init_ctor[] initarray = {rgb2ansi_init};
diff --git a/dsp/tty/rgb2xterm256.c b/dsp/tty/rgb2xterm256.c
deleted file mode 100644
index 3f37a1f6c..000000000
--- a/dsp/tty/rgb2xterm256.c
+++ /dev/null
@@ -1,71 +0,0 @@
-/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
-│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "dsp/tty/rgb2xterm256.h"
-
-/* 1bc */
-forceinline int sqr(int x) { return x * x; }
-/* forceinline int dst6(int x) { return x * x; } */
-int rgb2xterm256v2(int r, int g, int b) {
- static const int i2cv[] = {0, 0137, 0207, 0257, 0327, 0377, 0377};
-#define v2ci(v) (v < 060 ? 0 : v < 0163 ? 01 : (v - 043) / 050)
-#define dst6(A, B, C, a, b, c) (sqr(A - a) + sqr(B - b) + sqr(C - c))
- int ir = v2ci(r);
- int ig = v2ci(g);
- int ib = v2ci(b);
- int avg = (r + g + b) / 3;
- int cr = i2cv[ir];
- int cg = i2cv[ig];
- int cb = i2cv[ib];
- int gidx = avg > 238 ? 23 : (avg - 3) / 10;
- int gv = 8 + 10 * gidx;
- int cerr = dst6(cr, cg, cb, r, g, b);
- int gerr = dst6(gv, gv, gv, r, g, b);
- return cerr <= gerr ? 16 + (36 * ir + 6 * ig + ib) : 232 + gidx;
-#undef dst6
-#undef cidx
-#undef v2ci
-}
-
-/* 1e3 */
-// Convert RGB24 to xterm-256 8-bit value
-// For simplicity, assume RGB space is perceptually uniform.
-// There are 5 places where one of two outputs needs to be chosen when
-// input is the exact middle:
-// - The r/g/b channels and the gray value: choose higher value output
-// - If gray and color have same distance from input - choose color
-int rgb2xterm256(uint8_t r, uint8_t g, uint8_t b) {
- // Calculate the nearest 0-based color index at 16 .. 231
-#define v2ci(v) (v < 48 ? 0 : v < 115 ? 1 : (v - 35) / 40)
- int ir = v2ci(r), ig = v2ci(g), ib = v2ci(b); // 0..5 each
-#define color_index() (36 * ir + 6 * ig + ib) /* 0..215, lazy eval */
- // Calculate the nearest 0-based gray index at 232 .. 255
- int average = (r + g + b) / 3;
- int gray_index = average > 238 ? 23 : (average - 3) / 10; // 0..23
- // Calculate the represented colors back from the index
- static const int i2cv[6] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff};
- int cr = i2cv[ir], cg = i2cv[ig], cb = i2cv[ib]; // r/g/b, 0..255 each
- int gv = 8 + 10 * gray_index; // same value for r/g/b, 0..255
-// Return the one which is nearer to the original input rgb value
-#define dist_square(A, B, C, a, b, c) \
- ((A - a) * (A - a) + (B - b) * (B - b) + (C - c) * (C - c))
- int color_err = dist_square(cr, cg, cb, r, g, b);
- int gray_err = dist_square(gv, gv, gv, r, g, b);
- return color_err <= gray_err ? 16 + color_index() : 232 + gray_index;
-}
diff --git a/dsp/tty/rgb2xterm256.h b/dsp/tty/rgb2xterm256.h
deleted file mode 100644
index 071de016b..000000000
--- a/dsp/tty/rgb2xterm256.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#ifndef COSMOPOLITAN_DSP_TTY_RGB2XTERM256_H_
-#define COSMOPOLITAN_DSP_TTY_RGB2XTERM256_H_
-#if !(__ASSEMBLER__ + __LINKER__ + 0)
-COSMOPOLITAN_C_START_
-
-int rgb2xterm256(uint8_t, uint8_t, uint8_t);
-int rgb2xterm256v2(int, int, int);
-
-COSMOPOLITAN_C_END_
-#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#endif /* COSMOPOLITAN_DSP_TTY_RGB2XTERM256_H_ */
diff --git a/dsp/tty/quantinit.c b/dsp/tty/ttyquant.c
similarity index 89%
rename from dsp/tty/quantinit.c
rename to dsp/tty/ttyquant.c
index 07caa3e10..f1b02ae4d 100644
--- a/dsp/tty/quantinit.c
+++ b/dsp/tty/ttyquant.c
@@ -19,6 +19,7 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "dsp/tty/internal.h"
#include "dsp/tty/quant.h"
+#include "libc/bits/initializer.h"
#include "libc/dce.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
@@ -29,9 +30,9 @@ struct TtyQuant g_ttyquant_;
/**
* Chooses xterm quantization mode.
*/
-optimizesize textstartup void ttyquantinit(enum TtyQuantizationAlgorithm alg,
- enum TtyQuantizationChannels chans,
- enum TtyBlocksSelection blocks) {
+textstartup void ttyquantsetup(enum TtyQuantizationAlgorithm alg,
+ enum TtyQuantizationChannels chans,
+ enum TtyBlocksSelection blocks) {
switch (alg) {
case kTtyQuantAnsi:
TTYQUANT()->rgb2tty = rgb2ansi_;
@@ -74,5 +75,8 @@ optimizesize textstartup void ttyquantinit(enum TtyQuantizationAlgorithm alg,
TTYQUANT()->blocks = blocks;
}
-INITIALIZER(400, _init_ttyquant,
- ttyquantinit(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode));
+textstartup void ttyquant_init(void) {
+ ttyquantsetup(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode);
+}
+
+const void *const ttyquant_init_ctor[] initarray = {ttyquant_init};
diff --git a/dsp/tty/ttyraw.c b/dsp/tty/ttyraw.c
index a285cbe92..e6cbfa098 100644
--- a/dsp/tty/ttyraw.c
+++ b/dsp/tty/ttyraw.c
@@ -42,6 +42,7 @@
static struct TtyRaw {
bool setup;
+ bool hasold;
bool noreentry;
bool initialized;
enum TtyRawFlags flags;
@@ -51,12 +52,15 @@ static struct TtyRaw {
} g_ttyraw;
static textstartup int ttyraw_setup(void) {
- if (isatty(FD) &&
- ttyconfig(FD, ttysetrawmode, g_ttyraw.flags, &g_ttyraw.old) != -1) {
- return 0;
- } else {
- return -1;
+ struct termios *old;
+ if (isatty(FD)) {
+ old = !g_ttyraw.hasold ? &g_ttyraw.old : NULL;
+ if (ttyconfig(FD, ttysetrawmode, g_ttyraw.flags, old) != -1) {
+ g_ttyraw.hasold = true;
+ return 0;
+ }
}
+ return -1;
}
static textstartup int ttyraw_enable(void) {
diff --git a/dsp/tty/xtermname.c b/dsp/tty/xtermname.c
new file mode 100644
index 000000000..b238c7e99
--- /dev/null
+++ b/dsp/tty/xtermname.c
@@ -0,0 +1,282 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "dsp/tty/xtermname.h"
+
+/**
+ * 256-entry double-nul-terminated list of XTERM256 names.
+ */
+const char kXtermName[] = "\
+Black\0\
+Maroon\0\
+Green\0\
+Olive\0\
+Navy\0\
+Purple\0\
+Teal\0\
+Silver\0\
+Grey\0\
+Red\0\
+Lime\0\
+Yellow\0\
+Blue\0\
+Fuchsia\0\
+Aqua\0\
+White\0\
+Grey0\0\
+NavyBlue\0\
+DarkBlue\0\
+Blue3\0\
+Blue3\0\
+Blue1\0\
+DarkGreen\0\
+DeepSkyBlue4\0\
+DeepSkyBlue4\0\
+DeepSkyBlue4\0\
+DodgerBlue3\0\
+DodgerBlue2\0\
+Green4\0\
+SpringGreen4\0\
+Turquoise4\0\
+DeepSkyBlue3\0\
+DeepSkyBlue3\0\
+DodgerBlue1\0\
+Green3\0\
+SpringGreen3\0\
+DarkCyan\0\
+LightSeaGreen\0\
+DeepSkyBlue2\0\
+DeepSkyBlue1\0\
+Green3\0\
+SpringGreen3\0\
+SpringGreen2\0\
+Cyan3\0\
+DarkTurquoise\0\
+Turquoise2\0\
+Green1\0\
+SpringGreen2\0\
+SpringGreen1\0\
+MediumSpringGreen\0\
+Cyan2\0\
+Cyan1\0\
+DarkRed\0\
+DeepPink4\0\
+Purple4\0\
+Purple4\0\
+Purple3\0\
+BlueViolet\0\
+Orange4\0\
+Grey37\0\
+MediumPurple4\0\
+SlateBlue3\0\
+SlateBlue3\0\
+RoyalBlue1\0\
+Chartreuse4\0\
+DarkSeaGreen4\0\
+PaleTurquoise4\0\
+SteelBlue\0\
+SteelBlue3\0\
+CornflowerBlue\0\
+Chartreuse3\0\
+DarkSeaGreen4\0\
+CadetBlue\0\
+CadetBlue\0\
+SkyBlue3\0\
+SteelBlue1\0\
+Chartreuse3\0\
+PaleGreen3\0\
+SeaGreen3\0\
+Aquamarine3\0\
+MediumTurquoise\0\
+SteelBlue1\0\
+Chartreuse2\0\
+SeaGreen2\0\
+SeaGreen1\0\
+SeaGreen1\0\
+Aquamarine1\0\
+DarkSlateGray2\0\
+DarkRed\0\
+DeepPink4\0\
+DarkMagenta\0\
+DarkMagenta\0\
+DarkViolet\0\
+Purple\0\
+Orange4\0\
+LightPink4\0\
+Plum4\0\
+MediumPurple3\0\
+MediumPurple3\0\
+SlateBlue1\0\
+Yellow4\0\
+Wheat4\0\
+Grey53\0\
+LightSlateGrey\0\
+MediumPurple\0\
+LightSlateBlue\0\
+Yellow4\0\
+DarkOliveGreen3\0\
+DarkSeaGreen\0\
+LightSkyBlue3\0\
+LightSkyBlue3\0\
+SkyBlue2\0\
+Chartreuse2\0\
+DarkOliveGreen3\0\
+PaleGreen3\0\
+DarkSeaGreen3\0\
+DarkSlateGray3\0\
+SkyBlue1\0\
+Chartreuse1\0\
+LightGreen\0\
+LightGreen\0\
+PaleGreen1\0\
+Aquamarine1\0\
+DarkSlateGray1\0\
+Red3\0\
+DeepPink4\0\
+MediumVioletRed\0\
+Magenta3\0\
+DarkViolet\0\
+Purple\0\
+DarkOrange3\0\
+IndianRed\0\
+HotPink3\0\
+MediumOrchid3\0\
+MediumOrchid\0\
+MediumPurple2\0\
+DarkGoldenrod\0\
+LightSalmon3\0\
+RosyBrown\0\
+Grey63\0\
+MediumPurple2\0\
+MediumPurple1\0\
+Gold3\0\
+DarkKhaki\0\
+NavajoWhite3\0\
+Grey69\0\
+LightSteelBlue3\0\
+LightSteelBlue\0\
+Yellow3\0\
+DarkOliveGreen3\0\
+DarkSeaGreen3\0\
+DarkSeaGreen2\0\
+LightCyan3\0\
+LightSkyBlue1\0\
+GreenYellow\0\
+DarkOliveGreen2\0\
+PaleGreen1\0\
+DarkSeaGreen2\0\
+DarkSeaGreen1\0\
+PaleTurquoise1\0\
+Red3\0\
+DeepPink3\0\
+DeepPink3\0\
+Magenta3\0\
+Magenta3\0\
+Magenta2\0\
+DarkOrange3\0\
+IndianRed\0\
+HotPink3\0\
+HotPink2\0\
+Orchid\0\
+MediumOrchid1\0\
+Orange3\0\
+LightSalmon3\0\
+LightPink3\0\
+Pink3\0\
+Plum3\0\
+Violet\0\
+Gold3\0\
+LightGoldenrod3\0\
+Tan\0\
+MistyRose3\0\
+Thistle3\0\
+Plum2\0\
+Yellow3\0\
+Khaki3\0\
+LightGoldenrod2\0\
+LightYellow3\0\
+Grey84\0\
+LightSteelBlue1\0\
+Yellow2\0\
+DarkOliveGreen1\0\
+DarkOliveGreen1\0\
+DarkSeaGreen1\0\
+Honeydew2\0\
+LightCyan1\0\
+Red1\0\
+DeepPink2\0\
+DeepPink1\0\
+DeepPink1\0\
+Magenta2\0\
+Magenta1\0\
+OrangeRed1\0\
+IndianRed1\0\
+IndianRed1\0\
+HotPink\0\
+HotPink\0\
+MediumOrchid1\0\
+DarkOrange\0\
+Salmon1\0\
+LightCoral\0\
+PaleVioletRed1\0\
+Orchid2\0\
+Orchid1\0\
+Orange1\0\
+SandyBrown\0\
+LightSalmon1\0\
+LightPink1\0\
+Pink1\0\
+Plum1\0\
+Gold1\0\
+LightGoldenrod2\0\
+LightGoldenrod2\0\
+NavajoWhite1\0\
+MistyRose1\0\
+Thistle1\0\
+Yellow1\0\
+LightGoldenrod1\0\
+Khaki1\0\
+Wheat1\0\
+Cornsilk1\0\
+Grey100\0\
+Grey3\0\
+Grey7\0\
+Grey11\0\
+Grey15\0\
+Grey19\0\
+Grey23\0\
+Grey27\0\
+Grey30\0\
+Grey35\0\
+Grey39\0\
+Grey42\0\
+Grey46\0\
+Grey50\0\
+Grey54\0\
+Grey58\0\
+Grey62\0\
+Grey66\0\
+Grey70\0\
+Grey74\0\
+Grey78\0\
+Grey82\0\
+Grey85\0\
+Grey89\0\
+Grey93\0\
+\0";
diff --git a/dsp/tty/xtermname.h b/dsp/tty/xtermname.h
new file mode 100644
index 000000000..ed3551b80
--- /dev/null
+++ b/dsp/tty/xtermname.h
@@ -0,0 +1,10 @@
+#ifndef COSMOPOLITAN_DSP_TTY_XTERMNAME_H_
+#define COSMOPOLITAN_DSP_TTY_XTERMNAME_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+extern const char kXtermName[];
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_DSP_TTY_XTERMNAME_H_ */
diff --git a/examples/bigmem.c b/examples/bigmem.c
index fd58835c8..c311ab3b5 100644
--- a/examples/bigmem.c
+++ b/examples/bigmem.c
@@ -8,6 +8,7 @@
╚─────────────────────────────────────────────────────────────────*/
#endif
#include "libc/calls/calls.h"
+#include "libc/calls/hefty/copyfile.h"
#include "libc/calls/hefty/spawn.h"
#include "libc/calls/struct/stat.h"
#include "libc/log/check.h"
@@ -87,7 +88,7 @@ int main(int argc, char *argv[]) {
CHECK_NE(-1, close(fd));
t1 = dtime(CLOCK_REALTIME);
- CHECK_NE(-1, copyfile(core, core2, false));
+ CHECK_NE(-1, copyfile(core, core2, 0));
t2 = dtime(CLOCK_REALTIME);
printf("%.6Lf\n", t2 - t1);
diff --git a/examples/cp.c b/examples/cp.c
new file mode 100644
index 000000000..99bcdb67c
--- /dev/null
+++ b/examples/cp.c
@@ -0,0 +1,96 @@
+#if 0
+/*─────────────────────────────────────────────────────────────────╗
+│ To the extent possible under law, Justine Tunney has waived │
+│ all copyright and related or neighboring rights to this file, │
+│ as it is written in the following disclaimers: │
+│ • http://unlicense.org/ │
+│ • http://creativecommons.org/publicdomain/zero/1.0/ │
+╚─────────────────────────────────────────────────────────────────*/
+#endif
+#include "libc/calls/calls.h"
+#include "libc/calls/hefty/copyfile.h"
+#include "libc/conv/conv.h"
+#include "libc/errno.h"
+#include "libc/fmt/fmt.h"
+#include "libc/runtime/gc.h"
+#include "libc/runtime/runtime.h"
+#include "libc/stdio/stdio.h"
+#include "libc/str/str.h"
+#include "libc/sysv/consts/ex.h"
+#include "libc/sysv/consts/exit.h"
+#include "libc/sysv/consts/ok.h"
+#include "libc/x/x.h"
+#include "third_party/getopt/getopt.h"
+
+#define USAGE \
+ " SRC... DST\n\
+\n\
+SYNOPSIS\n\
+\n\
+ Copies Files\n\
+\n\
+FLAGS\n\
+\n\
+ -?\n\
+ -h help\n\
+ -f force\n\
+ -n no clobber\n\
+ -a preserve all\n\
+ -p preserve owner and timestamps\n\
+\n"
+
+int flags;
+bool force;
+
+noreturn void PrintUsage(int rc, FILE *f) {
+ fprintf(f, "%s%s%s", "Usage: ", program_invocation_name, USAGE);
+ exit(rc);
+}
+
+void GetOpts(int argc, char *argv[]) {
+ int opt;
+ while ((opt = getopt(argc, argv, "?hfnap")) != -1) {
+ switch (opt) {
+ case 'f':
+ force = true;
+ break;
+ case 'n':
+ flags |= COPYFILE_NOCLOBBER;
+ break;
+ case 'a':
+ case 'p':
+ flags |= COPYFILE_PRESERVE_OWNER;
+ flags |= COPYFILE_PRESERVE_TIMESTAMPS;
+ break;
+ case 'h':
+ case '?':
+ PrintUsage(EXIT_SUCCESS, stdout);
+ default:
+ PrintUsage(EX_USAGE, stderr);
+ }
+ }
+}
+
+int cp(const char *src, const char *dst) {
+ if (endswith(dst, "/") || isdirectory(dst)) {
+ dst = gc(xasprintf("%s/%s", dst, basename));
+ }
+ if (!force && access(dst, W_OK) == -1 && errno != ENOENT) goto OnFail;
+ if (copyfile(src, dst, flags) == -1) goto OnFail;
+ return 0;
+OnFail:
+ fprintf(stderr, "%s %s %s: %s\n", "error: cp", src, dst, strerror(errno));
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ int i;
+ GetOpts(argc, argv);
+ if (argc - optind < 2) PrintUsage(EX_USAGE, stderr);
+ for (i = optind; i < argc - 1; ++i) {
+ if (cp(argv[i], argv[argc - 1]) == -1) {
+ return -1;
+ }
+ }
+ return 0;
+}
diff --git a/examples/examples.mk b/examples/examples.mk
index 92d2868f8..304cd0a2b 100644
--- a/examples/examples.mk
+++ b/examples/examples.mk
@@ -8,120 +8,135 @@ EXAMPLES_MAINS_S = $(filter %.S,$(EXAMPLES_FILES))
EXAMPLES_MAINS_C = $(filter %.c,$(EXAMPLES_FILES))
EXAMPLES_MAINS_CC = $(filter %.cc,$(EXAMPLES_FILES))
-EXAMPLES_SRCS = \
- $(EXAMPLES_MAINS_S) \
- $(EXAMPLES_MAINS_C) \
+EXAMPLES_SRCS = \
+ $(EXAMPLES_MAINS_S) \
+ $(EXAMPLES_MAINS_C) \
$(EXAMPLES_MAINS_CC)
-EXAMPLES_MAINS = \
- $(EXAMPLES_MAINS_S) \
- $(EXAMPLES_MAINS_C) \
+EXAMPLES_MAINS = \
+ $(EXAMPLES_MAINS_S) \
+ $(EXAMPLES_MAINS_C) \
$(EXAMPLES_MAINS_CC)
-EXAMPLES_OBJS = \
- $(EXAMPLES_SRCS:%=o/$(MODE)/%.zip.o) \
- $(EXAMPLES_MAINS_S:%.S=o/$(MODE)/%.o) \
- $(EXAMPLES_MAINS_C:%.c=o/$(MODE)/%.o) \
+EXAMPLES_OBJS = \
+ $(EXAMPLES_SRCS:%=o/$(MODE)/%.zip.o) \
+ $(EXAMPLES_MAINS_S:%.S=o/$(MODE)/%.o) \
+ $(EXAMPLES_MAINS_C:%.c=o/$(MODE)/%.o) \
$(EXAMPLES_MAINS_CC:%.cc=o/$(MODE)/%.o)
-EXAMPLES_COMS = \
- $(EXAMPLES_OBJS:%.o=%.com)
+EXAMPLES_COMS = \
+ $(EXAMPLES_MAINS_S:%.S=o/$(MODE)/%.com) \
+ $(EXAMPLES_MAINS_C:%.c=o/$(MODE)/%.com) \
+ $(EXAMPLES_MAINS_CC:%.cc=o/$(MODE)/%.com)
-EXAMPLES_ELFS = \
+EXAMPLES_ELFS = \
$(EXAMPLES_OBJS:%.o=%.elf)
-EXAMPLES_BINS = \
- $(EXAMPLES_ELFS) \
- $(EXAMPLES_COMS) \
+EXAMPLES_BINS = \
+ $(EXAMPLES_ELFS) \
+ $(EXAMPLES_COMS) \
$(EXAMPLES_COMS:%=%.dbg)
-EXAMPLES_DIRECTDEPS = \
- APE_LIB \
- DSP_CORE \
- DSP_SCALE \
- DSP_TTY \
- LIBC_ALG \
- LIBC_BITS \
- LIBC_CALLS \
- LIBC_CALLS_HEFTY \
- LIBC_CONV \
- LIBC_FMT \
- LIBC_LOG \
- LIBC_MEM \
- LIBC_NEXGEN32E \
- LIBC_NT_KERNELBASE \
- LIBC_NT_NTDLL \
- LIBC_NT_USER32 \
- LIBC_OHMYPLUS \
- LIBC_RAND \
- LIBC_RUNTIME \
- LIBC_SOCK \
- LIBC_STDIO \
- LIBC_STR \
- LIBC_STUBS \
- LIBC_SYSV \
- LIBC_SYSV_CALLS \
- LIBC_TESTLIB \
- LIBC_TIME \
- LIBC_TINYMATH \
- LIBC_UNICODE \
- LIBC_X \
- THIRD_PARTY_COMPILER_RT \
- THIRD_PARTY_DLMALLOC \
- THIRD_PARTY_DTOA \
- THIRD_PARTY_GETOPT \
- THIRD_PARTY_MUSL \
- THIRD_PARTY_STB \
- THIRD_PARTY_XED \
- THIRD_PARTY_ZLIB \
+EXAMPLES_DIRECTDEPS = \
+ APE_LIB \
+ DSP_CORE \
+ DSP_SCALE \
+ DSP_TTY \
+ LIBC_ALG \
+ LIBC_BITS \
+ LIBC_CALLS \
+ LIBC_CALLS_HEFTY \
+ LIBC_CONV \
+ LIBC_FMT \
+ LIBC_LOG \
+ LIBC_MEM \
+ LIBC_NEXGEN32E \
+ LIBC_NT_KERNELBASE \
+ LIBC_NT_NTDLL \
+ LIBC_NT_USER32 \
+ LIBC_OHMYPLUS \
+ LIBC_RAND \
+ LIBC_RUNTIME \
+ LIBC_SOCK \
+ LIBC_STDIO \
+ LIBC_STR \
+ LIBC_STUBS \
+ LIBC_SYSV \
+ LIBC_SYSV_CALLS \
+ LIBC_TESTLIB \
+ LIBC_TIME \
+ LIBC_TINYMATH \
+ LIBC_UNICODE \
+ LIBC_X \
+ LIBC_ZIPOS \
+ THIRD_PARTY_COMPILER_RT \
+ THIRD_PARTY_DLMALLOC \
+ THIRD_PARTY_DTOA \
+ THIRD_PARTY_GETOPT \
+ THIRD_PARTY_MUSL \
+ THIRD_PARTY_STB \
+ THIRD_PARTY_XED \
+ THIRD_PARTY_ZLIB \
TOOL_VIZ_LIB
-EXAMPLES_DEPS := \
+EXAMPLES_DEPS := \
$(call uniq,$(foreach x,$(EXAMPLES_DIRECTDEPS),$($(x))))
-o/$(MODE)/examples/examples.pkg: \
- $(EXAMPLES_OBJS) \
- $(THIRD_PARTY_DUKTAPE_A).pkg \
+o/$(MODE)/examples/examples.pkg: \
+ $(EXAMPLES_OBJS) \
+ $(THIRD_PARTY_DUKTAPE_A).pkg \
$(foreach x,$(EXAMPLES_DIRECTDEPS),$($(x)_A).pkg)
-o/$(MODE)/examples/unbourne.o: \
- OVERRIDE_CPPFLAGS += \
+o/$(MODE)/examples/unbourne.o: \
+ OVERRIDE_CPPFLAGS += \
-DSTACK_FRAME_UNLIMITED
-o/$(MODE)/examples/%.com.dbg: \
- $(EXAMPLES_DEPS) \
- o/$(MODE)/examples/%.o \
- o/$(MODE)/examples/examples.pkg \
- $(CRT) \
+o/$(MODE)/examples/%.com.dbg: \
+ $(EXAMPLES_DEPS) \
+ o/$(MODE)/examples/%.o \
+ o/$(MODE)/examples/examples.pkg \
+ $(CRT) \
$(APE)
@$(APELINK)
-o/$(MODE)/examples/%.elf: \
- $(EXAMPLES_DEPS) \
- $(THIRD_PARTY_DUKTAPE) \
- o/$(MODE)/examples/%.o \
- $(CRT) \
+o/$(MODE)/examples/%.elf: \
+ $(EXAMPLES_DEPS) \
+ $(THIRD_PARTY_DUKTAPE) \
+ o/$(MODE)/examples/%.o \
+ $(CRT) \
$(ELF)
@$(ELFLINK)
$(EXAMPLES_OBJS): examples/examples.mk
-o/$(MODE)/examples/hellojs.com.dbg: \
- $(EXAMPLES_DEPS) \
- $(THIRD_PARTY_DUKTAPE) \
- o/$(MODE)/examples/hellojs.o \
- o/$(MODE)/examples/hello.js.zip.o \
- o/$(MODE)/examples/examples.pkg \
- $(CRT) \
+o/$(MODE)/examples/hellojs.com.dbg: \
+ $(EXAMPLES_DEPS) \
+ $(THIRD_PARTY_DUKTAPE) \
+ o/$(MODE)/examples/hellojs.o \
+ o/$(MODE)/examples/hello.js.zip.o \
+ o/$(MODE)/examples/examples.pkg \
+ $(CRT) \
$(APE)
@$(APELINK)
-o/$(MODE)/examples/ispell.com.dbg: \
- $(EXAMPLES_DEPS) \
- o/$(MODE)/examples/ispell.o \
- o/$(MODE)/usr/share/dict/words.zip.o \
- o/$(MODE)/examples/examples.pkg \
- $(CRT) \
+o/$(MODE)/examples/ispell.com.dbg: \
+ $(EXAMPLES_DEPS) \
+ o/$(MODE)/examples/ispell.o \
+ o/$(MODE)/usr/share/dict/words.zip.o \
+ o/$(MODE)/examples/examples.pkg \
+ $(CRT) \
+ $(APE)
+ @$(APELINK)
+
+o/$(MODE)/examples/nesemu1.com.dbg: \
+ $(EXAMPLES_DEPS) \
+ $(THIRD_PARTY_DUKTAPE) \
+ o/$(MODE)/examples/nesemu1.o \
+ o/$(MODE)/usr/share/rom/mario.nes.zip.o \
+ o/$(MODE)/usr/share/rom/zelda.nes.zip.o \
+ o/$(MODE)/usr/share/rom/tetris.nes.zip.o \
+ o/$(MODE)/examples/examples.pkg \
+ $(CRT) \
$(APE)
@$(APELINK)
@@ -129,7 +144,11 @@ o/$(MODE)/usr/share/dict/words: usr/share/dict/words.gz
@$(MKDIR) $(dir $@)
@$(GZ) $(ZFLAGS) <$< >$@
+o/$(MODE)/examples/ugh.ok: o/$(MODE)/examples/wut.com
+ $<
+ touch $@
+
.PHONY: o/$(MODE)/examples
-o/$(MODE)/examples: \
- o/$(MODE)/examples/package \
+o/$(MODE)/examples: \
+ o/$(MODE)/examples/package \
$(EXAMPLES_BINS)
diff --git a/examples/findprime.c b/examples/findprime.c
index 7a7524f59..fcc754171 100644
--- a/examples/findprime.c
+++ b/examples/findprime.c
@@ -28,6 +28,7 @@ static const char *magic_;
void OnInterrupt(int sig) {
showprogress_ = true;
}
+
void ShowProgress(uintmax_t i, uintmax_t j, uintmax_t n) {
fprintf(stderr, "%s%,40jd%,40jd%,40jd\r\n", magic_, i, j, n);
showprogress_ = false;
diff --git a/examples/hello.c b/examples/hello.c
index 3b2e16d09..0a367ca17 100644
--- a/examples/hello.c
+++ b/examples/hello.c
@@ -8,6 +8,7 @@
╚─────────────────────────────────────────────────────────────────*/
#endif
#include "libc/errno.h"
+#include "libc/log/log.h"
#include "libc/stdio/stdio.h"
int main() {
@@ -19,5 +20,5 @@ int main() {
* have that string consist solely of directives.
*/
printf("%s\n", "hello world");
- return errno;
+ return 0;
}
diff --git a/examples/kilo.c b/examples/kilo.c
index a9607fe95..f74a094d2 100644
--- a/examples/kilo.c
+++ b/examples/kilo.c
@@ -58,7 +58,7 @@ Contact: antirez@gmail.com\"\n\
#define _GNU_SOURCE
#include "libc/alg/alg.h"
-#include "libc/alg/arraylist.h"
+#include "libc/alg/arraylist2.h"
#include "libc/calls/calls.h"
#include "libc/calls/termios.h"
#include "libc/calls/weirdtypes.h"
@@ -879,7 +879,7 @@ struct abuf {
};
static void abAppend(struct abuf *ab, const char *s, int len) {
- concat(ab, s, len);
+ CONCAT(&ab->p, &ab->i, &ab->n, s, len);
}
/* This function writes the whole screen using VT100 escape characters
diff --git a/examples/nesemu1.cc b/examples/nesemu1.cc
index 04d53af21..20403b748 100644
--- a/examples/nesemu1.cc
+++ b/examples/nesemu1.cc
@@ -4,11 +4,13 @@
/* TRADEMARKS ARE OWNED BY THEIR RESPECTIVE OWNERS LAWYERCATS LUV TAUTOLOGIES */
/* https://bisqwit.iki.fi/jutut/kuvat/programming_examples/nesemu1/nesemu1.cc */
#include "dsp/core/core.h"
+#include "dsp/core/half.h"
#include "dsp/core/illumination.h"
#include "dsp/scale/scale.h"
#include "dsp/tty/itoa8.h"
#include "dsp/tty/quant.h"
#include "dsp/tty/tty.h"
+#include "libc/alg/arraylist2.h"
#include "libc/assert.h"
#include "libc/bits/bits.h"
#include "libc/bits/safemacros.h"
@@ -16,6 +18,7 @@
#include "libc/calls/hefty/spawn.h"
#include "libc/calls/struct/itimerval.h"
#include "libc/calls/struct/winsize.h"
+#include "libc/conv/conv.h"
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
#include "libc/inttypes.h"
@@ -30,6 +33,8 @@
#include "libc/sock/sock.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
+#include "libc/sysv/consts/ex.h"
+#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/fileno.h"
#include "libc/sysv/consts/itimer.h"
#include "libc/sysv/consts/o.h"
@@ -37,24 +42,77 @@
#include "libc/sysv/consts/sig.h"
#include "libc/time/time.h"
#include "libc/x/x.h"
+#include "libc/zip.h"
+#include "libc/zipos/zipos.h"
+#include "third_party/getopt/getopt.h"
#include "tool/viz/lib/knobs.h"
-#define DYN 240
-#define DXN 256
-#define FPS 60.0988
-#define HZ 1789773
-#define KEYHZ 20
-#define GAMMA 2.2
+#define USAGE \
+ " [ROM] [FMV]\n\
+\n\
+SYNOPSIS\n\
+\n\
+ Emulates NES Video Games in Terminal\n\
+\n\
+FLAGS\n\
+\n\
+ -A ansi color mode\n\
+ -t normal color mode\n\
+ -x xterm256 color mode\n\
+ -4 unicode character set\n\
+ -3 ibm cp437 character set\n\
+ -1 ntsc crt artifact emulation\n\
+ -h\n\
+ -? shows this information\n\
+\n\
+KEYBOARD\n\
+\n\
+ We support Emacs / Mac OS X control key bindings. We also support\n\
+ Vim. We support arrow keys. We also support WASD QWERTY & Dvorak.\n\
+ The 'A' button is mapped to SPACE. The 'B' button is mapped to b.\n\
+ Lastly TAB is SELECT and ENTER is START.\n\
+\n\
+ Teletypewriters are naturally limited in terms of keyboard input.\n\
+ They don't exactly have n-key rollover. More like 1-key rollover.\n\
+\n\
+ Try tapping rather than holding keys. You can tune the activation\n\
+ duration by pressing '8' and '9'. You can also adjust the keyboard\n\
+ repeat delay in your operating system settings to make it shorter.\n\
+\n\
+ Ideally we should send patches to all the terms that introduces a\n\
+ new ANSI escape sequences for key down / key up events. It'd also\n\
+ be great to have inband audio with terminals too.\n\
+\n\
+GRAPHICS\n\
+\n\
+ The '1' key toggles CRT monitor artifact emulation, which can make\n\
+ some games like Zelda II look better. The '3' and '4' keys toggle\n\
+ the selection of UNICODE block characters.\n\
+\n\
+ZIP\n\
+\n\
+ This executable is also a ZIP archive. If you change the extension\n\
+ then you can modify its inner structure, to place roms inside it.\n\
+\n\
+AUTHORS\n\
+\n\
+ Joel Yliluoma \n\
+ Justine Tunney \n\
+\n\
+\n"
+#define DYN 240
+#define DXN 256
+#define FPS 60.0988
+#define HZ 1789773
+#define GAMMA 2.2
#define CTRL(C) ((C) ^ 0100)
#define ALT(C) ((033 << 010) | (C))
-static const char* inputfn;
-
-typedef uint32_t u32;
-typedef uint16_t u16;
-typedef uint8_t u8;
typedef int8_t s8;
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
static const struct itimerval kNesFps = {
{0, 1. / FPS * 1e6},
@@ -75,9 +133,19 @@ struct Audio {
int16_t p[FRAMESIZE];
};
+struct Status {
+ int wait;
+ char text[80];
+};
+
+struct ZipGames {
+ size_t i, n;
+ char** p;
+};
+
static int frame_;
+static int drain_;
static int playfd_;
-static bool piped_;
static int devnull_;
static int playpid_;
static bool exited_;
@@ -87,22 +155,32 @@ static size_t vtsize_;
static bool artifacts_;
static long tyn_, txn_;
static const char* ffplay_;
-static struct Audio audio_;
-static struct TtyRgb* ttyrgb_;
static struct Frame vf_[2];
+static struct Audio audio_;
+static const char* inputfn_;
+static struct Status status_;
+static struct TtyRgb* ttyrgb_;
static unsigned char *R, *G, *B;
+static struct ZipGames zipgames_;
static struct Action arrow_, button_;
static struct SamplingSolution* asx_;
static struct SamplingSolution* ssy_;
static struct SamplingSolution* ssx_;
static unsigned char pixels_[3][DYN][DXN];
static unsigned char palette_[3][64][512][3];
-static int joy_current_[2] = {0, 0};
-static int joy_next_[2] = {0, 0};
-static int joypos_[2] = {0, 0};
+static int joy_current_[2], joy_next_[2], joypos_[2];
-static int Clamp(int v) { return MAX(0, MIN(255, v)); }
-static double FixGamma(double x) { return tv2pcgamma(x, GAMMA); }
+static int keyframes_ = 20;
+static enum TtyBlocksSelection blocks_ = kTtyBlocksUnicode;
+static enum TtyQuantizationAlgorithm quant_ = kTtyQuantTrue;
+
+static int Clamp(int v) {
+ return MAX(0, MIN(255, v));
+}
+
+static double FixGamma(double x) {
+ return tv2pcgamma(x, GAMMA);
+}
void InitPalette(void) {
// The input value is a NES color index (with de-emphasis bits).
@@ -110,7 +188,7 @@ void InitPalette(void) {
// We need RGB values. To produce a RGB value, we emulate the NTSC circuitry.
double A[3] = {-1.109, -.275, .947};
double B[3] = {1.709, -.636, .624};
- double rgbc[3], lightbulb[3][3], rgbd65[3];
+ double rgbc[3], lightbulb[3][3], rgbd65[3], sc[2];
int o, u, r, c, b, p, y, i, l, q, e, p0, p1, pixel;
signed char volt[] = "\372\273\32\305\35\311I\330D\357\175\13D!}N";
GetChromaticAdaptationMatrix(lightbulb, kIlluminantC, kIlluminantD65);
@@ -119,9 +197,7 @@ void InitPalette(void) {
for (p1 = 0; p1 < 64; ++p1) {
for (u = 0; u < 3; ++u) {
// Calculate the luma and chroma by emulating the relevant circuits:
- y = 0;
- i = 0;
- q = 0;
+ y = i = q = 0;
// 12 samples of NTSC signal constitute a color.
for (p = 0; p < 12; ++p) {
// Sample either the previous or the current pixel.
@@ -140,9 +216,10 @@ void InitPalette(void) {
b = 40 + volt[(c > 12 * ((c + 8 + p) % 12 < 6)) +
2 * !(0451326 >> p / 2 * 3 & e) + l];
// Ideal TV NTSC demodulator?
+ sincos(M_PI * p / 6, &sc[0], &sc[1]);
y += b;
- i += b * round(cos(M_PI * p / 6) * 5909);
- q += b * round(sin(M_PI * p / 6) * 5909);
+ i += b * sc[1] * 5909;
+ q += b * sc[0] * 5909;
}
// Converts YIQ to RGB
// Store color at subpixel precision
@@ -161,31 +238,62 @@ static void WriteStringNow(const char* s) {
ttywrite(STDOUT_FILENO, s, strlen(s));
}
-void CleanupTerminal(void) {
- ttyraw((enum TtyRawFlags)(-1u));
- ttyshowcursor(STDOUT_FILENO);
+void Exit(int rc) {
+ WriteStringNow("\r\n\e[0m\e[J");
+ if (rc && errno) {
+ fprintf(stderr, "%s%s\r\n", "error: ", strerror(errno));
+ }
+ exit(rc);
}
-void OnTimer(void) { timeout_ = true; }
-void OnResize(void) { resized_ = true; }
-void OnCtrlC(void) { exited_ = true; }
-void OnSigChld(void) { piped_ = true, playpid_ = 0; }
+void Cleanup(void) {
+ ttyraw((enum TtyRawFlags)(-1u));
+ ttyshowcursor(STDOUT_FILENO);
+ if (playpid_) kill(playpid_, SIGTERM), sched_yield();
+}
+
+void OnTimer(void) {
+ timeout_ = true; // also sends EINTR to poll()
+}
+
+void OnResize(void) {
+ resized_ = true;
+}
+
+void OnPiped(void) {
+ exited_ = true;
+}
+
+void OnCtrlC(void) {
+ drain_ = exited_ = true;
+}
+
+void OnSigChld(void) {
+ exited_ = true, playpid_ = 0;
+}
void InitFrame(struct Frame* f) {
f->p = f->w = f->mem = (char*)realloc(f->mem, vtsize_);
}
+long ChopAxis(long dn, long sn) {
+ while (HALF(sn) > dn) {
+ sn = HALF(sn);
+ }
+ return sn;
+}
+
void GetTermSize(void) {
struct winsize wsize_;
wsize_.ws_row = 25;
wsize_.ws_col = 80;
- getttysize(STDOUT_FILENO, &wsize_);
- tyn_ = wsize_.ws_row * 2;
- txn_ = wsize_.ws_col * 2;
+ getttysize(STDIN_FILENO, &wsize_);
FreeSamplingSolution(ssy_);
FreeSamplingSolution(ssx_);
- ssy_ = ComputeSamplingSolution(tyn_, DYN, 0, 0, 2);
- ssx_ = ComputeSamplingSolution(txn_, DXN, 0, 0, 0);
+ tyn_ = wsize_.ws_row * 2;
+ txn_ = wsize_.ws_col * 2;
+ ssy_ = ComputeSamplingSolution(tyn_, ChopAxis(tyn_, DYN), 0, 0, 2);
+ ssx_ = ComputeSamplingSolution(txn_, ChopAxis(txn_, DXN), 0, 0, 0);
R = (unsigned char*)realloc(R, tyn_ * txn_);
G = (unsigned char*)realloc(G, tyn_ * txn_);
B = (unsigned char*)realloc(B, tyn_ * txn_);
@@ -216,20 +324,23 @@ bool TrySpeaker(const char* prog, char* const* args) {
void IoInit(void) {
GetTermSize();
xsigaction(SIGINT, (void*)OnCtrlC, 0, 0, NULL);
+ xsigaction(SIGPIPE, (void*)OnPiped, 0, 0, NULL);
xsigaction(SIGWINCH, (void*)OnResize, 0, 0, NULL);
xsigaction(SIGALRM, (void*)OnTimer, 0, 0, NULL);
+ xsigaction(SIGCHLD, (void*)OnSigChld, 0, 0, NULL);
setitimer(ITIMER_REAL, &kNesFps, NULL);
ttyhidecursor(STDOUT_FILENO);
ttyraw(kTtySigs);
- ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode);
- atexit(CleanupTerminal);
+ ttyquantsetup(quant_, kTtyQuantRgb, blocks_);
+ atexit(Cleanup);
}
-void SystemFailure(void) {
- fputs("error: ", stderr);
- fputs(strerror(errno), stderr);
- fputc('\n', stderr);
- exit(7);
+void SetStatus(const char* fmt, ...) {
+ va_list va;
+ va_start(va, fmt);
+ vsnprintf(status_.text, sizeof(status_.text), fmt, va);
+ va_end(va);
+ status_.wait = FPS / 2;
}
void ReadKeyboard(void) {
@@ -238,6 +349,7 @@ void ReadKeyboard(void) {
ssize_t i, rc;
memset(b, -1, sizeof(b));
if ((rc = read(STDIN_FILENO, b, 16)) != -1) {
+ if (!rc) exited_ = true;
for (i = 0; i < rc; ++i) {
ch = b[i];
if (b[i] == '\e') {
@@ -263,71 +375,105 @@ void ReadKeyboard(void) {
}
}
switch (ch) {
- case '1':
- artifacts_ = !artifacts_;
- InitPalette();
- break;
case ' ':
button_.code = 0b00100000; // A
- button_.wait = KEYHZ;
+ button_.wait = keyframes_;
break;
case 'b':
button_.code = 0b00010000; // B
- button_.wait = KEYHZ;
+ button_.wait = keyframes_;
break;
case '\r': // enter
button_.code = 0b10000000; // START
- button_.wait = KEYHZ;
+ button_.wait = keyframes_;
break;
case '\t': // tab
button_.code = 0b01000000; // SELECT
- button_.wait = KEYHZ;
+ button_.wait = keyframes_;
break;
case 'k': // vim
case 'w': // wasd qwerty
case ',': // wasd dvorak
case CTRL('P'): // emacs
arrow_.code = 0b00000100; // UP
- arrow_.wait = KEYHZ;
+ arrow_.wait = keyframes_;
break;
case 'j': // vim
case 's': // wasd qwerty
case 'o': // wasd dvorak
case CTRL('N'): // emacs
arrow_.code = 0b00001000; // DOWN
- arrow_.wait = KEYHZ;
+ arrow_.wait = keyframes_;
break;
case 'h': // vim
case 'a': // wasd qwerty & dvorak
case CTRL('B'): // emacs
arrow_.code = 0b00000010; // LEFT
- arrow_.wait = KEYHZ;
+ arrow_.wait = keyframes_;
break;
case 'l': // vim
case 'd': // wasd qwerty
case 'e': // wasd dvorak
case CTRL('F'): // emacs
arrow_.code = 0b00000001; // RIGHT
- arrow_.wait = KEYHZ;
+ arrow_.wait = keyframes_;
+ break;
+ case 'A': // ansi 4-bit color mode
+ quant_ = kTtyQuantAnsi;
+ ttyquantsetup(quant_, kTtyQuantRgb, blocks_);
+ SetStatus("ansi color");
break;
case 'x': // xterm256 color mode
- ttyquantinit(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode);
+ quant_ = kTtyQuantXterm256;
+ ttyquantsetup(quant_, kTtyQuantRgb, blocks_);
+ SetStatus("xterm256 color");
break;
case 't': // ansi 24bit color mode
- ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode);
+ quant_ = kTtyQuantTrue;
+ ttyquantsetup(quant_, kTtyQuantRgb, blocks_);
+ SetStatus("24-bit color");
+ break;
+ case '1':
+ artifacts_ = !artifacts_;
+ InitPalette();
+ SetStatus("artifacts %s", artifacts_ ? "on" : "off");
+ break;
+ case '3': // oldskool ibm unicode rasterization
+ blocks_ = kTtyBlocksCp437;
+ ttyquantsetup(quant_, kTtyQuantRgb, blocks_);
+ SetStatus("IBM CP437");
+ break;
+ case '4': // newskool unicode rasterization
+ blocks_ = kTtyBlocksUnicode;
+ ttyquantsetup(quant_, kTtyQuantRgb, blocks_);
+ SetStatus("UNICODE");
+ break;
+ case '8':
+ keyframes_ = MAX(1, keyframes_ - 1);
+ SetStatus("%d key frames", keyframes_);
+ break;
+ case '9':
+ keyframes_ = keyframes_ + 1;
+ SetStatus("%d key frames", keyframes_);
break;
default:
break;
}
}
- } else {
- SystemFailure();
}
}
-bool HasVideo(struct Frame* f) { return f->w < f->p; }
-bool HasPendingVideo(void) { return HasVideo(&vf_[0]) || HasVideo(&vf_[1]); }
-bool HasPendingAudio(void) { return playpid_ && audio_.i; }
+bool HasVideo(struct Frame* f) {
+ return f->w < f->p;
+}
+
+bool HasPendingVideo(void) {
+ return HasVideo(&vf_[0]) || HasVideo(&vf_[1]);
+}
+
+bool HasPendingAudio(void) {
+ return playpid_ && audio_.i;
+}
struct Frame* FlipFrameBuffer(void) {
frame_ = !frame_;
@@ -341,8 +487,10 @@ void TransmitVideo(void) {
if (!HasVideo(f)) f = FlipFrameBuffer();
if ((rc = write(STDOUT_FILENO, f->w, f->p - f->w)) != -1) {
f->w += rc;
- } else {
- SystemFailure();
+ } else if (errno == EPIPE) {
+ Exit(0);
+ } else if (errno != EINTR) {
+ Exit(1);
}
}
@@ -353,19 +501,36 @@ void TransmitAudio(void) {
rc /= sizeof(short);
memmove(audio_.p, audio_.p + rc, (audio_.i - rc) * sizeof(short));
audio_.i -= rc;
- } else {
- SystemFailure();
+ } else if (errno == EPIPE) {
+ Exit(0);
+ } else if (errno != EINTR) {
+ Exit(1);
}
}
void ScaleVideoFrameToTeletypewriter(void) {
- long y, x;
- GyaradosUint8(tyn_, txn_, R, DYN, DXN, pixels_[0], tyn_, txn_, DYN, DXN, 0,
- 255, ssy_, ssx_, true);
- GyaradosUint8(tyn_, txn_, G, DYN, DXN, pixels_[1], tyn_, txn_, DYN, DXN, 0,
- 255, ssy_, ssx_, true);
- GyaradosUint8(tyn_, txn_, B, DYN, DXN, pixels_[2], tyn_, txn_, DYN, DXN, 0,
- 255, ssy_, ssx_, true);
+ long y, x, yn, xn;
+ yn = DYN, xn = DXN;
+ while (HALF(yn) > tyn_ || HALF(xn) > txn_) {
+ if (HALF(xn) > txn_) {
+ Magikarp2xX(DYN, DXN, pixels_[0], yn, xn);
+ Magikarp2xX(DYN, DXN, pixels_[1], yn, xn);
+ Magikarp2xX(DYN, DXN, pixels_[2], yn, xn);
+ xn = HALF(xn);
+ }
+ if (HALF(yn) > tyn_) {
+ Magikarp2xY(DYN, DXN, pixels_[0], yn, xn);
+ Magikarp2xY(DYN, DXN, pixels_[1], yn, xn);
+ Magikarp2xY(DYN, DXN, pixels_[2], yn, xn);
+ yn = HALF(yn);
+ }
+ }
+ GyaradosUint8(tyn_, txn_, R, DYN, DXN, pixels_[0], tyn_, txn_, yn, xn, 0, 255,
+ ssy_, ssx_, true);
+ GyaradosUint8(tyn_, txn_, G, DYN, DXN, pixels_[1], tyn_, txn_, yn, xn, 0, 255,
+ ssy_, ssx_, true);
+ GyaradosUint8(tyn_, txn_, B, DYN, DXN, pixels_[2], tyn_, txn_, yn, xn, 0, 255,
+ ssy_, ssx_, true);
for (y = 0; y < tyn_; ++y) {
for (x = 0; x < txn_; ++x) {
ttyrgb_[y * txn_ + x] =
@@ -382,15 +547,10 @@ void KeyCountdown(struct Action* a) {
}
}
-void DrainAndExit(void) {
- while (HasPendingVideo()) TransmitVideo();
- WriteStringNow("\r\n\e[0m\e[J");
- exit(0);
-}
-
void PollAndSynchronize(void) {
struct pollfd fds[3];
do {
+ errno = 0;
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
fds[1].fd = HasPendingVideo() ? STDOUT_FILENO : -1;
@@ -402,10 +562,15 @@ void PollAndSynchronize(void) {
if (fds[1].revents & (POLLOUT | POLLERR)) TransmitVideo();
if (fds[2].revents & (POLLOUT | POLLERR)) TransmitAudio();
} else if (errno != EINTR) {
- SystemFailure();
+ Exit(1);
}
if (exited_) {
- DrainAndExit();
+ if (drain_) {
+ while (HasPendingVideo()) {
+ TransmitVideo();
+ }
+ }
+ Exit(0);
}
if (resized_) {
resized_ = false;
@@ -429,6 +594,11 @@ void Raster(void) {
f->p = f->w = f->mem;
f->p = stpcpy(f->p, "\e[0m\e[H");
f->p = ttyraster(f->p, ttyrgb_, tyn_, txn_, bg, fg);
+ if (status_.wait) {
+ status_.wait--;
+ f->p = stpcpy(f->p, "\e[0m\e[H");
+ f->p = stpcpy(f->p, status_.text);
+ }
CHECK_LT(f->p - f->mem, vtsize_);
PollAndSynchronize();
}
@@ -443,13 +613,8 @@ void FlushScanline(unsigned py) {
}
static void PutPixel(unsigned px, unsigned py, unsigned pixel, int offset) {
- static bool once;
- static unsigned prev;
unsigned rgb;
- if (!once) {
- InitPalette();
- once = true;
- }
+ static unsigned prev;
pixels_[0][py][px] = palette_[offset][prev % 64][pixel][2];
pixels_[1][py][px] = palette_[offset][prev % 64][pixel][1];
pixels_[2][py][px] = palette_[offset][prev % 64][pixel][0];
@@ -491,8 +656,12 @@ struct RegBit {
data = (data & ~(mask << bitno)) | ((nbits > 1 ? v & mask : !!v) << bitno);
return *this;
}
- operator unsigned() const { return (data >> bitno) & mask; }
- RegBit& operator++() { return *this = *this + 1; }
+ operator unsigned() const {
+ return (data >> bitno) & mask;
+ }
+ RegBit& operator++() {
+ return *this = *this + 1;
+ }
unsigned operator++(int) {
unsigned r = *this;
++*this;
@@ -602,8 +771,12 @@ bool intr = false;
template
u8 MemAccess(u16 addr, u8 v = 0);
-u8 RB(u16 addr) { return MemAccess<0>(addr); }
-u8 WB(u16 addr, u8 v) { return MemAccess<1>(addr, v); }
+u8 RB(u16 addr) {
+ return MemAccess<0>(addr);
+}
+u8 WB(u16 addr, u8 v) {
+ return MemAccess<1>(addr, v);
+}
void Tick();
} // namespace CPU
@@ -922,8 +1095,8 @@ void RenderPixel() {
void ReadToolAssistedSpeedrunRobotKeys() {
static FILE* fp;
- if (!fp && !isempty(inputfn)) {
- fp = fopen(inputfn, "rb");
+ if (!fp && !isempty(inputfn_)) {
+ fp = fopen(inputfn_, "rb");
}
if (fp) {
static unsigned ctrlmask = 0;
@@ -1039,7 +1212,9 @@ bool ChannelsEnabled[5];
bool PeriodicIRQ;
bool DMC_IRQ;
-bool count(int& v, int reset) { return --v < 0 ? (v = reset), true : false; }
+bool count(int& v, int reset) {
+ return --v < 0 ? (v = reset), true : false;
+}
struct channel {
int length_counter, linear_counter, address, envelope;
@@ -1119,9 +1294,11 @@ struct channel {
// Note: Re-entrant! But not recursive, because even
// the shortest wave length is greater than the read time.
// TODO: proper clock
- if (ch.reg.WaveLength > 20)
- for (unsigned t = 0; t < 3; ++t)
- CPU::RB(u16(ch.address) | 0x8000); // timing
+ if (ch.reg.WaveLength > 20) {
+ for (unsigned t = 0; t < 3; ++t) {
+ CPU::RB(u16(ch.address) | 0x8000); // timing
+ }
+ }
ch.hold = CPU::RB(u16(ch.address++) | 0x8000); // Fetch byte
ch.phase = 8;
--ch.length_counter;
@@ -1357,13 +1534,19 @@ union { /* Status flags: */
RegBit<7> N; // negative
} P;
-u16 wrap(u16 oldaddr, u16 newaddr) { return (oldaddr & 0xFF00) + u8(newaddr); }
+u16 wrap(u16 oldaddr, u16 newaddr) {
+ return (oldaddr & 0xFF00) + u8(newaddr);
+}
void Misfire(u16 old, u16 addr) {
u16 q = wrap(old, addr);
if (q != addr) RB(q);
}
-u8 Pop() { return RB(0x100 | u8(++S)); }
-void Push(u8 v) { WB(0x100 | u8(S--), v); }
+u8 Pop() {
+ return RB(0x100 | u8(++S));
+}
+void Push(u8 v) {
+ WB(0x100 | u8(S--), v);
+}
template // Execute a single CPU instruction, defined by opcode "op".
void Ins() { // With template magic, the compiler will literally synthesize
@@ -1503,47 +1686,59 @@ void Op() {
} // namespace CPU
-int main(int argc, char** argv) {
+char* GetLine(void) {
+ static char* line;
+ static size_t linesize;
+ if (getline(&line, &linesize, stdin) > 0) {
+ return chomp(line);
+ } else {
+ return NULL;
+ }
+}
+
+int PlayGame(const char* romfile, const char* opt_tasfile) {
FILE* fp;
+ inputfn_ = opt_tasfile;
- if (argc <= 1 || (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-?") == 0 ||
- strcmp(argv[1], "--help") == 0)) {
- fprintf(stderr, "%s%s%s\n", "Usage: ", argv[0], " ROM [FMV]");
- exit(1);
+ if (!(fp = fopen(romfile, "rb"))) {
+ fprintf(stderr, "%s%s\n", "failed to open: ", romfile);
+ return 2;
}
-
- // Open the ROM file specified on commandline
- fp = fopen(argv[1], "rb"); /* your .nes file */
- inputfn = argc >= 3 ? argv[2] : NULL; /* some tas thing */
-
- if (!fp) {
- fprintf(stderr, "%s%s\n", "not a nes rom file: ", argv[1]);
- exit(2);
- }
-
if (!(fgetc(fp) == 'N' && fgetc(fp) == 'E' && fgetc(fp) == 'S' &&
fgetc(fp) == CTRL('Z'))) {
- fprintf(stderr, "%s%s\n", "not a nes rom file: ", argv[1]);
- exit(3);
+ fprintf(stderr, "%s%s\n", "not a nes rom file: ", romfile);
+ return 3;
}
+ InitPalette();
+
// open speaker
// todo: this needs plenty of work
devnull_ = open("/dev/null", O_WRONLY);
- ffplay_ = commandvenv("FFPLAY", "ffplay");
- if (devnull_ != -1 && ffplay_) {
+ if ((ffplay_ = commandvenv("FFPLAY", "ffplay"))) {
const char* args[] = {
"ffplay", "-nodisp", "-loglevel", "quiet", "-fflags", "nobuffer", "-ac",
"1", "-ar", "1789773", "-f", "s16le", "pipe:", NULL,
};
TrySpeaker(ffplay_, (char* const*)args);
+ } else {
+ fputs("\nWARNING\n\
+\n\
+ Need `ffplay` command to play audio\n\
+ Try `sudo apt install ffmpeg` on Linux\n\
+ You can specify it on `PATH` or in `FFPLAY`\n\
+\n\
+Press enter to continue without sound: ",
+ stdout);
+ fflush(stdout);
+ GetLine();
}
// Read the ROM file header
u8 rom16count = fgetc(fp);
u8 vrom8count = fgetc(fp);
u8 ctrlbyte = fgetc(fp);
- u8 mappernum = fgetc(fp) | (ctrlbyte >> 4);
+ u8 mappernum = fgetc(fp) | ctrlbyte >> 4;
fgetc(fp);
fgetc(fp);
@@ -1578,3 +1773,96 @@ int main(int argc, char** argv) {
// Run the CPU until the program is killed.
for (;;) CPU::Op();
}
+
+noreturn void PrintUsage(int rc, FILE* f) {
+ fprintf(f, "%s%s%s", "Usage: ", program_invocation_name, USAGE);
+ exit(rc);
+}
+
+void GetOpts(int argc, char* argv[]) {
+ int opt;
+ while ((opt = getopt(argc, argv, "?hAxt134")) != -1) {
+ switch (opt) {
+ case 'A':
+ quant_ = kTtyQuantAnsi;
+ break;
+ case 'x':
+ quant_ = kTtyQuantXterm256;
+ break;
+ case 't':
+ quant_ = kTtyQuantTrue;
+ break;
+ case '1':
+ artifacts_ = !artifacts_;
+ break;
+ case '3':
+ blocks_ = kTtyBlocksCp437;
+ break;
+ case '4':
+ blocks_ = kTtyBlocksUnicode;
+ break;
+ case 'h':
+ case '?':
+ PrintUsage(EXIT_SUCCESS, stdout);
+ default:
+ PrintUsage(EX_USAGE, stderr);
+ }
+ }
+}
+
+size_t FindZipGames(void) {
+ char* name;
+ struct Zipos* zipos;
+ size_t i, cf, namesize;
+ if ((zipos = __zipos_get())) {
+ for (i = 0, cf = ZIP_CDIR_OFFSET(zipos->cdir);
+ i < ZIP_CDIR_RECORDS(zipos->cdir);
+ ++i, cf += ZIP_CFILE_HDRSIZE(zipos->map + cf)) {
+ if ((name = strndup(ZIP_CFILE_NAME(zipos->map + cf),
+ ZIP_CFILE_NAMESIZE(zipos->map + cf))) &&
+ endswith(name, ".nes")) {
+ APPEND(&zipgames_.p, &zipgames_.i, &zipgames_.n, &name);
+ } else {
+ free(name);
+ }
+ }
+ }
+ return zipgames_.i;
+}
+
+int SelectGameFromZip(void) {
+ int i, rc;
+ char *line, *uri;
+ fputs("\nCOSMOPOLITAN NESEMU1\n\n", stdout);
+ for (i = 0; i < zipgames_.i; ++i) {
+ printf(" [%d] %s\n", i, zipgames_.p[i]);
+ }
+ fputs("\nPlease choose a game (or CTRL-C to quit) [default 0]: ", stdout);
+ fflush(stdout);
+ rc = 0;
+ if ((line = GetLine())) {
+ i = MAX(0, MIN(zipgames_.i - 1, atoi(line)));
+ uri = xasprintf("zip:%s", zipgames_.p[i]);
+ rc = PlayGame(uri, NULL);
+ free(uri);
+ } else {
+ fputs("\n", stdout);
+ }
+ return rc;
+}
+
+int main(int argc, char** argv) {
+ int rc;
+ GetOpts(argc, argv);
+ if (optind + 1 < argc) {
+ rc = PlayGame(argv[optind], argv[optind + 1]);
+ } else if (optind < argc) {
+ rc = PlayGame(argv[optind], NULL);
+ } else {
+ if (!FindZipGames()) {
+ PrintUsage(0, stderr);
+ }
+ rc = SelectGameFromZip();
+ }
+ return rc;
+}
diff --git a/examples/printargs.c b/examples/printargs.c
index c3648a015..0953aed35 100644
--- a/examples/printargs.c
+++ b/examples/printargs.c
@@ -50,19 +50,22 @@ const struct AuxiliaryValue {
};
int main(int argc, char *argv[], char **envp) {
+ long key;
+ unsigned i;
+ unsigned long val;
+ char fmt[64], **env;
printf("\nArguments:\n");
- for (unsigned i = 0; i < argc; ++i) {
+ for (i = 0; i < argc; ++i) {
printf(" ☼ %s\n", argv[i]);
}
printf("\nEnvironment:\n");
- for (char **env = envp; *env; ++env) {
+ for (env = envp; *env; ++env) {
printf(" ☼ %s\n", *env);
}
printf("\nAuxiliary Values:\n");
- for (unsigned i = 0; i < ARRAYLEN(kAuxiliaryValues); ++i) {
- long key = *kAuxiliaryValues[i].id;
- unsigned long val = getauxval(key);
- char fmt[64];
+ for (i = 0; i < ARRAYLEN(kAuxiliaryValues); ++i) {
+ key = *kAuxiliaryValues[i].id;
+ val = getauxval(key);
printf(PROGN(stpcpy(stpcpy(stpcpy(fmt, "%16s[%p] = "),
kAuxiliaryValues[i].fmt),
" # %s\n"),
diff --git a/examples/unbourne.c b/examples/unbourne.c
index fc61eefbc..03adad576 100644
--- a/examples/unbourne.c
+++ b/examples/unbourne.c
@@ -431,40 +431,40 @@
/*
* Evaluate a command.
*/
-#define ALIASCMD (builtincmd + 3)
-#define BGCMD (builtincmd + 4)
-#define BREAKCMD (builtincmd + 5)
-#define CDCMD (builtincmd + 6)
-#define COMMANDCMD (builtincmd + 8)
-#define DOTCMD (builtincmd + 0)
-#define ECHOCMD (builtincmd + 10)
-#define EVALCMD (builtincmd + 11)
-#define EXECCMD (builtincmd + 12)
-#define EXITCMD (builtincmd + 13)
-#define EXPORTCMD (builtincmd + 14)
-#define FALSECMD (builtincmd + 15)
-#define FGCMD (builtincmd + 16)
-#define GETOPTSCMD (builtincmd + 17)
-#define HASHCMD (builtincmd + 18)
-#define JOBSCMD (builtincmd + 19)
-#define KILLCMD (builtincmd + 20)
-#define LOCALCMD (builtincmd + 21)
-#define PRINTFCMD (builtincmd + 22)
-#define PWDCMD (builtincmd + 23)
-#define READCMD (builtincmd + 24)
-#define RETURNCMD (builtincmd + 26)
-#define SETCMD (builtincmd + 27)
-#define SHIFTCMD (builtincmd + 28)
-#define TESTCMD (builtincmd + 2)
-#define TIMESCMD (builtincmd + 30)
-#define TRAPCMD (builtincmd + 31)
-#define TRUECMD (builtincmd + 1)
-#define TYPECMD (builtincmd + 33)
-#define ULIMITCMD (builtincmd + 34)
-#define UMASKCMD (builtincmd + 35)
-#define UNALIASCMD (builtincmd + 36)
-#define UNSETCMD (builtincmd + 37)
-#define WAITCMD (builtincmd + 38)
+#define ALIASCMD (kBuiltinCmds + 3)
+#define BGCMD (kBuiltinCmds + 4)
+#define BREAKCMD (kBuiltinCmds + 5)
+#define CDCMD (kBuiltinCmds + 6)
+#define COMMANDCMD (kBuiltinCmds + 8)
+#define DOTCMD (kBuiltinCmds + 0)
+#define ECHOCMD (kBuiltinCmds + 10)
+#define EVALCMD (kBuiltinCmds + 11)
+#define EXECCMD (kBuiltinCmds + 12)
+#define EXITCMD (kBuiltinCmds + 13)
+#define EXPORTCMD (kBuiltinCmds + 14)
+#define FALSECMD (kBuiltinCmds + 15)
+#define FGCMD (kBuiltinCmds + 16)
+#define GETOPTSCMD (kBuiltinCmds + 17)
+#define HASHCMD (kBuiltinCmds + 18)
+#define JOBSCMD (kBuiltinCmds + 19)
+#define KILLCMD (kBuiltinCmds + 20)
+#define LOCALCMD (kBuiltinCmds + 21)
+#define PRINTFCMD (kBuiltinCmds + 22)
+#define PWDCMD (kBuiltinCmds + 23)
+#define READCMD (kBuiltinCmds + 24)
+#define RETURNCMD (kBuiltinCmds + 26)
+#define SETCMD (kBuiltinCmds + 27)
+#define SHIFTCMD (kBuiltinCmds + 28)
+#define TESTCMD (kBuiltinCmds + 2)
+#define TIMESCMD (kBuiltinCmds + 30)
+#define TRAPCMD (kBuiltinCmds + 31)
+#define TRUECMD (kBuiltinCmds + 1)
+#define TYPECMD (kBuiltinCmds + 33)
+#define ULIMITCMD (kBuiltinCmds + 34)
+#define UMASKCMD (kBuiltinCmds + 35)
+#define UNALIASCMD (kBuiltinCmds + 36)
+#define UNSETCMD (kBuiltinCmds + 37)
+#define WAITCMD (kBuiltinCmds + 38)
#define BUILTIN_SPECIAL 0x1
#define BUILTIN_REGULAR 0x2
@@ -620,12 +620,12 @@
#define octtobin(c) ((c) - '0')
#define scopy(s1, s2) ((void)strcpy(s2, s1))
-#define TRACE(param)
-/* #define TRACE(param) \ */
-/* do { \ */
-/* printf("TRACE: "); \ */
-/* printf param; \ */
-/* } while (0) */
+/* #define TRACE(param) */
+#define TRACE(param) \
+ do { \
+ printf("TRACE: "); \
+ printf param; \
+ } while (0)
#define TRACEV(param)
#define digit_val(c) ((c) - '0')
@@ -1184,13 +1184,16 @@ static const char dolatstr[] = {CTLQUOTEMARK, CTLVAR, VSNORMAL, '@', '=', CTLQUO
/* Some macros depend on the order, add new variables to the end. */
static void changepath(const char *);
static void getoptsreset(const char *);
-static struct Var varinit[] = {{0, VSTRFIXED | VTEXTFIXED, defifsvar, 0},
- {0, VSTRFIXED | VTEXTFIXED, defpathvar, changepath},
- {0, VSTRFIXED | VTEXTFIXED, "PS1=$ ", 0},
- {0, VSTRFIXED | VTEXTFIXED, "PS2=> ", 0},
- {0, VSTRFIXED | VTEXTFIXED, "PS4=+ ", 0},
- {0, VSTRFIXED | VTEXTFIXED, defoptindvar, getoptsreset},
- {0, VSTRFIXED | VTEXTFIXED, linenovar, 0}};
+
+static struct Var varinit[] = {
+ {0, VSTRFIXED | VTEXTFIXED, defifsvar, 0},
+ {0, VSTRFIXED | VTEXTFIXED, defpathvar, changepath},
+ {0, VSTRFIXED | VTEXTFIXED, "PS1=$ ", 0},
+ {0, VSTRFIXED | VTEXTFIXED, "PS2=> ", 0},
+ {0, VSTRFIXED | VTEXTFIXED, "PS4=+ ", 0},
+ {0, VSTRFIXED | VTEXTFIXED, defoptindvar, getoptsreset},
+ {0, VSTRFIXED | VTEXTFIXED, linenovar, 0},
+};
static const char kPrec[ARITH_BINOP_MAX - ARITH_BINOP_MIN] = {
#define ARITH_PRECEDENCE(OP, PREC) [OP - ARITH_BINOP_MIN] = PREC
@@ -1308,7 +1311,8 @@ static const char sqsyntax[] = {
CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
CCTL, CCTL, CCTL, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
- CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CCTL, CWORD};
+ CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CCTL, CWORD,
+};
/* syntax table used when in arithmetic */
static const char arisyntax[] = {
@@ -1331,7 +1335,8 @@ static const char arisyntax[] = {
CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
CWORD, CBACK, CWORD, CWORD, CWORD, CBQUOTE, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
- CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CENDVAR, CWORD, CWORD};
+ CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CENDVAR, CWORD, CWORD,
+};
/* character classification table */
static const char is_type[] /* clang-format off */ = {
@@ -1405,20 +1410,47 @@ static int unaliascmd();
static int unsetcmd();
static int waitcmd();
-static const struct builtincmd builtincmd[] = {
- {".", dotcmd, 3}, {":", truecmd, 3}, {"[", testcmd, 0},
- {"alias", aliascmd, 6}, {"bg", bgcmd, 2}, {"break", breakcmd, 3},
- {"cd", cdcmd, 2}, {"chdir", cdcmd, 0}, {"command", commandcmd, 2},
- {"continue", breakcmd, 3}, {"echo", echocmd, 0}, {"eval", NULL, 3},
- {"exec", execcmd, 3}, {"exit", exitcmd, 3}, {"export", exportcmd, 7},
- {"false", falsecmd, 2}, {"fg", fgcmd, 2}, {"getopts", getoptscmd, 2},
- {"hash", hashcmd, 2}, {"jobs", jobscmd, 2}, {"kill", killcmd, 2},
- {"local", localcmd, 7}, {"printf", printfcmd, 0}, {"pwd", pwdcmd, 2},
- {"read", readcmd, 2}, {"readonly", exportcmd, 7}, {"return", returncmd, 3},
- {"set", setcmd, 3}, {"shift", shiftcmd, 3}, {"test", testcmd, 0},
- {"times", timescmd, 3}, {"trap", trapcmd, 3}, {"true", truecmd, 2},
- {"type", typecmd, 2}, {"ulimit", ulimitcmd, 2}, {"umask", umaskcmd, 2},
- {"unalias", unaliascmd, 2}, {"unset", unsetcmd, 3}, {"wait", waitcmd, 2}};
+static const struct builtincmd kBuiltinCmds[] = {
+ {".", dotcmd, 3}, //
+ {":", truecmd, 3}, //
+ {"[", testcmd, 0}, //
+ {"alias", aliascmd, 6}, //
+ {"bg", bgcmd, 2}, //
+ {"break", breakcmd, 3}, //
+ {"cd", cdcmd, 2}, //
+ {"chdir", cdcmd, 0}, //
+ {"command", commandcmd, 2}, //
+ {"continue", breakcmd, 3}, //
+ {"echo", echocmd, 0}, //
+ {"eval", NULL, 3}, //
+ {"exec", execcmd, 3}, //
+ {"exit", exitcmd, 3}, //
+ {"export", exportcmd, 7}, //
+ {"false", falsecmd, 2}, //
+ {"fg", fgcmd, 2}, //
+ {"getopts", getoptscmd, 2}, //
+ {"hash", hashcmd, 2}, //
+ {"jobs", jobscmd, 2}, //
+ {"kill", killcmd, 2}, //
+ {"local", localcmd, 7}, //
+ {"printf", printfcmd, 0}, //
+ {"pwd", pwdcmd, 2}, //
+ {"read", readcmd, 2}, //
+ {"readonly", exportcmd, 7}, //
+ {"return", returncmd, 3}, //
+ {"set", setcmd, 3}, //
+ {"shift", shiftcmd, 3}, //
+ {"test", testcmd, 0}, //
+ {"times", timescmd, 3}, //
+ {"trap", trapcmd, 3}, //
+ {"true", truecmd, 2}, //
+ {"type", typecmd, 2}, //
+ {"ulimit", ulimitcmd, 2}, //
+ {"umask", umaskcmd, 2}, //
+ {"unalias", unaliascmd, 2}, //
+ {"unset", unsetcmd, 3}, //
+ {"wait", waitcmd, 2}, //
+};
enum token {
EOI,
@@ -2115,7 +2147,7 @@ static int olderf(const char *, const char *);
static int64_t openhere(union node *);
static int openredirect(union node *);
static int options(int);
-static int padvance_magic(const char **path, const char *name, int magic);
+static int padvance_magic(const char **, const char *, int);
static int patmatch(char *, const char *);
static int peektoken(void);
static int pgetc(void);
@@ -2882,7 +2914,7 @@ step6:
goto docd;
err:
sh_error("can't cd to %s", dest);
- /* NOTREACHED */
+ unreachable;
out:
if (flags & CD_PRINT) out1fmt(snlfmt, curdir);
return 0;
@@ -3446,7 +3478,7 @@ static void evalbackcmd(union node *n, struct backcmd *result) {
}
ifsfree();
evaltreenr(n, EV_EXIT);
- /* NOTREACHED */
+ unreachable;
}
close(pip[1]);
result->fd = pip[0];
@@ -3629,7 +3661,7 @@ static int evalcommand(union node *cmd, int flags) {
break;
}
shellexec(argv, path, cmdentry.u.index);
- /* NOTREACHED */
+ unreachable;
case CMDBUILTIN:
if (evalbltin(cmdentry.u.cmd, argc, argv, flags) &&
!(exception == EXERROR && spclbltin <= 0)) {
@@ -3742,9 +3774,11 @@ funcdone:
*/
static void prehash(union node *n) {
struct cmdentry entry;
- if (n->type == NCMD && n->ncmd.args)
- if (goodname(n->ncmd.args->narg.text))
+ if (n->type == NCMD && n->ncmd.args) {
+ if (goodname(n->ncmd.args->narg.text)) {
find_command(n->ncmd.args->narg.text, &entry, 0, pathval());
+ }
+ }
}
/*───────────────────────────────────────────────────────────────────────────│─╗
@@ -4014,10 +4048,8 @@ static void readcmdfile(char *name) {
* Search the table of builtin commands.
*/
static struct builtincmd *find_builtin(const char *name) {
- struct builtincmd *bp;
- bp = bsearch(&name, builtincmd, sizeof(builtincmd) / sizeof(builtincmd[0]), sizeof(builtincmd[0]),
- pstrcmp);
- return bp;
+ return bsearch(&name, kBuiltinCmds, sizeof(kBuiltinCmds) / sizeof(kBuiltinCmds[0]),
+ sizeof(kBuiltinCmds[0]), pstrcmp);
}
/*
@@ -4025,15 +4057,11 @@ static struct builtincmd *find_builtin(const char *name) {
* change the shellexec routine as well.
*/
static void find_command(char *name, struct cmdentry *entry, int act, const char *path) {
- struct tblentry *cmdp;
- int idx;
- int prev;
char *fullname;
struct stat statb;
- int e;
- int updatetbl;
+ struct tblentry *cmdp;
struct builtincmd *bcmd;
- int len;
+ int e, bit, idx, prev, updatetbl, len;
/* If name contains a slash, don't use PATH or hash table */
if (strchr(name, '/') != NULL) {
entry->u.index = -1;
@@ -4050,7 +4078,6 @@ static void find_command(char *name, struct cmdentry *entry, int act, const char
if (!updatetbl) act |= DO_ALTPATH;
/* If name is in the table, check answer will be ok */
if ((cmdp = cmdlookup(name, 0)) != NULL) {
- int bit;
switch (cmdp->cmdtype) {
default:
case CMDNORMAL:
@@ -4067,9 +4094,10 @@ static void find_command(char *name, struct cmdentry *entry, int act, const char
if (act & bit & DO_REGBLTIN) goto fail;
updatetbl = 0;
cmdp = NULL;
- } else if (cmdp->rehash == 0)
+ } else if (cmdp->rehash == 0) {
/* if not invalidated by cd, we're done */
goto success;
+ }
}
/* If %builtin not in path, check for builtin next */
bcmd = find_builtin(name);
@@ -4744,7 +4772,7 @@ static void expbackq(union node *cmd, int flag) {
recordregion(startloc, dest - (char *)stackblock(), 0);
}
TRACE(("evalbackq: size=%d: \"%.*s\"\n", (dest - (char *)stackblock()) - startloc,
- (dest - (char *)stackblock()) - startloc, stackblock() + startloc));
+ (dest - (char *)stackblock()) - startloc, (char *)stackblock() + startloc));
out:
argbackq = argbackq->next;
}
@@ -4822,7 +4850,7 @@ static char *subevalvar(char *start, char *str, int strloc, int startloc, int va
goto out;
case VSQUESTION:
varunset(start, str, startp, varflags);
- /* NOTREACHED */
+ unreachable;
}
subtype -= VSTRIMRIGHT;
rmesc = startp;
@@ -6450,7 +6478,7 @@ static void forkparent(struct job *jp, union node *n, int mode, int pid) {
TRACE(("Fork failed, errno=%d", errno));
if (jp) freejob(jp);
sh_error("Cannot fork");
- /* NOTREACHED */
+ unreachable;
}
TRACE(("In parent shell: child = %d\n", pid));
if (!jp) return;
@@ -6480,10 +6508,11 @@ static int forkshell(struct job *jp, union node *n, int mode) {
int pid;
TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode));
pid = fork();
- if (pid == 0)
+ if (pid == 0) {
forkchild(jp, n, mode);
- else
+ } else {
forkparent(jp, n, mode, pid);
+ }
return pid;
}
@@ -6498,7 +6527,7 @@ static struct job *vforkexec(union node *n, char **argv, const char *path, int i
forkchild(jp, n, FORK_FG);
sigclearmask();
shellexec(argv, path, idx);
- /* NOTREACHED */
+ unreachable;
}
vforked = 0;
sigclearmask();
@@ -7355,7 +7384,7 @@ static void setoption(int flag, int val) {
return;
}
sh_error("Illegal option -%c", flag);
- /* NOTREACHED */
+ unreachable;
}
/*
@@ -7799,7 +7828,7 @@ static union node *command(void) {
switch (readtoken()) {
default:
synexpect(-1);
- /* NOTREACHED */
+ unreachable;
case TIF:
n1 = (union node *)stalloc(sizeof(struct nif));
n1->type = NIF;
@@ -9998,10 +10027,10 @@ static void initvar(void) {
vp->next = *vpp;
*vpp = vp;
} while (++vp < end);
- /*
- * PS1 depends on uid
- */
- if (!geteuid()) vps1.text = "PS1=# ";
+ /* PS1 depends on uid */
+ if (!geteuid()) {
+ vps1.text = "PS1=# ";
+ }
}
/*
@@ -10749,7 +10778,7 @@ static int exitcmd(int argc, char **argv) {
exraise(EXEXIT);
}
-/*
+/**
* Main routine. We initialize things, parse the arguments, execute
* profiles if we're a login shell, and then call cmdloop to execute
* commands. The setjmp call sets up the location to jump to when an
diff --git a/examples/x86split.c b/examples/x86split.c
index 22122abd7..a5b423d52 100644
--- a/examples/x86split.c
+++ b/examples/x86split.c
@@ -39,7 +39,7 @@ int main(int argc, char *argv[argc]) {
errno = err;
break;
}
- l = xedd.decoded_length;
+ l = xedd.length;
if (l <= 0 || l > i) abort();
for (j = 0; j < l; ++j) {
if (fputhex(buf[j], stdout) == -1) {
diff --git a/libc/README.md b/libc/README.md
index 6fc8dd362..184c406e2 100644
--- a/libc/README.md
+++ b/libc/README.md
@@ -48,7 +48,3 @@ OVERVIEW
be compared to something more along the lines of, "the Russians
just used a pencil to write in space", versus spending millions
researching a pen like NASA.
-
- One cost worth noting, concerning stateless API translation, is
- file descriptors needed to change from `int` to `int64_t`, thus
- requiring a certain degree of added care when porting software.
diff --git a/libc/alg/alg.h b/libc/alg/alg.h
index 467eb3a62..207ce4032 100644
--- a/libc/alg/alg.h
+++ b/libc/alg/alg.h
@@ -18,9 +18,8 @@ void qsort(void *, size_t, size_t, int (*)(const void *, const void *))
void qsort_r(void *, size_t, size_t,
int cmp(const void *, const void *, void *), void *arg)
paramsnonnull((1, 4));
-int tarjan(uint32_t, const uint32_t (*)[2], uint32_t, uint32_t[], uint32_t[],
- uint32_t *) paramsnonnull((2, 4)) nocallback nothrow;
-void heapsortcar(int32_t (*)[2], unsigned) paramsnonnull() nocallback nothrow;
+int tarjan(int, const int (*)[2], int, int[], int[], int *)
+ paramsnonnull((2, 4)) nocallback nothrow;
void *memmem(const void *, size_t, const void *, size_t)
paramsnonnull() nothrow nocallback nosideeffect;
diff --git a/libc/alg/arraylist2.h b/libc/alg/arraylist2.h
index 92195f2a8..9ad045bc3 100644
--- a/libc/alg/arraylist2.h
+++ b/libc/alg/arraylist2.h
@@ -19,6 +19,11 @@ COSMOPOLITAN_C_START_
size_t SizE = sizeof(*Item); \
size_t Count = (COUNT); \
ssize_t Entry = -1; \
+ /* NOTE: We use `<` to guarantee one additional slot */ \
+ /* grow() will memset(0) extended memory, thus */ \
+ /* you get a nul-terminator for free sometimes */ \
+ /* the exception is if you list.i=0 and re-use */ \
+ /* so you need concat(...); list.p[list.i++]=0 */ \
if (*ListI + Count < *ListN || grow(ListP, ListN, SizE, Count)) { \
memcpy(&(*ListP)[*ListI], Item, (SizE) * (Count)); \
Entry = *ListI; \
diff --git a/libc/alg/qsort.c b/libc/alg/qsort.c
index 5d1dda34c..1083e6fc0 100644
--- a/libc/alg/qsort.c
+++ b/libc/alg/qsort.c
@@ -71,25 +71,25 @@ static void cycle(size_t width, unsigned char *ar[], size_t n) {
forceinline void shl(unsigned p[2], size_t n) {
assert(n > 0);
- if (n >= 8 * sizeof(unsigned)) {
- n -= 8 * sizeof(unsigned);
+ if (n >= CHAR_BIT * sizeof(unsigned)) {
+ n -= CHAR_BIT * sizeof(unsigned);
p[1] = p[0];
p[0] = 0;
}
p[1] <<= n;
- p[1] |= p[0] >> (sizeof(unsigned) * 8 - n);
+ p[1] |= p[0] >> (sizeof(unsigned) * CHAR_BIT - n);
p[0] <<= n;
}
forceinline void shr(unsigned p[2], size_t n) {
assert(n > 0);
- if (n >= 8 * sizeof(unsigned)) {
- n -= 8 * sizeof(unsigned);
+ if (n >= CHAR_BIT * sizeof(unsigned)) {
+ n -= CHAR_BIT * sizeof(unsigned);
p[0] = p[1];
p[1] = 0;
}
p[0] >>= n;
- p[0] |= p[1] << (sizeof(unsigned) * 8 - n);
+ p[0] |= p[1] << (sizeof(unsigned) * CHAR_BIT - n);
p[1] >>= n;
}
diff --git a/libc/alg/replacestr16.c b/libc/alg/replacestr16.c
index 7c5513bf1..751b36f22 100644
--- a/libc/alg/replacestr16.c
+++ b/libc/alg/replacestr16.c
@@ -23,7 +23,9 @@
#include "libc/str/str.h"
#include "libc/sysv/errfuns.h"
+#undef strlen
#undef replacestr
#define replacestr replacestr16
#define char char16_t
+#define strlen strlen16
#include "libc/alg/replacestr.c"
diff --git a/libc/alg/tarjan.c b/libc/alg/tarjan.c
index 2c9128db6..bdd51e2d2 100644
--- a/libc/alg/tarjan.c
+++ b/libc/alg/tarjan.c
@@ -19,8 +19,8 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/alg/alg.h"
#include "libc/assert.h"
-#include "libc/bits/safemacros.h"
#include "libc/limits.h"
+#include "libc/macros.h"
#include "libc/mem/mem.h"
/**
@@ -33,79 +33,77 @@
* topological sorting as a byproduct.” ──D.E. Knuth
*/
-struct Vertex {
- uint32_t Vi;
- uint32_t Ei;
- uint32_t index;
- uint32_t lowlink;
- bool onstack;
- bool selfreferential;
-};
-
-struct TarjanStack {
- size_t i;
- size_t n;
- uint32_t *p;
-};
-
struct Tarjan {
- uint32_t Vn;
- uint32_t En;
- struct Vertex *V;
- const uint32_t (*E)[2];
- uint32_t *R;
- uint32_t *C;
- uint32_t Ci;
- uint32_t Ri;
- uint32_t index;
- struct TarjanStack S;
+ int Vn, En, Ci, Ri, *R, *C, index;
+ const int (*E)[2];
+ struct Vertex {
+ int Vi;
+ int Ei;
+ int index;
+ int lowlink;
+ bool onstack;
+ bool selfreferential;
+ } * V;
+ struct TarjanStack {
+ int i;
+ int n;
+ int *p;
+ } S;
};
-static uint32_t TarjanPush(struct TarjanStack *st, uint32_t Vi) {
- if (st->i < st->n || grow(&st->p, &st->n, sizeof(uint32_t), 0)) {
- return (st->p[st->i++] = Vi);
- } else {
- return -1;
+static bool TarjanPush(struct Tarjan *t, int v) {
+ int *q;
+ assert(t->S.i >= 0);
+ assert(t->S.n >= 0);
+ assert(0 <= v && v < t->Vn);
+ if (t->S.i == t->S.n) {
+ if ((q = realloc(t->S.p, (t->S.n + (t->S.n >> 1) + 8) * sizeof(*t->S.p)))) {
+ t->S.p = q;
+ } else {
+ return false;
+ }
}
+ t->S.p[t->S.i++] = v;
+ return true;
}
-static uint32_t TarjanPop(struct TarjanStack *st) {
- assert(st->i != 0);
- return st->p[--st->i];
+static int TarjanPop(struct Tarjan *t) {
+ assert(t->S.i > 0);
+ return t->S.p[--t->S.i];
}
-static int TarjanConnect(struct Tarjan **tj, uint32_t Vi) {
- struct Vertex *v = &(*tj)->V[Vi];
- v->index = (*tj)->index;
- v->lowlink = (*tj)->index;
- v->onstack = true;
- (*tj)->index++;
- if (TarjanPush(&(*tj)->S, Vi) == -1) return -1;
- uint32_t fs = (*tj)->V[Vi].Ei;
+static bool TarjanConnect(struct Tarjan *t, int v) {
+ int fs, w, e;
+ assert(0 <= v && v < t->Vn);
+ t->V[v].index = t->index;
+ t->V[v].lowlink = t->index;
+ t->V[v].onstack = true;
+ t->index++;
+ if (!TarjanPush(t, v)) return false;
+ fs = t->V[v].Ei;
if (fs != -1) {
- for (uint32_t Ei = fs; Ei < (*tj)->En && Vi == (*tj)->E[Ei][0]; ++Ei) {
- struct Vertex *w = &(*tj)->V[(*tj)->E[Ei][1]];
- if (!w->index) {
- if (TarjanConnect(tj, w->Vi) == -1) return -1;
- v->lowlink = min(v->lowlink, w->lowlink);
- } else if (w->onstack) {
- v->lowlink = min(v->lowlink, w->index);
+ for (e = fs; e < t->En && v == t->E[e][0]; ++e) {
+ w = t->E[e][1];
+ if (!t->V[w].index) {
+ if (!TarjanConnect(t, t->V[w].Vi)) return false;
+ t->V[v].lowlink = MIN(t->V[v].lowlink, t->V[w].lowlink);
+ } else if (t->V[w].onstack) {
+ t->V[v].lowlink = MIN(t->V[v].lowlink, t->V[w].index);
}
if (w == v) {
- w->selfreferential = true;
+ t->V[w].selfreferential = true;
}
}
}
- if (v->lowlink == v->index) {
- struct Vertex *w;
+ if (t->V[v].lowlink == t->V[v].index) {
do {
- w = &(*tj)->V[TarjanPop(&(*tj)->S)];
- w->onstack = false;
- (*tj)->R[(*tj)->Ri++] = w->Vi;
+ w = TarjanPop(t);
+ t->V[w].onstack = false;
+ t->R[t->Ri++] = t->V[w].Vi;
} while (w != v);
- if ((*tj)->C) (*tj)->C[(*tj)->Ci++] = (*tj)->Ri;
+ if (t->C) t->C[t->Ci++] = t->Ri;
}
- return 0;
+ return true;
}
/**
@@ -133,51 +131,53 @@ static int TarjanConnect(struct Tarjan **tj, uint32_t Vi) {
* @error ENOMEM
* @note Tarjan's Algorithm is O(|V|+|E|)
*/
-int tarjan(uint32_t vertex_count, const uint32_t (*edges)[2],
- uint32_t edge_count, uint32_t out_sorted[],
- uint32_t out_opt_components[], uint32_t *out_opt_componentcount) {
- assert(edge_count <= INT_MAX);
- assert(vertex_count <= INT_MAX);
- for (unsigned i = 0; i < edge_count; ++i) {
+int tarjan(int vertex_count, const int (*edges)[2], int edge_count,
+ int out_sorted[], int out_opt_components[],
+ int *out_opt_componentcount) {
+ int i, rc, v, e;
+ struct Tarjan *t;
+ assert(0 <= edge_count && edge_count <= INT_MAX);
+ assert(0 <= vertex_count && vertex_count <= INT_MAX);
+ for (i = 0; i < edge_count; ++i) {
if (i) assert(edges[i - 1][0] <= edges[i][0]);
assert(edges[i][0] < vertex_count);
assert(edges[i][1] < vertex_count);
}
- int rc;
- struct Tarjan *tj;
- if ((tj = calloc(1, (sizeof(struct Tarjan) +
+ if (!(t = calloc(1, (sizeof(struct Tarjan) +
sizeof(struct Vertex) * vertex_count)))) {
- tj->V = (struct Vertex *)((char *)tj + sizeof(struct Tarjan));
- tj->Vn = vertex_count;
- tj->E = edges;
- tj->En = edge_count;
- tj->R = out_sorted;
- tj->C = out_opt_components;
- tj->index = 1;
- uint32_t Vi, Ei;
- for (Vi = 0; Vi < tj->Vn; ++Vi) {
- tj->V[Vi].Vi = Vi;
- tj->V[Vi].Ei = -1u;
- }
- for (Ei = 0, Vi = -1u; Ei < tj->En; ++Ei) {
- if (tj->E[Ei][0] == Vi) continue;
- Vi = tj->E[Ei][0];
- tj->V[Vi].Ei = Ei;
- }
- rc = 0;
- for (Vi = 0; Vi < tj->Vn; ++Vi) {
- if (!tj->V[Vi].index) {
- if ((rc = TarjanConnect(&tj, Vi)) == -1) {
- break;
- }
+ return -1;
+ }
+ t->V = (struct Vertex *)((char *)t + sizeof(struct Tarjan));
+ t->Vn = vertex_count;
+ t->E = edges;
+ t->En = edge_count;
+ t->R = out_sorted;
+ t->C = out_opt_components;
+ t->index = 1;
+ for (v = 0; v < t->Vn; ++v) {
+ t->V[v].Vi = v;
+ t->V[v].Ei = -1;
+ }
+ for (e = 0, v = -1; e < t->En; ++e) {
+ if (t->E[e][0] == v) continue;
+ v = t->E[e][0];
+ t->V[v].Ei = e;
+ }
+ rc = 0;
+ for (v = 0; v < t->Vn; ++v) {
+ if (!t->V[v].index) {
+ if (!TarjanConnect(t, v)) {
+ free(t->S.p);
+ free(t);
+ return -1;
}
}
- free(tj->S.p);
- assert(tj->Ri == vertex_count);
- if (out_opt_components) *out_opt_componentcount = tj->Ci;
- } else {
- rc = -1;
}
- free(tj);
+ if (out_opt_components) {
+ *out_opt_componentcount = t->Ci;
+ }
+ assert(t->Ri == vertex_count);
+ free(t->S.p);
+ free(t);
return rc;
}
diff --git a/libc/bits/bitreverse32.c b/libc/bits/bitreverse32.c
index 05b1804da..9ccc86d1d 100644
--- a/libc/bits/bitreverse32.c
+++ b/libc/bits/bitreverse32.c
@@ -18,6 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/bits.h"
+#include "libc/bits/bswap.h"
uint32_t(bitreverse32)(uint32_t x) {
x = bswap_32(x);
diff --git a/libc/bits/bitreverse64.c b/libc/bits/bitreverse64.c
index b38647032..8e9310eb7 100644
--- a/libc/bits/bitreverse64.c
+++ b/libc/bits/bitreverse64.c
@@ -18,6 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/bits.h"
+#include "libc/bits/bswap.h"
uint64_t bitreverse64(uint64_t x) {
x = bswap_64(x);
diff --git a/libc/bits/bits.h b/libc/bits/bits.h
index 4fc78f3f8..e26f53c84 100644
--- a/libc/bits/bits.h
+++ b/libc/bits/bits.h
@@ -13,9 +13,6 @@ extern const bool kTrue;
extern const bool kFalse;
extern const uint8_t kReverseBits[256];
-uint16_t bswap_16(uint16_t) pureconst;
-uint32_t bswap_32(uint32_t) pureconst;
-uint32_t bswap_64(uint32_t) pureconst;
uint32_t gray(uint32_t) pureconst;
uint32_t ungray(uint32_t) pureconst;
unsigned bcdadd(unsigned, unsigned) pureconst;
@@ -157,16 +154,6 @@ unsigned long hamming(unsigned long, unsigned long) pureconst;
#define ABOVEFLAG_ASM(OP) OP "\n\tseta\t%b0"
#endif
-/**
- * Reads scalar from memory, offset by segment.
- *
- * @return *(MEM) relative to segment
- * @see arch_prctl()
- * @see pushpop()
- */
-#define fs(MEM) __peek("fs", MEM)
-#define gs(MEM) __peek("gs", MEM)
-
/**
* Reads scalar from memory w/ one operation.
*
@@ -374,115 +361,16 @@ unsigned long hamming(unsigned long, unsigned long) pureconst;
#define invlpg(MEM) \
asm volatile("invlpg\t(%0)" : /* no outputs */ : "r"(MEM) : "memory")
-/**
- * Teleports code fragment inside _init().
- */
-#define INITIALIZER(PRI, NAME, CODE) \
- asm(".pushsection .init." #PRI "." #NAME ",\"ax\",@progbits\n\t" \
- "call\t" #NAME "\n\t" \
- ".popsection"); \
- textstartup optimizesize void NAME(char *rdi, const char *rsi) { \
- CODE; \
- asm volatile("" : /* no outputs */ : "D"(rdi), "S"(rsi)); \
- }
-
-#ifndef __STRICT_ANSI__
-#if __PIC__ + __code_model_medium__ + __code_model_large__ + 0 > 1
-#define __EZLEA(SYMBOL) "lea\t" SYMBOL "(%%rip),%"
-#else
-#define __EZLEA(SYMBOL) "mov\t$" SYMBOL ",%k"
-#endif
-#define weaken(symbol) ((const typeof(&(symbol)))weakaddr(#symbol))
-#define strongaddr(symbolstr) \
- ({ \
- intptr_t waddr; \
- asm(__EZLEA(symbolstr) "0" : "=r"(waddr)); \
- waddr; \
- })
-#define weakaddr(symbolstr) \
- ({ \
- intptr_t waddr; \
- asm(".weak\t" symbolstr "\n\t" __EZLEA(symbolstr) "0" : "=r"(waddr)); \
- waddr; \
- })
-#else
-#define weaken(symbol) symbol
-#define weakaddr(symbolstr) &(symbolstr)
-#endif
-
-#define slowcall(fn, arg1, arg2, arg3, arg4, arg5, arg6) \
- ({ \
- void *ax; \
- asm volatile("push\t%7\n\t" \
- "push\t%6\n\t" \
- "push\t%5\n\t" \
- "push\t%4\n\t" \
- "push\t%3\n\t" \
- "push\t%2\n\t" \
- "push\t%1\n\t" \
- "call\tslowcall" \
- : "=a"(ax) \
- : "g"(fn), "g"(arg1), "g"(arg2), "g"(arg3), "g"(arg4), \
- "g"(arg5), "g"(arg6) \
- : "memory"); \
- ax; \
- })
-
#define IsAddressCanonicalForm(P) \
({ \
intptr_t p2 = (intptr_t)(P); \
(0xffff800000000000l <= p2 && p2 <= 0x00007fffffffffffl); \
})
-/*───────────────────────────────────────────────────────────────────────────│─╗
-│ cosmopolitan § bits » optimizations ─╬─│┼
-╚────────────────────────────────────────────────────────────────────────────│*/
-#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
-
-#define bswap_16(U16) \
- (isconstant(U16) ? ((((U16)&0xff00) >> 010) | (((U16)&0x00ff) << 010)) : ({ \
- uint16_t Swapped16, Werd16 = (U16); \
- asm("xchg\t%b0,%h0" : "=Q"(Swapped16) : "0"(Werd16)); \
- Swapped16; \
- }))
-
-#define bswap_32(U32) \
- (isconstant(U32) \
- ? ((((U32)&0xff000000) >> 030) | (((U32)&0x000000ff) << 030) | \
- (((U32)&0x00ff0000) >> 010) | (((U32)&0x0000ff00) << 010)) \
- : ({ \
- uint32_t Swapped32, Werd32 = (U32); \
- asm("bswap\t%0" : "=r"(Swapped32) : "0"(Werd32)); \
- Swapped32; \
- }))
-
-#define bswap_64(U64) \
- (isconstant(U64) ? ((((U64)&0xff00000000000000ul) >> 070) | \
- (((U64)&0x00000000000000fful) << 070) | \
- (((U64)&0x00ff000000000000ul) >> 050) | \
- (((U64)&0x000000000000ff00ul) << 050) | \
- (((U64)&0x0000ff0000000000ul) >> 030) | \
- (((U64)&0x0000000000ff0000ul) << 030) | \
- (((U64)&0x000000ff00000000ul) >> 010) | \
- (((U64)&0x00000000ff000000ul) << 010)) \
- : ({ \
- uint64_t Swapped64, Werd64 = (U64); \
- asm("bswap\t%0" : "=r"(Swapped64) : "0"(Werd64)); \
- Swapped64; \
- }))
-
-#endif /* defined(__GNUC__) && !defined(__STRICT_ANSI__) */
/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § bits » implementation details ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│*/
-#define __peek(SEGMENT, ADDRESS) \
- ({ \
- typeof(*(ADDRESS)) Pk; \
- asm("mov\t%%" SEGMENT ":%1,%0" : "=r"(Pk) : "m"(*(ADDRESS))); \
- Pk; \
- })
-
#define __ArithmeticOp1(OP, MEM) \
({ \
asm(OP "%z0\t%0" : "+m"(*(MEM)) : /* no inputs */ : "cc"); \
diff --git a/libc/bits/bswap.h b/libc/bits/bswap.h
new file mode 100644
index 000000000..6bde2bc99
--- /dev/null
+++ b/libc/bits/bswap.h
@@ -0,0 +1,48 @@
+#ifndef COSMOPOLITAN_LIBC_BITS_BSWAP_H_
+#define COSMOPOLITAN_LIBC_BITS_BSWAP_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+uint16_t bswap_16(uint16_t) pureconst;
+uint32_t bswap_32(uint32_t) pureconst;
+uint32_t bswap_64(uint32_t) pureconst;
+
+#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
+
+#define bswap_16(U16) \
+ (isconstant(U16) ? ((((U16)&0xff00) >> 010) | (((U16)&0x00ff) << 010)) : ({ \
+ uint16_t Swapped16, Werd16 = (U16); \
+ asm("xchg\t%b0,%h0" : "=Q"(Swapped16) : "0"(Werd16)); \
+ Swapped16; \
+ }))
+
+#define bswap_32(U32) \
+ (isconstant(U32) \
+ ? ((((U32)&0xff000000) >> 030) | (((U32)&0x000000ff) << 030) | \
+ (((U32)&0x00ff0000) >> 010) | (((U32)&0x0000ff00) << 010)) \
+ : ({ \
+ uint32_t Swapped32, Werd32 = (U32); \
+ asm("bswap\t%0" : "=r"(Swapped32) : "0"(Werd32)); \
+ Swapped32; \
+ }))
+
+#define bswap_64(U64) \
+ (isconstant(U64) ? ((((U64)&0xff00000000000000ul) >> 070) | \
+ (((U64)&0x00000000000000fful) << 070) | \
+ (((U64)&0x00ff000000000000ul) >> 050) | \
+ (((U64)&0x000000000000ff00ul) << 050) | \
+ (((U64)&0x0000ff0000000000ul) >> 030) | \
+ (((U64)&0x0000000000ff0000ul) << 030) | \
+ (((U64)&0x000000ff00000000ul) >> 010) | \
+ (((U64)&0x00000000ff000000ul) << 010)) \
+ : ({ \
+ uint64_t Swapped64, Werd64 = (U64); \
+ asm("bswap\t%0" : "=r"(Swapped64) : "0"(Werd64)); \
+ Swapped64; \
+ }))
+
+#endif /* defined(__GNUC__) && !defined(__STRICT_ANSI__) */
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_BITS_BSWAP_H_ */
diff --git a/libc/bits/ezlea.h b/libc/bits/ezlea.h
new file mode 100644
index 000000000..08507b47c
--- /dev/null
+++ b/libc/bits/ezlea.h
@@ -0,0 +1,12 @@
+#ifndef COSMOPOLITAN_LIBC_BITS_EZLEA_H_
+#define COSMOPOLITAN_LIBC_BITS_EZLEA_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+
+#if __pic__ + __pie__ + __code_model_medium__ + __code_model_large__ + 0 > 1
+#define ezlea(symbol) "lea\t" symbol "(%%rip),%"
+#else
+#define ezlea(symbol) "mov\t$" symbol ",%k"
+#endif
+
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_BITS_EZLEA_H_ */
diff --git a/libc/bits/initializer.h b/libc/bits/initializer.h
new file mode 100644
index 000000000..901f71489
--- /dev/null
+++ b/libc/bits/initializer.h
@@ -0,0 +1,18 @@
+#ifndef COSMOPOLITAN_LIBC_BITS_INITIALIZER_H_
+#define COSMOPOLITAN_LIBC_BITS_INITIALIZER_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+
+/**
+ * Teleports code fragment inside _init().
+ */
+#define INITIALIZER(PRI, NAME, CODE) \
+ asm(".pushsection .init." #PRI "." #NAME ",\"ax\",@progbits\n\t" \
+ "call\t" #NAME "\n\t" \
+ ".popsection"); \
+ textstartup optimizesize void NAME(char *rdi, const char *rsi) { \
+ CODE; \
+ asm volatile("" : /* no outputs */ : "D"(rdi), "S"(rsi)); \
+ }
+
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_BITS_INITIALIZER_H_ */
diff --git a/libc/bits/popcnt.c b/libc/bits/popcnt.c
index 532ada5c6..3e4a85247 100644
--- a/libc/bits/popcnt.c
+++ b/libc/bits/popcnt.c
@@ -19,18 +19,13 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/popcnt.h"
-static uint32_t popcnt32(uint32_t x) {
- x -= (x >> 1) & 0x55555555;
- x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
- return (((x + (x >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
-}
-
-unsigned long(popcnt)(unsigned long x) {
- unsigned long r;
- r = 0;
- while (x) {
- r += popcnt32(x);
- x >>= 32;
- }
- return r;
+uint64_t(popcnt)(uint64_t x) {
+ uint32_t r;
+ x = x - ((x >> 1) & 0x5555555555555555);
+ x = ((x >> 2) & 0x3333333333333333) + (x & 0x3333333333333333);
+ x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0f;
+ x = (x + (x >> 32)) & 0xffffffff;
+ x = x + (x >> 16);
+ x = (x + (x >> 8)) & 0x0000007f;
+ return x;
}
diff --git a/libc/bits/popcnt.h b/libc/bits/popcnt.h
index 0af8af808..e3f95540e 100644
--- a/libc/bits/popcnt.h
+++ b/libc/bits/popcnt.h
@@ -8,7 +8,7 @@ unsigned long popcnt(unsigned long) pureconst;
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
#define popcnt(X) \
- (isconstant(X) ? __builtin_popcount(X) : ({ \
+ (isconstant(X) ? __builtin_popcountll(X) : ({ \
unsigned long Res, Pop = (X); \
if (X86_HAVE(POPCNT)) { \
asm("popcnt\t%1,%0" : "=r"(Res) : "r"(Pop) : "cc"); \
diff --git a/libc/bits/pushpop.h b/libc/bits/pushpop.h
index caf26b4ee..16656c7db 100644
--- a/libc/bits/pushpop.h
+++ b/libc/bits/pushpop.h
@@ -31,7 +31,7 @@
#endif
#if !defined(__GNUC__) || defined(__STRICT_ANSI__)
-#define pushmov(d, x) ((d) = (x))
+#define pushmov(d, x) (*(d) = (x))
#else
#define pushmov(d, x) \
({ \
diff --git a/libc/bits/safemacros.h b/libc/bits/safemacros.h
index df8c5783d..00e638b1a 100644
--- a/libc/bits/safemacros.h
+++ b/libc/bits/safemacros.h
@@ -1,20 +1,21 @@
#ifndef COSMOPOLITAN_LIBC_BITS_SAFEMACROS_H_
#define COSMOPOLITAN_LIBC_BITS_SAFEMACROS_H_
+#ifndef __STRICT_ANSI__
#include "libc/limits.h"
#include "libc/macros.h"
#include "libc/runtime/runtime.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
-long min(long x, long y);
-long max(long x, long y);
-long roundup(long w, long k);
-long rounddown(long w, long k);
-bool isempty(const char *s);
-const char *nulltoempty(const char *s);
-const char *emptytonull(const char *s);
-const char *firstnonnull(const char *a, const char *b);
-uint64_t(unsignedsubtract)(uint64_t x, uint64_t y) pureconst;
+long min(long, long);
+long max(long, long);
+long roundup(long, long);
+long rounddown(long, long);
+bool isempty(const char *);
+const char *nulltoempty(const char *);
+const char *emptytonull(const char *);
+const char *firstnonnull(const char *, const char *);
+uint64_t(unsignedsubtract)(uint64_t, uint64_t) pureconst;
#if !defined(__STRICT_ANSI__) && defined(__GNUC__)
@@ -83,4 +84,5 @@ uint64_t(unsignedsubtract)(uint64_t x, uint64_t y) pureconst;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_BITS_SAFEMACROS_H_ */
diff --git a/libc/bits/segmentation.h b/libc/bits/segmentation.h
new file mode 100644
index 000000000..ad36da681
--- /dev/null
+++ b/libc/bits/segmentation.h
@@ -0,0 +1,23 @@
+#ifndef COSMOPOLITAN_LIBC_BITS_SEGMENTATION_H_
+#define COSMOPOLITAN_LIBC_BITS_SEGMENTATION_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+
+/**
+ * Reads scalar from memory, offset by segment.
+ *
+ * @return *(MEM) relative to segment
+ * @see arch_prctl()
+ * @see pushpop()
+ */
+#define fs(MEM) __peek("fs", MEM)
+#define gs(MEM) __peek("gs", MEM)
+
+#define __peek(SEGMENT, ADDRESS) \
+ ({ \
+ typeof(*(ADDRESS)) Pk; \
+ asm("mov\t%%" SEGMENT ":%1,%0" : "=r"(Pk) : "m"(*(ADDRESS))); \
+ Pk; \
+ })
+
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_BITS_SEGMENTATION_H_ */
diff --git a/libc/bits/weaken.h b/libc/bits/weaken.h
new file mode 100644
index 000000000..c190d44f6
--- /dev/null
+++ b/libc/bits/weaken.h
@@ -0,0 +1,28 @@
+#ifndef COSMOPOLITAN_LIBC_BITS_WEAKEN_H_
+#define COSMOPOLITAN_LIBC_BITS_WEAKEN_H_
+#include "libc/bits/ezlea.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+#ifndef __STRICT_ANSI__
+
+#define weaken(symbol) ((const typeof(&(symbol)))weakaddr(#symbol))
+
+#define strongaddr(symbolstr) \
+ ({ \
+ intptr_t waddr; \
+ asm(ezlea(symbolstr) "0" : "=r"(waddr)); \
+ waddr; \
+ })
+
+#define weakaddr(symbolstr) \
+ ({ \
+ intptr_t waddr; \
+ asm(".weak\t" symbolstr "\n\t" ezlea(symbolstr) "0" : "=r"(waddr)); \
+ waddr; \
+ })
+
+#else
+#define weaken(symbol) symbol
+#define weakaddr(symbolstr) &(symbolstr)
+#endif /* __STRICT_ANSI__ */
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_BITS_WEAKEN_H_ */
diff --git a/libc/calls/calls.h b/libc/calls/calls.h
index 626f323df..638af166c 100644
--- a/libc/calls/calls.h
+++ b/libc/calls/calls.h
@@ -81,20 +81,19 @@ bool isregularfile(const char *);
bool32 isatty(int) nosideeffect;
bool32 ischardev(int) nosideeffect;
char *get_current_dir_name(void) nodiscard;
-char *getcwd(char *, size_t) paramsnonnull();
+char *getcwd(char *, size_t);
char *realpath(const char *, char *);
char *replaceuser(const char *) nodiscard;
char *slurp(const char *, size_t *) nodiscard;
char *ttyname(int);
const char *commandv(const char *);
-int access(const char *, int) nothrow paramsnonnull();
+int access(const char *, int) nothrow;
int arch_prctl();
-int chdir(const char *) paramsnonnull();
+int chdir(const char *);
int chmod(const char *, uint32_t);
-int chown(const char *, uint32_t, uint32_t) paramsnonnull();
+int chown(const char *, uint32_t, uint32_t);
int close(int);
int closedir(DIR *);
-int copyfile(const char *, const char *, bool);
int creat(const char *, uint32_t) nodiscard;
int dirfd(DIR *);
int dup(int) nodiscard;
@@ -119,7 +118,7 @@ int fdatasync(int);
int filecmp(const char *, const char *);
int flock(int, int);
int fork(void);
-int fstat(int, struct stat *) paramsnonnull();
+int fstat(int, struct stat *);
int fstatat(int, const char *, struct stat *, uint32_t);
int fsync(int);
int ftruncate(int, int64_t);
@@ -131,7 +130,7 @@ int kill(int, int);
int killpg(int, int);
int link(const char *, const char *) nothrow;
int linkat(int, const char *, int, const char *, uint32_t);
-int lstat(const char *, struct stat *) paramsnonnull();
+int lstat(const char *, struct stat *);
int madvise(void *, uint64_t, int);
int mkdir(const char *, uint32_t);
int mkdirat(int, const char *, uint32_t);
@@ -141,7 +140,7 @@ int mknodat(int, const char *, int32_t, uint64_t);
int mlock(const void *, size_t);
int mlock2(const void *, size_t, int);
int mlockall(int);
-int mprotect(void *, uint64_t, int) paramsnonnull() privileged;
+int mprotect(void *, uint64_t, int) privileged;
int msync(void *, size_t, int);
int munlock(const void *, size_t);
int munlockall(void);
@@ -150,11 +149,11 @@ int munmap_s(void *, uint64_t);
int nice(int);
int open(const char *, int, ...) nodiscard;
int openanon(char *, unsigned) nodiscard;
-int openat();
+int openat(int, const char *, int, ...);
int pause(void);
int personality(uint64_t);
-int pipe(int[hasatleast 2]) paramsnonnull() nodiscard;
-int pipe2(int[hasatleast 2], int) paramsnonnull() nodiscard;
+int pipe(int[hasatleast 2]) nodiscard;
+int pipe2(int[hasatleast 2], int) nodiscard;
int posix_fadvise(int, uint64_t, uint64_t, int);
int posix_fallocate(int, int64_t, int64_t);
int posix_madvise(void *, uint64_t, int);
@@ -184,7 +183,7 @@ int sigaction(int, const struct sigaction *, struct sigaction *);
int sigignore(int);
int sigprocmask(int, const struct sigset *, struct sigset *);
int sigsuspend(const struct sigset *);
-int stat(const char *, struct stat *) paramsnonnull();
+int stat(const char *, struct stat *);
int symlink(const char *, const char *);
int symlinkat(const char *, int, const char *);
int sync_file_range(int, int64_t, int64_t, unsigned);
@@ -230,7 +229,7 @@ uint32_t gettid(void) nosideeffect;
uint32_t getuid(void) nosideeffect;
uint32_t umask(int32_t);
void *getprocaddressmodule(const char *, const char *);
-void *mmap(void *, uint64_t, int32_t, int32_t, int32_t, int64_t) vallocesque;
+void *mmap(void *, uint64_t, int32_t, int32_t, int32_t, int64_t);
void *mremap(void *, uint64_t, uint64_t, int32_t, void *);
#define getcwd(BUF, SIZE) \
diff --git a/libc/calls/chdir.c b/libc/calls/chdir.c
index 859f71d1d..741acd40e 100644
--- a/libc/calls/chdir.c
+++ b/libc/calls/chdir.c
@@ -20,12 +20,14 @@
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/dce.h"
+#include "libc/sysv/errfuns.h"
/**
* Sets current directory.
* @asyncsignalsafe
*/
int chdir(const char *path) {
+ if (!path) return efault();
if (!IsWindows()) {
return chdir$sysv(path);
} else {
diff --git a/libc/calls/chmod.c b/libc/calls/chmod.c
index f8378b869..2b6a4a180 100644
--- a/libc/calls/chmod.c
+++ b/libc/calls/chmod.c
@@ -45,5 +45,6 @@
* @see fchmod()
*/
int chmod(const char *pathname, uint32_t mode) {
+ if (!pathname) return efault();
return fchmodat$sysv(AT_FDCWD, pathname, mode, 0);
}
diff --git a/libc/calls/chown.c b/libc/calls/chown.c
index 023df63d7..2f2221066 100644
--- a/libc/calls/chown.c
+++ b/libc/calls/chown.c
@@ -20,6 +20,7 @@
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/sysv/consts/at.h"
+#include "libc/sysv/errfuns.h"
/**
* Changes owner and/or group of pathname.
@@ -34,5 +35,6 @@
* @asyncsignalsafe
*/
int chown(const char *pathname, uint32_t uid, uint32_t gid) {
+ if (!pathname) return efault();
return fchownat$sysv(AT_FDCWD, pathname, uid, gid, 0);
}
diff --git a/libc/calls/close.c b/libc/calls/close.c
index e0d700abb..14bcd0d52 100644
--- a/libc/calls/close.c
+++ b/libc/calls/close.c
@@ -17,10 +17,9 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
-#include "libc/dce.h"
#include "libc/sock/internal.h"
#include "libc/sysv/errfuns.h"
#include "libc/zipos/zipos.h"
@@ -33,7 +32,7 @@
*/
int close(int fd) {
int rc;
- if (fd == -1) return 0;
+ if (fd == -1) return einval();
if (isfdkind(fd, kFdZip)) {
rc = weaken(__zipos_close)(
(struct ZiposHandle *)(intptr_t)g_fds.p[fd].handle);
diff --git a/libc/calls/fstat-nt.c b/libc/calls/fstat-nt.c
index be4a64d3e..95d16dd03 100644
--- a/libc/calls/fstat-nt.c
+++ b/libc/calls/fstat-nt.c
@@ -49,9 +49,9 @@ textwindows int fstat$nt(int64_t handle, struct stat *st) {
: (((filetype == kNtFileTypeDisk) ? S_IFBLK : 0) |
((filetype == kNtFileTypeChar) ? S_IFCHR : 0) |
((filetype == kNtFileTypePipe) ? S_IFIFO : 0))));
- filetimetotimespec(&st->st_atim, wst.ftLastAccessFileTime);
- filetimetotimespec(&st->st_mtim, wst.ftLastWriteFileTime);
- filetimetotimespec(&st->st_ctim, wst.ftCreationFileTime);
+ st->st_atim = filetimetotimespec(wst.ftLastAccessFileTime);
+ st->st_mtim = filetimetotimespec(wst.ftLastWriteFileTime);
+ st->st_ctim = filetimetotimespec(wst.ftCreationFileTime);
st->st_size = (uint64_t)wst.nFileSizeHigh << 32 | wst.nFileSizeLow;
st->st_blksize = PAGESIZE;
st->st_dev = wst.dwVolumeSerialNumber;
diff --git a/libc/calls/fstat-sysv.c b/libc/calls/fstat-sysv.c
index 0931d4f4a..de793d0b1 100644
--- a/libc/calls/fstat-sysv.c
+++ b/libc/calls/fstat-sysv.c
@@ -23,7 +23,7 @@
* Supports fstat(), etc. implementations.
* @asyncsignalsafe
*/
-int32_t fstat$sysv(int32_t fd, struct stat *st) {
+textstartup int32_t fstat$sysv(int32_t fd, struct stat *st) {
int res;
if ((res = __fstat$sysv(fd, st)) != -1) {
stat2linux(st);
diff --git a/libc/calls/fstat.c b/libc/calls/fstat.c
index aec7e4567..df1812319 100644
--- a/libc/calls/fstat.c
+++ b/libc/calls/fstat.c
@@ -17,7 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/dce.h"
diff --git a/libc/calls/g_fds.c b/libc/calls/g_fds.c
index 17e42925f..896004fe8 100644
--- a/libc/calls/g_fds.c
+++ b/libc/calls/g_fds.c
@@ -17,18 +17,19 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/bits/bits.h"
+#include "libc/bits/initializer.h"
#include "libc/bits/pushpop.h"
#include "libc/calls/internal.h"
-#include "libc/macros.h"
#include "libc/nt/runtime.h"
#include "libc/sysv/consts/fileno.h"
+STATIC_YOINK("_init_g_fds");
+
struct Fds g_fds;
-INITIALIZER(300, _init_g_fds, {
+void InitializeFileDescriptors(void) {
struct Fds *fds;
- fds = VEIL("D", &g_fds);
+ fds = VEIL("r", &g_fds);
pushmov(&fds->f, 3ul);
pushmov(&fds->n, ARRAYLEN(fds->__init_p));
fds->p = fds->__init_p;
@@ -40,4 +41,4 @@ INITIALIZER(300, _init_g_fds, {
GetStdHandle(pushpop(kNtStdOutputHandle));
fds->__init_p[STDERR_FILENO].handle =
GetStdHandle(pushpop(kNtStdErrorHandle));
-})
+}
diff --git a/libc/runtime/mapanon-thunk.S b/libc/calls/g_fds_init.S
similarity index 92%
rename from libc/runtime/mapanon-thunk.S
rename to libc/calls/g_fds_init.S
index 88cc453c7..69b9d40f1 100644
--- a/libc/runtime/mapanon-thunk.S
+++ b/libc/calls/g_fds_init.S
@@ -18,16 +18,12 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.source __FILE__
-mapanon:push %rbp
- mov %rsp,%rbp
- push %rbx
- push %rbx
- ezlea _base,bx
- call __mapanon
- pop %rbx
- pop %rbx
- pop %rbp
- ret
- .endfn mapanon,globl
+ .init.start 300,_init_g_fds
+ push %rdi
+ push %rsi
+ call InitializeFileDescriptors
+ pop %rsi
+ pop %rdi
+ .init.end 300,_init_g_fds
+ .source __FILE__
diff --git a/libc/calls/getcwd.c b/libc/calls/getcwd.c
index bf3c21c48..70c5225f0 100644
--- a/libc/calls/getcwd.c
+++ b/libc/calls/getcwd.c
@@ -35,23 +35,28 @@
* @error ERANGE, EINVAL
*/
char *(getcwd)(char *buf, size_t size) {
- buf[0] = '\0';
- if (!IsWindows()) {
- int olderr = errno;
- if (getcwd$sysv(buf, size) != NULL) {
- return buf;
- } else if (IsXnu() && errno == ENOSYS) {
- if (size >= 2) {
- buf[0] = '.'; /* XXX: could put forth more effort */
- buf[1] = '\0';
- errno = olderr;
+ if (buf) {
+ buf[0] = '\0';
+ if (!IsWindows()) {
+ int olderr = errno;
+ if (getcwd$sysv(buf, size) != NULL) {
return buf;
- } else {
- erange();
+ } else if (IsXnu() && errno == ENOSYS) {
+ if (size >= 2) {
+ buf[0] = '.'; /* XXX: could put forth more effort */
+ buf[1] = '\0';
+ errno = olderr;
+ return buf;
+ } else {
+ erange();
+ }
}
+ return NULL;
+ } else {
+ return getcwd$nt(buf, size);
}
- return NULL;
} else {
- return getcwd$nt(buf, size);
+ efault();
+ return NULL;
}
}
diff --git a/libc/calls/getenv.c b/libc/calls/getenv.c
index df77208b0..19932159e 100644
--- a/libc/calls/getenv.c
+++ b/libc/calls/getenv.c
@@ -26,10 +26,13 @@
* Returns value of environment variable, or NULL if not found.
*/
char *getenv(const char *name) {
- char *empty[1] = {NULL};
- char **ep = firstnonnull(environ, empty);
- unsigned namelen = strlen(name);
- for (int i = 0; ep[i]; ++i) {
+ char **ep;
+ size_t i, namelen;
+ char *empty[1] = {0};
+ ep = environ;
+ if (!ep) ep = empty;
+ namelen = strlen(name);
+ for (i = 0; ep[i]; ++i) {
if (strncmp(ep[i], name, namelen) == 0 && ep[i][namelen] == '=') {
return &ep[i][namelen + 1];
}
diff --git a/test/libc/intrin/phaddw_test.c b/libc/calls/getrusage-nt.c
similarity index 69%
rename from test/libc/intrin/phaddw_test.c
rename to libc/calls/getrusage-nt.c
index 99c8319de..2df0f13f9 100644
--- a/test/libc/intrin/phaddw_test.c
+++ b/libc/calls/getrusage-nt.c
@@ -17,32 +17,29 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/intrin/phaddw.h"
+#include "libc/calls/calls.h"
+#include "libc/calls/internal.h"
+#include "libc/calls/struct/rusage.h"
+#include "libc/conv/conv.h"
+#include "libc/nt/accounting.h"
+#include "libc/nt/runtime.h"
+#include "libc/nt/thread.h"
#include "libc/str/str.h"
-#include "libc/testlib/testlib.h"
-#include "tool/viz/lib/formatstringtable-testlib.h"
-/* clang-format off */
+#include "libc/sysv/consts/rusage.h"
-FIXTURE(phaddw, disableHardwareExtensions) {
- memset((/*unconst*/ void *)kCpuids, 0, sizeof(kCpuids));
-}
-
-TEST(phaddw, testOverflow_wrapsAround) {
- short M[2][8] = {
- {0x7fff, 0, 0x7fff, 1, 13004, -30425, 20777, -16389},
- {-28040, 13318, -1336, -24798, -13876, 3599, -7346, -23575},
- };
- phaddw(M[0], M[0], M[1]);
- EXPECT_SHRTMATRIXEQ(2, 8, M, "\n\
- 32767 -32768 -17421 4388 -14722 -26134 -10277 -30921\n\
--28040 13318 -1336 -24798 -13876 3599 -7346 -23575");
-}
-
-TEST(phaddw, testAliasing_isOk) {
- short M[1][8] = {
- {0,1, 2,3, 4,5, 6,7},
- };
- phaddw(M[0],M[0],M[0]);
- EXPECT_SHRTMATRIXEQ(1, 8, M, "\n\
- 1 5 9 13 1 5 9 13");
+textwindows int getrusage$nt(int who, struct rusage *usage) {
+ struct NtFileTime CreationFileTime;
+ struct NtFileTime ExitFileTime;
+ struct NtFileTime KernelFileTime;
+ struct NtFileTime UserFileTime;
+ memset(usage, 0, sizeof(*usage));
+ if ((who == RUSAGE_SELF ? GetProcessTimes : GetThreadTimes)(
+ (who == RUSAGE_SELF ? GetCurrentProcess : GetCurrentThread)(),
+ &CreationFileTime, &ExitFileTime, &KernelFileTime, &UserFileTime)) {
+ filetimetotimeval(&usage->ru_utime, UserFileTime);
+ filetimetotimeval(&usage->ru_stime, KernelFileTime);
+ return 0;
+ } else {
+ return winerr();
+ }
}
diff --git a/libc/calls/getrusage.c b/libc/calls/getrusage.c
index db4332f66..c8979fa7f 100644
--- a/libc/calls/getrusage.c
+++ b/libc/calls/getrusage.c
@@ -19,35 +19,8 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
-#include "libc/calls/struct/rusage.h"
-#include "libc/conv/conv.h"
-#include "libc/dce.h"
-#include "libc/nt/accounting.h"
-#include "libc/nt/runtime.h"
-#include "libc/nt/struct/filetime.h"
-#include "libc/nt/thread.h"
-#include "libc/runtime/runtime.h"
-#include "libc/str/str.h"
-#include "libc/sysv/consts/rusage.h"
#include "libc/sysv/errfuns.h"
-static textwindows noinline int getrusage$nt(int who, struct rusage *usage) {
- struct NtFileTime CreationFileTime;
- struct NtFileTime ExitFileTime;
- struct NtFileTime KernelFileTime;
- struct NtFileTime UserFileTime;
- memset(usage, 0, sizeof(*usage));
- if ((who == RUSAGE_SELF ? GetProcessTimes : GetThreadTimes)(
- (who == RUSAGE_SELF ? GetCurrentProcess : GetCurrentThread)(),
- &CreationFileTime, &ExitFileTime, &KernelFileTime, &UserFileTime)) {
- filetimetotimeval(&usage->ru_utime, UserFileTime);
- filetimetotimeval(&usage->ru_stime, KernelFileTime);
- return 0;
- } else {
- return winerr();
- }
-}
-
/**
* Returns resource usage statistics.
*
diff --git a/libc/calls/growfds.c b/libc/calls/growfds.c
index 8759fd7ff..5329907aa 100644
--- a/libc/calls/growfds.c
+++ b/libc/calls/growfds.c
@@ -18,7 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
-#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/internal.h"
#include "libc/mem/mem.h"
#include "libc/sysv/errfuns.h"
diff --git a/libc/calls/hefty/access.c b/libc/calls/hefty/access.c
index cfa2647e1..94e3c43ec 100644
--- a/libc/calls/hefty/access.c
+++ b/libc/calls/hefty/access.c
@@ -17,10 +17,11 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/dce.h"
-#include "libc/calls/internal.h"
#include "libc/calls/calls.h"
+#include "libc/calls/internal.h"
+#include "libc/dce.h"
#include "libc/sysv/consts/at.h"
+#include "libc/sysv/errfuns.h"
/**
* Checks if effective user can access path in particular ways.
@@ -31,6 +32,7 @@
* @asyncsignalsafe
*/
int access(const char *path, int mode) {
+ if (!path) return efault();
if (!IsWindows()) {
return faccessat$sysv(AT_FDCWD, path, mode, 0);
} else {
diff --git a/libc/calls/hefty/copyfile.c b/libc/calls/hefty/copyfile.c
new file mode 100644
index 000000000..061cf21ca
--- /dev/null
+++ b/libc/calls/hefty/copyfile.c
@@ -0,0 +1,108 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/hefty/copyfile.h"
+#include "libc/calls/internal.h"
+#include "libc/calls/struct/stat.h"
+#include "libc/dce.h"
+#include "libc/nt/createfile.h"
+#include "libc/nt/enum/accessmask.h"
+#include "libc/nt/enum/creationdisposition.h"
+#include "libc/nt/files.h"
+#include "libc/nt/runtime.h"
+#include "libc/sysv/consts/at.h"
+#include "libc/sysv/consts/madv.h"
+#include "libc/sysv/consts/o.h"
+#include "libc/time/time.h"
+
+static textwindows int copyfile$nt(const char *src, const char *dst,
+ int flags) {
+ int64_t fhsrc, fhdst;
+ struct NtFileTime accessed, modified;
+ char16_t src16[PATH_MAX], dst16[PATH_MAX];
+ if (mkntpath(src, src16) == -1) return -1;
+ if (mkntpath(dst, dst16) == -1) return -1;
+ if (CopyFile(src16, dst16, !!(flags & COPYFILE_NOCLOBBER))) {
+ if (flags & COPYFILE_PRESERVE_TIMESTAMPS) {
+ fhsrc = CreateFile(src16, kNtFileReadAttributes, kNtFileShareRead, NULL,
+ kNtOpenExisting, kNtFileAttributeNormal, 0);
+ fhdst = CreateFile(dst16, kNtFileWriteAttributes, kNtFileShareRead, NULL,
+ kNtOpenExisting, kNtFileAttributeNormal, 0);
+ if (fhsrc != -1 && fhdst != -1) {
+ GetFileTime(fhsrc, NULL, &accessed, &modified);
+ SetFileTime(fhdst, NULL, &accessed, &modified);
+ }
+ CloseHandle(fhsrc);
+ CloseHandle(fhdst);
+ }
+ return 0;
+ } else {
+ return winerr();
+ }
+}
+
+static int copyfile$sysv(const char *src, const char *dst, int flags) {
+ struct stat st;
+ size_t remaining;
+ ssize_t transferred;
+ struct timespec amtime[2];
+ int64_t inoffset, outoffset;
+ int rc, srcfd, dstfd, oflags, omode;
+ rc = -1;
+ if ((srcfd = openat$sysv(AT_FDCWD, src, O_RDONLY, 0)) != -1) {
+ if (fstat$sysv(srcfd, &st) != -1) {
+ omode = st.st_mode & 0777;
+ oflags = O_WRONLY | O_CREAT;
+ if (flags & COPYFILE_NOCLOBBER) oflags |= O_EXCL;
+ if ((dstfd = openat$sysv(AT_FDCWD, dst, oflags, omode)) != -1) {
+ remaining = st.st_size;
+ ftruncate(dstfd, remaining);
+ inoffset = 0;
+ outoffset = 0;
+ while (remaining &&
+ (transferred = copy_file_range(
+ srcfd, &inoffset, dstfd, &outoffset, remaining, 0)) != -1) {
+ remaining -= transferred;
+ }
+ if (!remaining) {
+ rc = 0;
+ if (flags & COPYFILE_PRESERVE_TIMESTAMPS) {
+ amtime[0] = st.st_atim;
+ amtime[1] = st.st_mtim;
+ utimensat$sysv(dstfd, NULL, amtime, 0);
+ }
+ }
+ rc |= close$sysv(dstfd);
+ }
+ }
+ rc |= close$sysv(srcfd);
+ }
+ return rc;
+}
+
+/**
+ * Copies file.
+ */
+int copyfile(const char *src, const char *dst, int flags) {
+ if (!IsWindows()) {
+ return copyfile$sysv(src, dst, flags);
+ } else {
+ return copyfile$nt(src, dst, flags);
+ }
+}
diff --git a/libc/calls/hefty/copyfile.h b/libc/calls/hefty/copyfile.h
new file mode 100644
index 000000000..0f0c78ed6
--- /dev/null
+++ b/libc/calls/hefty/copyfile.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_CALLS_HEFTY_COPYFILE_H_
+#define COSMOPOLITAN_LIBC_CALLS_HEFTY_COPYFILE_H_
+
+#define COPYFILE_NOCLOBBER 1
+#define COPYFILE_PRESERVE_OWNER 2
+#define COPYFILE_PRESERVE_TIMESTAMPS 4
+
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+int copyfile(const char *, const char *, int) paramsnonnull();
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_CALLS_HEFTY_COPYFILE_H_ */
diff --git a/libc/calls/hefty/faccessat-nt.c b/libc/calls/hefty/faccessat-nt.c
new file mode 100644
index 000000000..e5290a735
--- /dev/null
+++ b/libc/calls/hefty/faccessat-nt.c
@@ -0,0 +1,30 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/hefty/internal.h"
+#include "libc/calls/internal.h"
+#include "libc/sysv/consts/at.h"
+#include "libc/sysv/errfuns.h"
+
+int faccessat$nt(int dirfd, const char *path, int mode, uint32_t flags) {
+ char16_t path16[PATH_MAX];
+ if (dirfd != AT_FDCWD || flags) return einval();
+ if (mkntpath(path, path16) == -1) return -1;
+ return ntaccesscheck(path16, mode);
+}
diff --git a/libc/calls/hefty/faccessat.c b/libc/calls/hefty/faccessat.c
index bd8302aca..2a36c592e 100644
--- a/libc/calls/hefty/faccessat.c
+++ b/libc/calls/hefty/faccessat.c
@@ -18,6 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/calls.h"
+#include "libc/calls/hefty/internal.h"
#include "libc/calls/internal.h"
#include "libc/dce.h"
#include "libc/sysv/consts/at.h"
@@ -33,12 +34,10 @@
* @asyncsignalsafe
*/
int faccessat(int dirfd, const char *path, int mode, uint32_t flags) {
+ if (!path) return efault();
if (!IsWindows()) {
return faccessat$sysv(dirfd, path, mode, flags);
} else {
- char16_t path16[PATH_MAX];
- if (dirfd != AT_FDCWD || flags) return einval();
- if (mkntpath(path, path16) == -1) return -1;
- return ntaccesscheck(path16, mode);
+ return faccessat$nt(dirfd, path, mode, flags);
}
}
diff --git a/libc/calls/hefty/hefty.mk b/libc/calls/hefty/hefty.mk
index 5d9ef1b70..cd294d02f 100644
--- a/libc/calls/hefty/hefty.mk
+++ b/libc/calls/hefty/hefty.mk
@@ -36,17 +36,18 @@ LIBC_CALLS_HEFTY_A_CHECKS = \
LIBC_CALLS_HEFTY_A_DIRECTDEPS = \
LIBC_ALG \
+ LIBC_CALLS \
LIBC_CONV \
LIBC_FMT \
LIBC_MEM \
- LIBC_STR \
LIBC_NEXGEN32E \
- LIBC_RUNTIME \
- LIBC_CALLS \
- LIBC_STUBS \
LIBC_NT_KERNELBASE \
+ LIBC_RUNTIME \
+ LIBC_STR \
+ LIBC_STUBS \
+ LIBC_SYSV \
LIBC_SYSV_CALLS \
- LIBC_SYSV
+ LIBC_TIME
LIBC_CALLS_HEFTY_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_CALLS_HEFTY_A_DIRECTDEPS),$($(x))))
diff --git a/libc/calls/hefty/internal.h b/libc/calls/hefty/internal.h
index b56dd330a..e3e86cd0c 100644
--- a/libc/calls/hefty/internal.h
+++ b/libc/calls/hefty/internal.h
@@ -3,6 +3,7 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
+int faccessat$nt(int, const char *, int, uint32_t) hidden;
int execve$nt(const char *, char *const[], char *const[]) hidden;
int spawnve$nt(unsigned, int[3], const char *, char *const[],
char *const[]) hidden;
diff --git a/libc/calls/hefty/mkntenvblock.c b/libc/calls/hefty/mkntenvblock.c
index 8777c25d3..493f42c80 100644
--- a/libc/calls/hefty/mkntenvblock.c
+++ b/libc/calls/hefty/mkntenvblock.c
@@ -35,36 +35,32 @@
* @return freshly allocated lpEnvironment or NULL w/ errno
*/
textwindows char16_t *mkntenvblock(char *const envp[]) {
- size_t block_i = 0;
- size_t block_n = 0;
- char16_t *block_p = NULL;
- size_t i, j;
- if (!(envp = sortenvp(envp))) goto error;
- const char16_t kNul = u'\0';
- for (i = 0; envp[i]; ++i) {
- unsigned consumed;
- for (j = 0;; j += consumed) {
- wint_t wc;
- char16_t cbuf[2];
- consumed = abs(tpdecode(&envp[i][j], &wc));
- if (CONCAT(&block_p, &block_i, &block_n, cbuf,
- abs(pututf16(cbuf, ARRAYLEN(cbuf), wc, false))) == -1) {
- goto error;
+ wint_t wc;
+ size_t i, j, bi, bn;
+ char16_t *bp, cbuf[2];
+ unsigned consumed, produced;
+ bi = 0;
+ bn = 8;
+ bp = NULL;
+ if ((envp = sortenvp(envp)) && (bp = calloc(bn, sizeof(char16_t)))) {
+ for (i = 0; envp[i]; ++i) {
+ for (j = 0;; j += consumed) {
+ consumed = abs(tpdecode(&envp[i][j], &wc));
+ produced = abs(pututf16(cbuf, ARRAYLEN(cbuf), wc, false));
+ if (CONCAT(&bp, &bi, &bn, cbuf, produced) == -1) goto error;
+ if (!wc) break;
}
- if (!wc) break;
}
+ ++bi;
+ if (bi > ENV_MAX) {
+ e2big();
+ goto error;
+ }
+ free(envp);
+ return bp;
}
- if (APPEND(&block_p, &block_i, &block_n, &kNul) == -1) {
- goto error;
- }
- if (block_i > ENV_MAX) {
- e2big();
- goto error;
- }
- free(envp);
- return block_p;
error:
free(envp);
- free(block_p);
+ free(bp);
return NULL;
}
diff --git a/libc/calls/internal.h b/libc/calls/internal.h
index 3e7ed2bb0..665c2a1a4 100644
--- a/libc/calls/internal.h
+++ b/libc/calls/internal.h
@@ -19,12 +19,14 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#ifndef COSMOPOLITAN_LIBC_CALLS_INTERNAL_H_
#define COSMOPOLITAN_LIBC_CALLS_INTERNAL_H_
+#ifndef __STRICT_ANSI__
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/limits.h"
#include "libc/macros.h"
#include "libc/nt/struct/securityattributes.h"
#include "libc/nt/struct/startupinfo.h"
+#include "libc/nt/struct/systeminfo.h"
#include "libc/runtime/runtime.h"
#define kSigactionMinRva 8 /* >SIG_{ERR,DFL,IGN,...} */
@@ -36,6 +38,7 @@ COSMOPOLITAN_C_START_
struct NtContext;
struct NtWin32FileAttributeData;
+struct ZiposHandle;
struct __darwin_siginfo;
struct __darwin_ucontext;
struct itimerval;
@@ -45,7 +48,7 @@ struct sigset;
struct sysinfo;
struct timeval;
struct timezone;
-struct ZiposHandle;
+struct utimbuf;
struct IoctlPtmGet {
int theduxfd;
@@ -62,7 +65,8 @@ struct IoctlPtmGet {
* and helps us abstract peculiarities like close() vs. closesocket().
*/
struct Fds {
- size_t f, n;
+ size_t f; // length
+ size_t n; // capacity
struct Fd {
int64_t handle;
int64_t extra;
@@ -132,6 +136,7 @@ i32 __dup3$sysv(i32, i32, i32) hidden;
i32 __fstat$sysv(i32, struct stat *) hidden;
i32 __fstatat$sysv(i32, const char *, struct stat *, i32) hidden;
i32 __pipe2$sysv(i32[hasatleast 2], u32) hidden;
+i32 __utimensat$sysv(i32, const char *, const struct timespec *, i32) hidden;
i32 chdir$sysv(const char *) hidden;
i32 clock_gettime$sysv(i32, struct timespec *) hidden;
i32 close$sysv(i32) hidden;
@@ -154,6 +159,8 @@ i32 fstat$sysv(i32, struct stat *) hidden;
i32 fstatat$sysv(i32, const char *, struct stat *, i32) hidden;
i32 fsync$sysv(i32) hidden;
i32 ftruncate$sysv(i32, i64) hidden;
+i32 futimes$sysv(i32, const struct timeval *) hidden;
+i32 futimesat$sysv(i32, const char *, const struct timeval *) hidden;
i32 getdents(i32, char *, u32) hidden;
i32 getppid$sysv(void) hidden;
i32 getpriority$sysv(i32, u32) hidden;
@@ -164,6 +171,7 @@ i32 ioctl$sysv(i32, u64, void *) hidden;
i32 kill$sysv(i32, i32, i32) hidden;
i32 linkat$sysv(i32, const char *, i32, const char *, i32) hidden;
i32 lseek$sysv(i32, i64, i32) hidden;
+i32 lutimes$sysv(const char *, const struct timeval *) hidden;
i32 madvise$sysv(void *, size_t, i32) hidden;
i32 memfd_create$sysv(const char *, u32) hidden;
i32 mkdirat$sysv(i32, const char *, u32) hidden;
@@ -194,6 +202,8 @@ i32 sysinfo$sysv(struct sysinfo *) hidden;
i32 truncate$sysv(const char *, u64) hidden;
i32 uname$sysv(char *) hidden;
i32 unlinkat$sysv(i32, const char *, i32) hidden;
+i32 utime$sysv(const char *, const struct utimbuf *) hidden;
+i32 utimensat$sysv(i32, const char *, const struct timespec *, i32) hidden;
i32 utimes$sysv(const char *, const struct timeval *) hidden;
i32 wait4$sysv(i32, i32 *, i32, struct rusage *) hidden;
i64 copy_file_range$sysv(i32, long *, i32, long *, u64, u32) hidden;
@@ -207,12 +217,12 @@ i64 sendfile$sysv(i32, i32, i64 *, u64) hidden;
i64 splice$sysv(i32, i64 *, i32, i64 *, u64, u32) hidden;
i64 vmsplice$sysv(i32, const struct iovec *, i64, u32) hidden;
i64 write$sysv(i32, const void *, u64) hidden;
+int setresgid$sysv(uint32_t, uint32_t, uint32_t) hidden;
+int setresuid$sysv(uint32_t, uint32_t, uint32_t) hidden;
u32 getgid$sysv(void) hidden;
u32 getpid$sysv(void) hidden;
u32 gettid$sysv(void) hidden;
u32 getuid$sysv(void) hidden;
-int setresuid$sysv(uint32_t, uint32_t, uint32_t) hidden;
-int setresgid$sysv(uint32_t, uint32_t, uint32_t) hidden;
void *mmap$sysv(void *, u64, u32, u32, i64, i64) hidden;
void *mremap$sysv(void *, u64, u64, i32, void *) hidden;
@@ -223,13 +233,13 @@ void *mremap$sysv(void *, u64, u64, i32, void *) hidden;
int __getpid(void) hidden;
void __onfork(void) hidden;
bool32 __sigenter(i32, struct siginfo *, struct ucontext *) hidden;
-i32 __mprotect(void *, u64, i32) privileged;
i32 fixupnewfd$sysv(i32, i32) hidden;
i32 tunefd$sysv(i32, i32, i32, i32) hidden;
u32 fprot2nt(i32, i32) hidden;
u32 prot2nt(i32, i32) privileged;
void __restore_rt() hidden;
void __sigenter$xnu(void *, i32, i32, void *, void *) hidden noreturn;
+int utimensat$xnu(int, const char *, const struct timespec *, int) hidden;
void stat2linux(void *) hidden;
void xnutrampoline(void *, i32, i32, const struct __darwin_siginfo *,
const struct __darwin_ucontext *) hidden noreturn;
@@ -271,6 +281,8 @@ int wait4$nt(int, int *, int, struct rusage *) hidden;
i64 lseek$nt(int, i64, int) hidden;
ssize_t read$nt(struct Fd *, const struct iovec *, size_t, ssize_t) hidden;
ssize_t write$nt(struct Fd *, const struct iovec *, size_t, ssize_t) hidden;
+int utimensat$nt(int, const char *, const struct timespec *, int) hidden;
+int getrusage$nt(int, struct rusage *) hidden;
/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § syscalls » windows nt » support ─╬─│┼
@@ -317,4 +329,5 @@ int __mkntpath(const char *, unsigned, char16_t[hasatleast PATH_MAX - 16])
#undef u64
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_CALLS_INTERNAL_H_ */
diff --git a/libc/calls/ioctl-default.c b/libc/calls/ioctl-default.c
index 2ec696629..ecccd46c2 100644
--- a/libc/calls/ioctl-default.c
+++ b/libc/calls/ioctl-default.c
@@ -17,7 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/internal.h"
#include "libc/calls/ioctl.h"
#include "libc/nt/winsock.h"
diff --git a/libc/calls/ioctl-tiocgwinsz-nt.c b/libc/calls/ioctl-tiocgwinsz-nt.c
index a787a6a1f..5aac3c3f6 100644
--- a/libc/calls/ioctl-tiocgwinsz-nt.c
+++ b/libc/calls/ioctl-tiocgwinsz-nt.c
@@ -32,18 +32,17 @@ textwindows int ioctl$tiocgwinsz$nt(int fd, struct winsize *ws) {
struct NtConsoleScreenBufferInfoEx sbinfo;
if (!isfdkind(fd, kFdFile)) return ebadf();
if (!GetConsoleMode(g_fds.p[fd].handle, &mode)) return enotty();
- if (g_ntstartupinfo.dwFlags & kNtStartfUsecountchars) {
- ws->ws_col = g_ntstartupinfo.dwXCountChars;
- ws->ws_row = g_ntstartupinfo.dwYCountChars;
- ws->ws_xpixel = 0;
- ws->ws_ypixel = 0;
- return 0;
- }
memset(&sbinfo, 0, sizeof(sbinfo));
sbinfo.cbSize = sizeof(sbinfo);
if (GetConsoleScreenBufferInfoEx(g_fds.p[fd].handle, &sbinfo)) {
- ws->ws_col = sbinfo.srWindow.Right;
- ws->ws_row = sbinfo.srWindow.Bottom;
+ ws->ws_col = sbinfo.srWindow.Right - sbinfo.srWindow.Left;
+ ws->ws_row = sbinfo.srWindow.Bottom - sbinfo.srWindow.Top;
+ ws->ws_xpixel = 0;
+ ws->ws_ypixel = 0;
+ return 0;
+ } else if (g_ntstartupinfo.dwFlags & kNtStartfUsecountchars) {
+ ws->ws_col = g_ntstartupinfo.dwXCountChars;
+ ws->ws_row = g_ntstartupinfo.dwYCountChars;
ws->ws_xpixel = 0;
ws->ws_ypixel = 0;
return 0;
diff --git a/libc/calls/ioctl.c b/libc/calls/ioctl.c
index 759117b36..90e4bdb47 100644
--- a/libc/calls/ioctl.c
+++ b/libc/calls/ioctl.c
@@ -17,6 +17,9 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#ifdef __STRICT_ANSI__
+#undef __STRICT_ANSI__
+#endif
#include "libc/calls/ioctl.h"
#define EQUAL(X, Y) ((X) == (Y))
diff --git a/libc/calls/ischardev.c b/libc/calls/ischardev.c
index 54585a62f..a65b8d52d 100644
--- a/libc/calls/ischardev.c
+++ b/libc/calls/ischardev.c
@@ -28,7 +28,7 @@
/**
* Returns true if file descriptor is backed by character i/o.
*/
-bool32 ischardev(int fd) {
+textstartup bool32 ischardev(int fd) {
int olderr;
struct stat st;
if (!IsWindows()) {
diff --git a/libc/calls/isdebuggerpresent.c b/libc/calls/isdebuggerpresent.c
index 802e6a6da..4522428f0 100644
--- a/libc/calls/isdebuggerpresent.c
+++ b/libc/calls/isdebuggerpresent.c
@@ -24,13 +24,14 @@
#include "libc/conv/conv.h"
#include "libc/dce.h"
#include "libc/log/log.h"
+#include "libc/nexgen32e/vendor.h"
#include "libc/nt/struct/teb.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/o.h"
-#define kBufSize 1024
+#define kBufSize 1024
#define kProcStatus "/proc/self/status"
alignas(16) static const char kGdbPid[] = "TracerPid:\t";
@@ -43,18 +44,20 @@ int IsDebuggerPresent(bool force) {
ssize_t got;
char buf[1024];
res = 0;
- if (force || isempty(getenv("HEISENDEBUG"))) {
- if (IsWindows()) {
- res = NtGetPeb()->BeingDebugged;
- } else if (IsLinux()) {
- if ((fd = openat$sysv(AT_FDCWD, kProcStatus, O_RDONLY, 0)) != -1) {
- if ((got = read$sysv(fd, buf, sizeof(buf) - sizeof(kGdbPid))) != -1) {
- buf[got] = '\0';
- res = atoi(firstnonnull(strstr(buf, kGdbPid), kGdbPid) +
- strlen(kGdbPid));
- }
- close$sysv(fd);
+ if (!force) {
+ if (getenv("HEISENDEBUG")) return false;
+ if (IsGenuineCosmo()) return false;
+ }
+ if (IsWindows()) {
+ res = NtGetPeb()->BeingDebugged;
+ } else if (IsLinux()) {
+ if ((fd = openat$sysv(AT_FDCWD, kProcStatus, O_RDONLY, 0)) != -1) {
+ if ((got = read$sysv(fd, buf, sizeof(buf) - sizeof(kGdbPid))) != -1) {
+ buf[got] = '\0';
+ res =
+ atoi(firstnonnull(strstr(buf, kGdbPid), kGdbPid) + strlen(kGdbPid));
}
+ close$sysv(fd);
}
}
return res;
diff --git a/libc/calls/isdirectory.c b/libc/calls/isdirectory.c
index 05b3ad7ef..6e79fa388 100644
--- a/libc/calls/isdirectory.c
+++ b/libc/calls/isdirectory.c
@@ -17,12 +17,12 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/errno.h"
-#include "libc/calls/struct/stat.h"
#include "libc/calls/calls.h"
+#include "libc/calls/struct/stat.h"
+#include "libc/errno.h"
/**
- * Returns true if file exists and is a directory
+ * Returns true if file exists and is a directory.
*/
bool isdirectory(const char *path) {
struct stat st;
diff --git a/libc/calls/kntprioritycombos.c b/libc/calls/kntprioritycombos.c
index 00d28d644..91cf47674 100644
--- a/libc/calls/kntprioritycombos.c
+++ b/libc/calls/kntprioritycombos.c
@@ -24,33 +24,35 @@
#include "libc/nt/enum/processcreationflags.h"
#include "libc/nt/enum/threadpriority.h"
+#define FFS(x) __builtin_ffs(x)
+
const struct NtPriorityCombo kNtPriorityCombos[] = {
- {-20, ffs(kNtHighPriorityClass), kNtThreadPriorityHighest, 15},
- {-18, ffs(kNtHighPriorityClass), kNtThreadPriorityTimeCritical, 15},
- {-17, ffs(kNtNormalPriorityClass), kNtThreadPriorityTimeCritical, 15},
- {-15, ffs(kNtIdlePriorityClass), kNtThreadPriorityTimeCritical, 15},
- {-13, ffs(kNtHighPriorityClass), kNtThreadPriorityAboveNormal, 14},
- {-11, ffs(kNtHighPriorityClass), kNtThreadPriorityNormal, 13},
- {-9, ffs(kNtHighPriorityClass), kNtThreadPriorityBelowNormal, 12},
- {-7, ffs(kNtNormalPriorityClass), kNtThreadPriorityHighest, 11},
- {-5, ffs(kNtHighPriorityClass), kNtThreadPriorityLowest, 11},
- {-3, ffs(kNtNormalPriorityClass), kNtThreadPriorityAboveNormal, 10},
- {-1, ffs(kNtNormalPriorityClass), kNtThreadPriorityHighest, 9},
- {0, ffs(kNtNormalPriorityClass), kNtThreadPriorityNormal, 9},
- {1, ffs(kNtNormalPriorityClass), kNtThreadPriorityAboveNormal, 8},
- {2, ffs(kNtNormalPriorityClass), kNtThreadPriorityBelowNormal, 8},
- {3, ffs(kNtNormalPriorityClass), kNtThreadPriorityNormal, 7},
- {4, ffs(kNtNormalPriorityClass), kNtThreadPriorityLowest, 7},
- {5, ffs(kNtIdlePriorityClass), kNtThreadPriorityHighest, 6},
- {6, ffs(kNtNormalPriorityClass), kNtThreadPriorityBelowNormal, 6},
- {7, ffs(kNtIdlePriorityClass), kNtThreadPriorityAboveNormal, 5},
- {9, ffs(kNtNormalPriorityClass), kNtThreadPriorityLowest, 5},
- {11, ffs(kNtIdlePriorityClass), kNtThreadPriorityNormal, 4},
- {13, ffs(kNtIdlePriorityClass), kNtThreadPriorityBelowNormal, 3},
- {15, ffs(kNtIdlePriorityClass), kNtThreadPriorityLowest, 2},
- {17, ffs(kNtHighPriorityClass), kNtThreadPriorityIdle, 1},
- {18, ffs(kNtNormalPriorityClass), kNtThreadPriorityIdle, 1},
- {19, ffs(kNtIdlePriorityClass), kNtThreadPriorityIdle, 1},
+ {-20, FFS(kNtHighPriorityClass), kNtThreadPriorityHighest, 15},
+ {-18, FFS(kNtHighPriorityClass), kNtThreadPriorityTimeCritical, 15},
+ {-17, FFS(kNtNormalPriorityClass), kNtThreadPriorityTimeCritical, 15},
+ {-15, FFS(kNtIdlePriorityClass), kNtThreadPriorityTimeCritical, 15},
+ {-13, FFS(kNtHighPriorityClass), kNtThreadPriorityAboveNormal, 14},
+ {-11, FFS(kNtHighPriorityClass), kNtThreadPriorityNormal, 13},
+ {-9, FFS(kNtHighPriorityClass), kNtThreadPriorityBelowNormal, 12},
+ {-7, FFS(kNtNormalPriorityClass), kNtThreadPriorityHighest, 11},
+ {-5, FFS(kNtHighPriorityClass), kNtThreadPriorityLowest, 11},
+ {-3, FFS(kNtNormalPriorityClass), kNtThreadPriorityAboveNormal, 10},
+ {-1, FFS(kNtNormalPriorityClass), kNtThreadPriorityHighest, 9},
+ {0, FFS(kNtNormalPriorityClass), kNtThreadPriorityNormal, 9},
+ {1, FFS(kNtNormalPriorityClass), kNtThreadPriorityAboveNormal, 8},
+ {2, FFS(kNtNormalPriorityClass), kNtThreadPriorityBelowNormal, 8},
+ {3, FFS(kNtNormalPriorityClass), kNtThreadPriorityNormal, 7},
+ {4, FFS(kNtNormalPriorityClass), kNtThreadPriorityLowest, 7},
+ {5, FFS(kNtIdlePriorityClass), kNtThreadPriorityHighest, 6},
+ {6, FFS(kNtNormalPriorityClass), kNtThreadPriorityBelowNormal, 6},
+ {7, FFS(kNtIdlePriorityClass), kNtThreadPriorityAboveNormal, 5},
+ {9, FFS(kNtNormalPriorityClass), kNtThreadPriorityLowest, 5},
+ {11, FFS(kNtIdlePriorityClass), kNtThreadPriorityNormal, 4},
+ {13, FFS(kNtIdlePriorityClass), kNtThreadPriorityBelowNormal, 3},
+ {15, FFS(kNtIdlePriorityClass), kNtThreadPriorityLowest, 2},
+ {17, FFS(kNtHighPriorityClass), kNtThreadPriorityIdle, 1},
+ {18, FFS(kNtNormalPriorityClass), kNtThreadPriorityIdle, 1},
+ {19, FFS(kNtIdlePriorityClass), kNtThreadPriorityIdle, 1},
};
const unsigned kNtPriorityCombosLen = ARRAYLEN(kNtPriorityCombos);
diff --git a/libc/calls/mprotect.c b/libc/calls/mkdirat.c
similarity index 87%
rename from libc/calls/mprotect.c
rename to libc/calls/mkdirat.c
index 2e23d0202..b1d5b90ce 100644
--- a/libc/calls/mprotect.c
+++ b/libc/calls/mkdirat.c
@@ -17,16 +17,14 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/calls/internal.h"
#include "libc/calls/calls.h"
+#include "libc/calls/internal.h"
+#include "libc/sysv/consts/at.h"
-/**
- * Modifies restrictions on virtual memory address range.
- *
- * @param prot can have PROT_{NONE,READ,WRITE,EXEC,GROWSDOWN}
- * @return 0 on success, or -1 w/ errno
- * @see mmap()
- */
-int mprotect(void *addr, uint64_t len, int prot) {
- return __mprotect(addr, len, prot);
+int mkdirat(int dirfd, const char *pathname, unsigned mode) {
+ if (dirfd == AT_FDCWD) {
+ return mkdir(pathname, mode);
+ } else {
+ return mkdirat$sysv(dirfd, pathname, mode);
+ }
}
diff --git a/libc/calls/mkntpath.ncabi.c b/libc/calls/mkntpath.ncabi.c
index 5958354fc..9ea70ef93 100644
--- a/libc/calls/mkntpath.ncabi.c
+++ b/libc/calls/mkntpath.ncabi.c
@@ -39,8 +39,9 @@
* @return short count excluding NUL on success, or -1 w/ errno
* @error ENAMETOOLONG
*/
-textwindows int(mkntpath)(const char *path, unsigned flags,
- char16_t path16[hasatleast PATH_MAX - 16]) {
+forcealignargpointer textwindows int(mkntpath)(
+ const char *path, unsigned flags,
+ char16_t path16[hasatleast PATH_MAX - 16]) {
/*
* 1. Reserve +1 for NUL-terminator
* 2. Reserve +1 for UTF-16 overflow
diff --git a/libc/calls/mprotect.greg.c b/libc/calls/mprotect.greg.c
index 3f516435d..d28b3e430 100644
--- a/libc/calls/mprotect.greg.c
+++ b/libc/calls/mprotect.greg.c
@@ -26,7 +26,14 @@
#include "libc/nt/thunk/msabi.h"
#include "libc/sysv/consts/nr.h"
-privileged int __mprotect(void *addr, uint64_t len, int prot) {
+/**
+ * Modifies restrictions on virtual memory address range.
+ *
+ * @param prot can have PROT_{NONE,READ,WRITE,EXEC,GROWSDOWN}
+ * @return 0 on success, or -1 w/ errno
+ * @see mmap()
+ */
+int mprotect(void *addr, uint64_t len, int prot) {
extern __msabi typeof(VirtualProtect) *const __imp_VirtualProtect;
bool cf;
int64_t rc;
diff --git a/libc/calls/open.c b/libc/calls/open.c
index 040c154a2..d707ac834 100644
--- a/libc/calls/open.c
+++ b/libc/calls/open.c
@@ -17,7 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/dce.h"
diff --git a/libc/calls/openat.c b/libc/calls/openat.c
new file mode 100644
index 000000000..e25f21f1f
--- /dev/null
+++ b/libc/calls/openat.c
@@ -0,0 +1,49 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/calls.h"
+#include "libc/calls/internal.h"
+#include "libc/sysv/consts/at.h"
+#include "libc/sysv/errfuns.h"
+
+/**
+ * Opens file, the modern way.
+ *
+ * @param dirfd is normally AT_FDCWD or an open relative directory thing
+ * @param file is a UTF-8 string, preferably relative w/ forward slashes
+ * @param flags should be O_RDONLY, O_WRONLY, or O_RDWR, and can be or'd
+ * with O_CREAT, O_TRUNC, O_APPEND, O_EXCL, O_CLOEXEC, O_TMPFILE
+ * @param mode is an octal user/group/other permission signifier, that's
+ * ignored if O_CREAT or O_TMPFILE weren't passed
+ * @return number needing close(), or -1 w/ errno
+ * @asyncsignalsafe
+ */
+int openat(int dirfd, const char *pathname, int flags, ...) {
+ va_list va;
+ unsigned mode;
+ va_start(va, flags);
+ mode = va_arg(va, unsigned);
+ va_end(va);
+ if (!pathname) return efault();
+ if (dirfd == AT_FDCWD) {
+ return open(pathname, flags, mode);
+ } else {
+ return openat$sysv(dirfd, pathname, flags, mode);
+ }
+}
diff --git a/libc/calls/pipe.c b/libc/calls/pipe.c
index d7e76ab8b..f82d2272e 100644
--- a/libc/calls/pipe.c
+++ b/libc/calls/pipe.c
@@ -20,6 +20,7 @@
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/dce.h"
+#include "libc/sysv/errfuns.h"
/**
* Creates file-less file descriptors for inter-process communication.
@@ -30,6 +31,7 @@
* @see pipe2()
*/
int pipe(int pipefd[hasatleast 2]) {
+ if (!pipefd) return efault();
if (!IsWindows()) {
return pipe$sysv(pipefd);
} else {
diff --git a/libc/calls/pipe2.c b/libc/calls/pipe2.c
index f53f147a3..5b3997817 100644
--- a/libc/calls/pipe2.c
+++ b/libc/calls/pipe2.c
@@ -20,6 +20,7 @@
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/dce.h"
+#include "libc/sysv/errfuns.h"
/**
* Creates file-less file descriptors for interprocess communication.
@@ -29,6 +30,7 @@
* @return 0 on success, or -1 w/ errno and pipefd isn't modified
*/
int pipe2(int pipefd[hasatleast 2], int flags) {
+ if (!pipefd) return efault();
if (!IsWindows()) {
return pipe2$sysv(pipefd, flags);
} else {
diff --git a/libc/calls/pread.c b/libc/calls/pread.c
index a7b219617..b4b10f7f3 100644
--- a/libc/calls/pread.c
+++ b/libc/calls/pread.c
@@ -18,7 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
-#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/dce.h"
diff --git a/libc/calls/read.c b/libc/calls/read.c
index 8f7c34e4f..eb3e0c4b2 100644
--- a/libc/calls/read.c
+++ b/libc/calls/read.c
@@ -18,7 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
-#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/iovec.h"
diff --git a/libc/calls/readv.c b/libc/calls/readv.c
index da64fd5f2..b299c3f53 100644
--- a/libc/calls/readv.c
+++ b/libc/calls/readv.c
@@ -17,6 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/iovec.h"
diff --git a/libc/calls/rename.c b/libc/calls/rename.c
index 003c69176..8b82c02be 100644
--- a/libc/calls/rename.c
+++ b/libc/calls/rename.c
@@ -17,17 +17,20 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/dce.h"
-#include "libc/calls/internal.h"
#include "libc/calls/calls.h"
+#include "libc/calls/internal.h"
+#include "libc/dce.h"
#include "libc/sysv/consts/at.h"
+#include "libc/sysv/errfuns.h"
/**
* Moves file the Unix way.
+ *
* @return 0 on success or -1 w/ errno
* @asyncsignalsafe
*/
int rename(const char *oldpathname, const char *newpathname) {
+ if (!oldpathname || !newpathname) return efault();
if (!IsWindows()) {
return renameat$sysv(AT_FDCWD, oldpathname, AT_FDCWD, newpathname);
} else {
diff --git a/libc/calls/renameat.c b/libc/calls/renameat.c
new file mode 100644
index 000000000..21503785a
--- /dev/null
+++ b/libc/calls/renameat.c
@@ -0,0 +1,32 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/calls.h"
+#include "libc/calls/internal.h"
+#include "libc/sysv/consts/at.h"
+
+int renameat(int olddirfd, const char *oldpath, int newdirfd,
+ const char *newpath) {
+ unsigned mode;
+ if (olddirfd == AT_FDCWD && newdirfd == AT_FDCWD) {
+ return rename(oldpath, newpath);
+ } else {
+ return renameat$sysv(olddirfd, oldpath, newdirfd, newpath);
+ }
+}
diff --git a/libc/calls/sched_setaffinity.c b/libc/calls/sched_setaffinity.c
index e06481274..ee0ebf880 100644
--- a/libc/calls/sched_setaffinity.c
+++ b/libc/calls/sched_setaffinity.c
@@ -30,10 +30,14 @@
static textwindows noinline int sched_setaffinity$nt(int pid,
uint64_t bitsetsize,
const void *bitset) {
+ int rc;
+ uintptr_t mask;
+ int64_t handle;
typeof(SetThreadAffinityMask) *SetAffinityMask = SetThreadAffinityMask;
- uintptr_t mask = 0;
+ mask = 0;
memcpy(&mask, bitset, min(bitsetsize, sizeof(uintptr_t)));
- int64_t handle = 0;
+ handle = 0;
+ if (!pid) pid = GetCurrentThreadId();
if (0 < pid && pid <= UINT32_MAX) {
if (pid == GetCurrentProcessId()) {
pid = GetCurrentProcess();
@@ -50,7 +54,7 @@ static textwindows noinline int sched_setaffinity$nt(int pid,
}
}
}
- int rc = SetAffinityMask(handle ? handle : pid, mask) ? 0 : winerr();
+ rc = SetAffinityMask(handle ? handle : pid, mask) ? 0 : winerr();
if (handle) CloseHandle(handle);
return rc;
}
@@ -58,7 +62,7 @@ static textwindows noinline int sched_setaffinity$nt(int pid,
/**
* Asks kernel to only schedule process on particular CPUs.
*
- * @param pid is the process or thread id
+ * @param pid is the process or thread id (or 0 for caller)
* @param bitsetsize is byte length of bitset
* @param bitset can be manipulated using bt(), bts(), etc.
* @return 0 on success, or -1 w/ errno
diff --git a/libc/calls/sigaction.c b/libc/calls/sigaction.c
index effc4c0ce..0aa2bc505 100644
--- a/libc/calls/sigaction.c
+++ b/libc/calls/sigaction.c
@@ -20,7 +20,12 @@
#include "libc/bits/bits.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
+#include "libc/calls/struct/sigaction-freebsd.h"
+#include "libc/calls/struct/sigaction-linux.h"
+#include "libc/calls/struct/sigaction-openbsd.h"
+#include "libc/calls/struct/sigaction-xnu.h"
#include "libc/calls/struct/sigaction.h"
+#include "libc/calls/typedef/sigaction_f.h"
#include "libc/calls/ucontext.h"
#include "libc/dce.h"
#include "libc/limits.h"
@@ -32,51 +37,13 @@
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
-struct siginfo;
-
union metasigaction {
struct sigaction cosmo;
-
- struct sigaction$linux {
- intptr_t sa_handler;
- uint64_t sa_flags;
- void (*sa_restorer)(void);
- struct sigset$linux {
- uint32_t sig[2];
- } sa_mask;
- } linux;
-
- struct sigaction$freebsd {
- intptr_t sa_handler;
- uint32_t sa_flags;
- struct sigset$freebsd {
- uint32_t sig[4];
- } sa_mask;
- } freebsd;
-
- struct sigaction$openbsd {
- intptr_t sa_handler;
- struct sigset$openbsd {
- uint32_t sig[1];
- } sa_mask;
- int32_t sa_flags;
- } openbsd;
-
- struct sigaction$xnu_in {
- intptr_t sa_handler;
- void (*sa_restorer)(void *, int, int, const struct __darwin_siginfo *,
- const struct __darwin_ucontext *);
- struct sigset$xnu {
- uint32_t sig[1];
- } sa_mask;
- int32_t sa_flags;
- } xnu_in;
-
- struct sigaction$xnu_out {
- intptr_t sa_handler;
- struct sigset$xnu sa_mask;
- int32_t sa_flags;
- } xnu_out;
+ struct sigaction$linux linux;
+ struct sigaction$freebsd freebsd;
+ struct sigaction$openbsd openbsd;
+ struct sigaction$xnu_in xnu_in;
+ struct sigaction$xnu_out xnu_out;
};
#define SWITCHEROO(S1, S2, A, B, C, D) \
@@ -210,9 +177,8 @@ int(sigaction)(int sig, const struct sigaction *act, struct sigaction *oldact) {
if (rc != -1) {
if (oldact) {
oldrva = g_sighandrvas[sig];
- oldact->sa_sigaction = oldrva < kSigactionMinRva
- ? (sigaction_f)(intptr_t)oldrva
- : (sigaction_f)((uintptr_t)&_base + oldrva);
+ oldact->sa_sigaction = (sigaction_f)(
+ oldrva < kSigactionMinRva ? oldrva : (intptr_t)&_base + oldrva);
}
if (act) {
g_sighandrvas[sig] = rva;
diff --git a/libc/calls/sigsuspend.c b/libc/calls/sigsuspend.c
index 65b55a845..2021068a1 100644
--- a/libc/calls/sigsuspend.c
+++ b/libc/calls/sigsuspend.c
@@ -17,10 +17,10 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/dce.h"
+#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigset.h"
-#include "libc/calls/calls.h"
+#include "libc/dce.h"
#include "libc/sysv/errfuns.h"
/**
@@ -31,6 +31,7 @@
* @asyncsignalsafe
*/
int sigsuspend(const sigset_t *mask) {
+ if (!mask) return efault();
if (!IsWindows()) {
return sigsuspend$sysv(mask, 8);
} else {
diff --git a/libc/calls/stat.c b/libc/calls/stat.c
index 5b9ef522c..be4019b50 100644
--- a/libc/calls/stat.c
+++ b/libc/calls/stat.c
@@ -17,7 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/dce.h"
diff --git a/libc/calls/stat2linux.c b/libc/calls/stat2linux.c
index 36ee41c69..d1a31c7a7 100644
--- a/libc/calls/stat2linux.c
+++ b/libc/calls/stat2linux.c
@@ -73,7 +73,7 @@ forceinline void stat2linux_openbsd(union metastat *ms) {
* Transcodes “The Dismal Data Structure” from BSD→Linux ABI.
* @asyncsignalsafe
*/
-void stat2linux(void *ms) {
+textstartup void stat2linux(void *ms) {
if (ms) {
if (SupportsXnu() && IsXnu()) {
stat2linux_xnu((union metastat *)ms);
diff --git a/libc/calls/struct/metastat.h b/libc/calls/struct/metastat.h
index 1c18a4ee3..9bcc4607e 100644
--- a/libc/calls/struct/metastat.h
+++ b/libc/calls/struct/metastat.h
@@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_METASTAT_H_
#define COSMOPOLITAN_LIBC_CALLS_STRUCT_METASTAT_H_
+#ifndef __STRICT_ANSI__
#include "libc/calls/struct/stat.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@@ -52,4 +53,5 @@ union metastat {
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_METASTAT_H_ */
diff --git a/libc/calls/struct/metatermios.h b/libc/calls/struct/metatermios.h
index dcde41a4e..f78b52b56 100644
--- a/libc/calls/struct/metatermios.h
+++ b/libc/calls/struct/metatermios.h
@@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_METATERMIOS_H_
#define COSMOPOLITAN_LIBC_CALLS_STRUCT_METATERMIOS_H_
+#ifndef __STRICT_ANSI__
#include "libc/calls/struct/termios.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@@ -32,4 +33,5 @@ union metatermios {
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_METATERMIOS_H_ */
diff --git a/libc/calls/struct/sigaction-freebsd.h b/libc/calls/struct/sigaction-freebsd.h
new file mode 100644
index 000000000..aac23d131
--- /dev/null
+++ b/libc/calls/struct/sigaction-freebsd.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_FREEBSD_H_
+#define COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_FREEBSD_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+
+struct sigaction$freebsd {
+ intptr_t sa_handler;
+ uint32_t sa_flags;
+ struct sigset$freebsd {
+ uint32_t sig[4];
+ } sa_mask;
+};
+
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_FREEBSD_H_ */
diff --git a/libc/calls/struct/sigaction-linux.h b/libc/calls/struct/sigaction-linux.h
new file mode 100644
index 000000000..5a4e3f93b
--- /dev/null
+++ b/libc/calls/struct/sigaction-linux.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_LINUX_H_
+#define COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_LINUX_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+
+struct sigaction$linux {
+ intptr_t sa_handler;
+ uint64_t sa_flags;
+ void (*sa_restorer)(void);
+ struct sigset$linux {
+ uint32_t sig[2];
+ } sa_mask;
+};
+
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_LINUX_H_ */
diff --git a/libc/calls/struct/sigaction-openbsd.h b/libc/calls/struct/sigaction-openbsd.h
new file mode 100644
index 000000000..47c629ea5
--- /dev/null
+++ b/libc/calls/struct/sigaction-openbsd.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_OPENBSD_H_
+#define COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_OPENBSD_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+
+struct sigaction$openbsd {
+ intptr_t sa_handler;
+ struct sigset$openbsd {
+ uint32_t sig[1];
+ } sa_mask;
+ int32_t sa_flags;
+};
+
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_OPENBSD_H_ */
diff --git a/libc/calls/struct/sigaction-xnu.h b/libc/calls/struct/sigaction-xnu.h
new file mode 100644
index 000000000..e83bac80e
--- /dev/null
+++ b/libc/calls/struct/sigaction-xnu.h
@@ -0,0 +1,28 @@
+#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_XNU_H_
+#define COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_XNU_H_
+#include "libc/calls/internal.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+
+struct __darwin_ucontext;
+struct __darwin_siginfo;
+
+struct sigset$xnu {
+ uint32_t sig[1];
+};
+
+struct sigaction$xnu_in {
+ intptr_t sa_handler;
+ void (*sa_restorer)(void *, int, int, const struct __darwin_siginfo *,
+ const struct __darwin_ucontext *);
+ struct sigset$xnu sa_mask;
+ int32_t sa_flags;
+};
+
+struct sigaction$xnu_out {
+ intptr_t sa_handler;
+ struct sigset$xnu sa_mask;
+ int32_t sa_flags;
+};
+
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_XNU_H_ */
diff --git a/libc/calls/struct/sigaltstack.h b/libc/calls/struct/sigaltstack.h
index fa11f4604..766cdfca5 100644
--- a/libc/calls/struct/sigaltstack.h
+++ b/libc/calls/struct/sigaltstack.h
@@ -10,7 +10,5 @@ struct sigaltstack {
typedef struct sigaltstack stack_t;
-static_assert(sizeof(stack_t) == 24);
-
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGALTSTACK_H_ */
diff --git a/libc/calls/struct/sigset.h b/libc/calls/struct/sigset.h
index bd076e48f..b46b60152 100644
--- a/libc/calls/struct/sigset.h
+++ b/libc/calls/struct/sigset.h
@@ -2,7 +2,7 @@
#define COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGSET_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
-struct sigset {
+struct sigset { /* cosmo abi (linux is uint64_t) */
uint32_t sig[4]; /* ignore sig[2] and sig[3] (for freebsd) */
} aligned(8);
diff --git a/libc/calls/termios-internal.h b/libc/calls/termios-internal.h
index 6bbb2af5d..2b468576c 100644
--- a/libc/calls/termios-internal.h
+++ b/libc/calls/termios-internal.h
@@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_TERMIOS_INTERNAL_H_
#define COSMOPOLITAN_LIBC_CALLS_TERMIOS_INTERNAL_H_
+#ifndef __STRICT_ANSI__
#include "libc/bits/safemacros.h"
#include "libc/str/str.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
@@ -26,4 +27,5 @@ void termios2linux(struct termios *, const union metatermios *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_CALLS_TERMIOS_INTERNAL_H_ */
diff --git a/libc/calls/termios.h b/libc/calls/termios.h
index b02ff6463..029a232d2 100644
--- a/libc/calls/termios.h
+++ b/libc/calls/termios.h
@@ -12,10 +12,10 @@ COSMOPOLITAN_C_START_
│ cosmopolitan § teletypewriter control ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│*/
-int tcgetattr(int fd, struct termios *tio);
-int tcsetattr(int fd, int opt, const struct termios *tio);
-int tcsetpgrp(int fd, int32_t pgrp);
-int32_t tcgetpgrp(int fd);
+int tcgetattr(int, struct termios *);
+int tcsetattr(int, int, const struct termios *);
+int tcsetpgrp(int, int32_t);
+int32_t tcgetpgrp(int);
int openpty(int *, int *, char *, const struct termios *,
const struct winsize *) paramsnonnull((1, 2)) nodiscard;
diff --git a/libc/calls/truncate.c b/libc/calls/truncate.c
index 9f444441e..087bde3c6 100644
--- a/libc/calls/truncate.c
+++ b/libc/calls/truncate.c
@@ -20,6 +20,7 @@
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/dce.h"
+#include "libc/sysv/errfuns.h"
/**
* Reduces or extends underlying physical medium of file.
@@ -32,6 +33,7 @@
* @error ENOENT
*/
int truncate(const char *path, uint64_t length) {
+ if (!path) return efault();
if (!IsWindows()) {
return truncate$sysv(path, length);
} else {
diff --git a/libc/calls/ucontext.h b/libc/calls/ucontext.h
index 8345b09d3..2af25a1e2 100644
--- a/libc/calls/ucontext.h
+++ b/libc/calls/ucontext.h
@@ -6,29 +6,29 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
-#define REG_R8 REG_R8
-#define REG_R9 REG_R9
-#define REG_R10 REG_R10
-#define REG_R11 REG_R11
-#define REG_R12 REG_R12
-#define REG_R13 REG_R13
-#define REG_R14 REG_R14
-#define REG_R15 REG_R15
-#define REG_RDI REG_RDI
-#define REG_RSI REG_RSI
-#define REG_RBP REG_RBP
-#define REG_RBX REG_RBX
-#define REG_RDX REG_RDX
-#define REG_RAX REG_RAX
-#define REG_RCX REG_RCX
-#define REG_RSP REG_RSP
-#define REG_RIP REG_RIP
-#define REG_EFL REG_EFL
-#define REG_CSGSFS REG_CSGSFS
-#define REG_ERR REG_ERR
-#define REG_TRAPNO REG_TRAPNO
+#define REG_R8 REG_R8
+#define REG_R9 REG_R9
+#define REG_R10 REG_R10
+#define REG_R11 REG_R11
+#define REG_R12 REG_R12
+#define REG_R13 REG_R13
+#define REG_R14 REG_R14
+#define REG_R15 REG_R15
+#define REG_RDI REG_RDI
+#define REG_RSI REG_RSI
+#define REG_RBP REG_RBP
+#define REG_RBX REG_RBX
+#define REG_RDX REG_RDX
+#define REG_RAX REG_RAX
+#define REG_RCX REG_RCX
+#define REG_RSP REG_RSP
+#define REG_RIP REG_RIP
+#define REG_EFL REG_EFL
+#define REG_CSGSFS REG_CSGSFS
+#define REG_ERR REG_ERR
+#define REG_TRAPNO REG_TRAPNO
#define REG_OLDMASK REG_OLDMASK
-#define REG_CR2 REG_CR2
+#define REG_CR2 REG_CR2
enum GeneralRegister {
REG_R8,
@@ -60,16 +60,12 @@ struct XmmRegister {
uint64_t u64[2];
};
-static_assert(sizeof(struct XmmRegister) == 16);
-
struct FpuStackEntry {
uint16_t significand[4];
uint16_t exponent;
uint16_t padding[3];
};
-static_assert(sizeof(struct FpuStackEntry) == 16);
-
struct FpuState {
uint16_t cwd;
uint16_t swd;
diff --git a/libc/calls/unlink.c b/libc/calls/unlink.c
index 1a5409989..8ae88b6a8 100644
--- a/libc/calls/unlink.c
+++ b/libc/calls/unlink.c
@@ -17,12 +17,12 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/internal.h"
#include "libc/dce.h"
#include "libc/nt/files.h"
#include "libc/str/str.h"
-#include "libc/calls/internal.h"
-#include "libc/sysv/errfuns.h"
#include "libc/sysv/consts/at.h"
+#include "libc/sysv/errfuns.h"
/**
* Deletes file.
@@ -36,7 +36,7 @@
* @asyncsignalsafe
*/
int unlink(const char *name) {
- if (!name) return 0;
+ if (!name) return efault();
if (!IsWindows()) {
return unlinkat$sysv(AT_FDCWD, name, 0);
} else {
diff --git a/libc/calls/unlink_s.c b/libc/calls/unlink_s.c
index 406e1a3eb..ea9f54644 100644
--- a/libc/calls/unlink_s.c
+++ b/libc/calls/unlink_s.c
@@ -19,7 +19,6 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/bits.h"
#include "libc/calls/calls.h"
-#include "libc/runtime/mappings.h"
/**
* Deletes file, the Cosmopolitan way.
diff --git a/libc/calls/unlinkat.c b/libc/calls/unlinkat.c
new file mode 100644
index 000000000..a3fd6f250
--- /dev/null
+++ b/libc/calls/unlinkat.c
@@ -0,0 +1,30 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/calls.h"
+#include "libc/calls/internal.h"
+#include "libc/sysv/consts/at.h"
+
+int unlinkat(int dirfd, const char *pathname, int flags) {
+ if (dirfd == AT_FDCWD) {
+ return unlink(pathname);
+ } else {
+ return unlinkat$sysv(dirfd, pathname, flags);
+ }
+}
diff --git a/libc/calls/vdprintf.c b/libc/calls/vdprintf.c
index 962034e2b..ff6dfe713 100644
--- a/libc/calls/vdprintf.c
+++ b/libc/calls/vdprintf.c
@@ -21,45 +21,46 @@
#include "libc/dce.h"
#include "libc/fmt/fmt.h"
#include "libc/limits.h"
+#include "libc/macros.h"
#include "libc/nt/files.h"
#include "libc/sysv/errfuns.h"
-#define DBUFSIZ 1460 /* tcp ethernet frame < -Wframe-larger-than=4096 */
-
-struct dfile {
+struct VdprintfState {
+ int n;
int fd;
- unsigned idx;
- unsigned toto;
- unsigned char buf[DBUFSIZ];
+ unsigned char buf[1024];
};
-static int vdprintf_flush(struct dfile *df) {
- ssize_t wrote;
- do {
- wrote = write(df->fd, &df->buf[0], df->idx);
- if (wrote == -1) return -1;
- df->toto += (unsigned)wrote;
- df->idx -= (unsigned)wrote;
- if (df->toto > INT_MAX) return eoverflow();
- } while (df->idx);
+static int vdprintf_flush(struct VdprintfState *df, int n) {
+ int i, rc;
+ for (i = 0; i < n; i += rc) {
+ if ((rc = write(df->fd, df->buf + i, n - i)) == -1) {
+ return -1;
+ }
+ }
return 0;
}
-static int vdprintfputchar(unsigned char c, struct dfile *df) {
- df->buf[df->idx++] = c;
- if (df->idx == DBUFSIZ && vdprintf_flush(df) == -1) return -1;
- return 0;
+static int vdprintfputchar(int c, struct VdprintfState *df) {
+ df->buf[df->n++ & (ARRAYLEN(df->buf) - 1)] = c & 0xff;
+ if ((df->n & (ARRAYLEN(df->buf) - 1))) {
+ return 0;
+ } else {
+ return vdprintf_flush(df, ARRAYLEN(df->buf));
+ }
}
/**
* Formats string directly to system i/o device.
*/
int(vdprintf)(int fd, const char *fmt, va_list va) {
- struct dfile df;
+ struct VdprintfState df;
+ df.n = 0;
df.fd = fd;
- df.idx = 0;
- df.toto = 0;
- if (palandprintf(vdprintfputchar, &df, fmt, va) == -1) return -1;
- if (df.idx && vdprintf_flush(&df) == -1) return -1;
- return df.toto;
+ if (palandprintf(vdprintfputchar, &df, fmt, va) != -1 ||
+ vdprintf_flush(&df, df.n & (ARRAYLEN(df.buf) - 1)) != -1) {
+ return df.n;
+ } else {
+ return -1;
+ }
}
diff --git a/libc/calls/write.c b/libc/calls/write.c
index 5628f1f04..a52fc22f8 100644
--- a/libc/calls/write.c
+++ b/libc/calls/write.c
@@ -18,7 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
-#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/iovec.h"
diff --git a/libc/calls/writev.c b/libc/calls/writev.c
index 4aceaf315..1a61dd848 100644
--- a/libc/calls/writev.c
+++ b/libc/calls/writev.c
@@ -17,6 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/sock/internal.h"
diff --git a/libc/complex.h b/libc/complex.h
index a804cdd21..cbb98cdbb 100644
--- a/libc/complex.h
+++ b/libc/complex.h
@@ -2,10 +2,26 @@
#define COSMOPOLITAN_LIBC_COMPLEX_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
+#if __STDC_VERSION__ + 0 >= 201112
#define complex _Complex
#define imaginary _Imaginary
+double cabs(complex double);
+double carg(complex double);
+double cimag(complex double);
+double creal(complex double);
+
+float cabsf(complex float);
+float cargf(complex float);
+float cimagf(complex float);
+float crealf(complex float);
+
+long double cabsl(complex long double);
+long double cargl(complex long double);
+long double cimagl(complex long double);
+long double creall(complex long double);
+
complex double cacos(complex double);
complex double cacosh(complex double);
complex double casin(complex double);
@@ -46,21 +62,6 @@ complex float csqrtf(complex float);
complex float ctanf(complex float);
complex float ctanhf(complex float);
-double cabs(complex double);
-double carg(complex double);
-double cimag(complex double);
-double creal(complex double);
-
-float cabsf(complex float);
-float cargf(complex float);
-float cimagf(complex float);
-float crealf(complex float);
-
-long double cabsl(complex long double);
-long double cargl(complex long double);
-long double cimagl(complex long double);
-long double creall(complex long double);
-
complex long double cprojl(complex long double);
complex long double csinhl(complex long double);
complex long double csinl(complex long double);
@@ -81,6 +82,7 @@ complex long double clogl(complex long double);
complex long double conjl(complex long double);
complex long double cpowl(complex long double, complex long double);
+#endif /* C11 */
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_COMPLEX_H_ */
diff --git a/libc/conv/basename.c b/libc/conv/basename.c
index da11e0cac..df16bb812 100644
--- a/libc/conv/basename.c
+++ b/libc/conv/basename.c
@@ -31,4 +31,6 @@
* @param path is NUL-terminated UTF-8 path
* @return pointer inside path or path itself
*/
-char *basename(const char *path) { return basename_n(path, strlen(path)); }
+textstartup char *basename(const char *path) {
+ return basename_n(path, strlen(path));
+}
diff --git a/libc/conv/basename_n.c b/libc/conv/basename_n.c
index 07266275a..544507d38 100644
--- a/libc/conv/basename_n.c
+++ b/libc/conv/basename_n.c
@@ -31,7 +31,7 @@
* @param size is byte length of path
* @return pointer inside path or path itself
*/
-char *basename_n(const char *path, size_t size) {
+textstartup char *basename_n(const char *path, size_t size) {
size_t i, l;
if (size) {
if (isslash(path[size - 1])) {
diff --git a/libc/conv/conv.h b/libc/conv/conv.h
index 6b8c5ae35..713ae0b20 100644
--- a/libc/conv/conv.h
+++ b/libc/conv/conv.h
@@ -1,5 +1,8 @@
#ifndef COSMOPOLITAN_LIBC_CONV_CONV_H_
#define COSMOPOLITAN_LIBC_CONV_CONV_H_
+#include "libc/calls/struct/timespec.h"
+#include "libc/calls/struct/timeval.h"
+#include "libc/nt/struct/filetime.h"
/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § conversion ─╬─│┼
@@ -23,6 +26,7 @@ long labs(long) libcesque pureconst;
long long llabs(long long) libcesque pureconst;
char *ltpcpy(char *, long) paramsnonnull() libcesque nocallback;
int llog10(unsigned long) libcesque pureconst;
+int unsleb128(const void *, size_t, int64_t *);
int atoi(const char *) paramsnonnull() libcesque nosideeffect;
long atol(const char *) paramsnonnull() libcesque nosideeffect;
long long atoll(const char *) paramsnonnull() libcesque nosideeffect;
@@ -38,18 +42,12 @@ long wcstol(const wchar_t *, wchar_t **, int);
long strtol(const char *, char **, int)
paramsnonnull((1)) libcesque nosideeffect;
-intmax_t __imaxabs(intmax_t) asm("imaxabs") libcesque pureconst;
-#define imaxabs(x) __imaxabs(x)
-
/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § conversion » time ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│*/
-struct NtFileTime;
-struct timespec;
-struct timeval;
-
-void filetimetotimespec(struct timespec *, struct NtFileTime) paramsnonnull();
+struct timespec filetimetotimespec(struct NtFileTime);
+struct NtFileTime timespectofiletime(struct timespec);
struct NtFileTime timetofiletime(int64_t) nothrow pureconst;
int64_t filetimetotime(struct NtFileTime) nothrow pureconst;
void filetimetotimeval(struct timeval *, struct NtFileTime) nothrow;
@@ -104,6 +102,11 @@ double RoundDecimalPlaces(double, double, double(double));
#define lldiv(num, den) ((lldiv_t){(num) / (den), (num) % (den)})
#endif
+#ifndef __STRICT_ANSI__
+intmax_t __imaxabs(intmax_t) asm("imaxabs") libcesque pureconst;
+#define imaxabs(x) __imaxabs(x)
+#endif /* !ANSI */
+
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CONV_CONV_H_ */
diff --git a/libc/conv/conv.mk b/libc/conv/conv.mk
index 9d1155269..5365f94eb 100644
--- a/libc/conv/conv.mk
+++ b/libc/conv/conv.mk
@@ -38,7 +38,8 @@ LIBC_CONV_A_DIRECTDEPS = \
LIBC_STUBS \
LIBC_NEXGEN32E \
LIBC_TINYMATH \
- LIBC_SYSV
+ LIBC_SYSV \
+ THIRD_PARTY_COMPILER_RT
LIBC_CONV_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_CONV_A_DIRECTDEPS),$($(x))))
@@ -51,12 +52,10 @@ $(LIBC_CONV_A).pkg: \
$(LIBC_CONV_A_OBJS) \
$(foreach x,$(LIBC_CONV_A_DIRECTDEPS),$($(x)_A).pkg)
-#o/$(MODE)/libc/conv/strtoimax.o: CC = clang-10
-#o/$(MODE)/libc/conv/strtoumax.o: CC = clang-10
-
-o/$(MODE)/libc/conv/itoa64radix10.o \
+o/$(MODE)/libc/conv/itoa64radix10.greg.o \
o/$(MODE)/libc/conv/timetofiletime.o \
o/$(MODE)/libc/conv/filetimetotime.o \
+o/$(MODE)/libc/conv/timespectofiletime.o \
o/$(MODE)/libc/conv/filetimetotimespec.o \
o/$(MODE)/libc/conv/filetimetotimeval.o: \
OVERRIDE_COPTS += \
diff --git a/libc/conv/filetimetotimespec.c b/libc/conv/filetimetotimespec.c
index c6a7d8a45..f1f79689f 100644
--- a/libc/conv/filetimetotimespec.c
+++ b/libc/conv/filetimetotimespec.c
@@ -24,9 +24,11 @@
/**
* Converts Windows COBOL timestamp to UNIX epoch in nanoseconds.
*/
-void filetimetotimespec(struct timespec *ts, struct NtFileTime ft) {
- uint64_t t = (uint64_t)ft.dwHighDateTime << 32 | ft.dwLowDateTime;
- uint64_t x = t - MODERNITYSECONDS * HECTONANOSECONDS;
- ts->tv_sec = x / HECTONANOSECONDS;
- ts->tv_nsec = x % HECTONANOSECONDS * 100;
+struct timespec filetimetotimespec(struct NtFileTime ft) {
+ uint64_t x;
+ x = ft.dwHighDateTime;
+ x <<= 32;
+ x |= ft.dwLowDateTime;
+ x -= MODERNITYSECONDS;
+ return (struct timespec){x / HECTONANOSECONDS, x % HECTONANOSECONDS * 100};
}
diff --git a/libc/conv/itoa.h b/libc/conv/itoa.h
index 437c1b57c..66de99125 100644
--- a/libc/conv/itoa.h
+++ b/libc/conv/itoa.h
@@ -23,13 +23,17 @@ COSMOPOLITAN_C_START_
*/
-size_t int128toarray_radix10(int128_t, char *);
-size_t uint128toarray_radix10(uint128_t, char *);
size_t int64toarray_radix10(int64_t, char *);
size_t uint64toarray_radix10(uint64_t, char *);
size_t int64toarray(int64_t, char *, int);
size_t uint64toarray(uint64_t, char *, int);
size_t uint64toarray_radix16(uint64_t, char[hasatleast 17]);
+size_t uint64toarray_fixed16(uint64_t, char[hasatleast 17], uint8_t);
+
+#ifndef __STRICT_ANSI__
+size_t int128toarray_radix10(int128_t, char *);
+size_t uint128toarray_radix10(uint128_t, char *);
+#endif
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/conv/itoa128radix10.greg.c b/libc/conv/itoa128radix10.greg.c
index 46033b806..65b222623 100644
--- a/libc/conv/itoa128radix10.greg.c
+++ b/libc/conv/itoa128radix10.greg.c
@@ -20,13 +20,21 @@
#include "libc/alg/reverse.h"
#include "libc/conv/conv.h"
#include "libc/conv/itoa.h"
+#include "libc/limits.h"
+uint128_t __udivmodti4(uint128_t, uint128_t, uint128_t *);
+
+/**
+ * Converts unsigned 128-bit integer to string.
+ * @param a needs at least 40 bytes
+ * @return bytes written w/o nul
+ */
noinline size_t uint128toarray_radix10(uint128_t i, char *a) {
size_t j;
- unsigned rem;
+ uint128_t rem;
j = 0;
do {
- i = div10(i, &rem);
+ i = __udivmodti4(i, 10, &rem);
a[j++] = rem + '0';
} while (i > 0);
a[j] = '\0';
@@ -34,10 +42,21 @@ noinline size_t uint128toarray_radix10(uint128_t i, char *a) {
return j;
}
+/**
+ * Converts signed 128-bit integer to string.
+ * @param a needs at least 41 bytes
+ * @return bytes written w/o nul
+ */
size_t int128toarray_radix10(int128_t i, char *a) {
if (i < 0) {
- *a++ = '-';
- i = -i;
+ if (i != INT128_MIN) {
+ *a++ = '-';
+ return 1 + uint128toarray_radix10(-i, a);
+ } else {
+ memcpy(a, "-170141183460469231731687303715884105728", 41);
+ return 40;
+ }
+ } else {
+ return uint128toarray_radix10(i, a);
}
- return uint128toarray_radix10(i, a);
}
diff --git a/libc/conv/itoa64fixed16.greg.c b/libc/conv/itoa64fixed16.greg.c
new file mode 100644
index 000000000..f88b25acf
--- /dev/null
+++ b/libc/conv/itoa64fixed16.greg.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/alg/reverse.h"
+#include "libc/assert.h"
+#include "libc/conv/itoa.h"
+
+size_t uint64toarray_fixed16(uint64_t i, char a[hasatleast 17], uint8_t b) {
+ size_t j;
+ assert(b <= 64);
+ assert(b % 4 == 0);
+ j = 0;
+ if (b) {
+ do {
+ a[j++] = "0123456789abcdef"[i & 15];
+ i >>= 4;
+ } while (b -= 4);
+ }
+ a[j] = '\0';
+ reverse(a, j);
+ return j;
+}
diff --git a/libc/conv/itoa64radix10.greg.c b/libc/conv/itoa64radix10.greg.c
index 37e7b6ff0..d7d39bb7c 100644
--- a/libc/conv/itoa64radix10.greg.c
+++ b/libc/conv/itoa64radix10.greg.c
@@ -20,7 +20,13 @@
#include "libc/alg/reverse.h"
#include "libc/conv/conv.h"
#include "libc/conv/itoa.h"
+#include "libc/limits.h"
+/**
+ * Converts unsigned 64-bit integer to string.
+ * @param a needs at least 21 bytes
+ * @return bytes written w/o nul
+ */
noinline size_t uint64toarray_radix10(uint64_t i, char *a) {
size_t j;
j = 0;
@@ -33,10 +39,21 @@ noinline size_t uint64toarray_radix10(uint64_t i, char *a) {
return j;
}
+/**
+ * Converts signed 64-bit integer to string.
+ * @param a needs at least 21 bytes
+ * @return bytes written w/o nul
+ */
size_t int64toarray_radix10(int64_t i, char *a) {
if (i < 0) {
- *a++ = '-';
- i = -i;
+ if (i != INT64_MIN) {
+ *a++ = '-';
+ return 1 + uint64toarray_radix10(-i, a);
+ } else {
+ memcpy(a, "-9223372036854775808", 21);
+ return 20;
+ }
+ } else {
+ return uint64toarray_radix10(i, a);
}
- return uint64toarray_radix10(i, a);
}
diff --git a/libc/conv/itoa64radix16.greg.c b/libc/conv/itoa64radix16.greg.c
index 56e5ed1be..a01d5919f 100644
--- a/libc/conv/itoa64radix16.greg.c
+++ b/libc/conv/itoa64radix16.greg.c
@@ -22,11 +22,9 @@
size_t uint64toarray_radix16(uint64_t i, char a[hasatleast 17]) {
size_t j;
- unsigned char d;
j = 0;
do {
- d = i % 16;
- a[j++] = d < 10 ? d + '0' : d + 'a';
+ a[j++] = "0123456789abcdef"[i % 16];
i /= 16;
} while (i > 0);
a[j] = '\0';
diff --git a/libc/conv/strtoimax.c b/libc/conv/strtoimax.c
index a4efc27e9..877a8e765 100644
--- a/libc/conv/strtoimax.c
+++ b/libc/conv/strtoimax.c
@@ -76,6 +76,10 @@ intmax_t strtoimax(const char *s, char **endptr, int base) {
} else {
base = 10;
}
+ } else if (*s == '0') {
+ ++s;
+ if (base == 2 && *s == 'b' && *s == 'B') ++s;
+ if (base == 16 && *s == 'x' && *s == 'X') ++s;
}
for (;;) {
diff --git a/libc/conv/strtoumax.c b/libc/conv/strtoumax.c
index da06a9473..2040aeaa9 100644
--- a/libc/conv/strtoumax.c
+++ b/libc/conv/strtoumax.c
@@ -28,7 +28,10 @@
*/
uintmax_t strtoumax(const char *s, char **endptr, int base) {
const unsigned char *p = (const unsigned char *)s;
- uintmax_t res = 0;
+ unsigned diglet;
+ uintmax_t res;
+
+ res = 0;
while (isspace(*p)) {
p++;
@@ -49,10 +52,14 @@ uintmax_t strtoumax(const char *s, char **endptr, int base) {
} else {
base = 10;
}
+ } else if (*s == '0') {
+ ++s;
+ if (base == 2 && *s == 'b' && *s == 'B') ++s;
+ if (base == 16 && *s == 'x' && *s == 'X') ++s;
}
for (;;) {
- unsigned diglet = kBase36[*p];
+ diglet = kBase36[*p];
if (!diglet || diglet > base) break;
p++;
res *= base;
diff --git a/libc/conv/timespectofiletime.c b/libc/conv/timespectofiletime.c
new file mode 100644
index 000000000..4d3dc8f5a
--- /dev/null
+++ b/libc/conv/timespectofiletime.c
@@ -0,0 +1,34 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/calls.h"
+#include "libc/conv/conv.h"
+#include "libc/nexgen32e/nexgen32e.h"
+#include "libc/nt/struct/filetime.h"
+
+/**
+ * Converts UNIX nanosecond timestamp to Windows COBOL timestamp.
+ */
+struct NtFileTime timespectofiletime(struct timespec ts) {
+ uint64_t x;
+ x = MODERNITYSECONDS;
+ x += ts.tv_sec * HECTONANOSECONDS;
+ x += div100int64(ts.tv_nsec);
+ return (struct NtFileTime){x, x >> 32};
+}
diff --git a/libc/conv/unsleb128.c b/libc/conv/unsleb128.c
new file mode 100644
index 000000000..e52fd41fb
--- /dev/null
+++ b/libc/conv/unsleb128.c
@@ -0,0 +1,45 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/conv/conv.h"
+
+/**
+ * Decodes a GNU-style varint from a buffer.
+ *
+ * The GNU Assembler is able to encode numbers this way, since it's used
+ * by the DWARF debug format.
+ */
+int unsleb128(const void *buf, size_t size, int64_t *out) {
+ int b;
+ int64_t r, w;
+ unsigned char c;
+ const unsigned char *p, *pe;
+ pe = (p = buf) + size;
+ r = b = 0;
+ do {
+ if (size && p == pe) return -1;
+ c = *p++;
+ w = c & 0x7f;
+ r |= w << b;
+ b += 7;
+ } while (c & 0x80);
+ if (c & 0x40) r |= -1ull << b;
+ if (out) *out = r;
+ return p - (const unsigned char *)buf;
+}
diff --git a/libc/crt/crt.S b/libc/crt/crt.S
index b349d1468..6f490515e 100644
--- a/libc/crt/crt.S
+++ b/libc/crt/crt.S
@@ -24,21 +24,22 @@
.section .start,"ax",@progbits
.source __FILE__
+ nop
+
/ System Five userspace program entrypoint.
/
/ @param rsp is [n,argv₀..argvₙ₋₁,0,envp₀..,0,auxv₀..,0,..]
/ @note FreeBSD is special (see freebsd/lib/csu/amd64/...)
/ @noreturn
-_start_xnu:
- movb $XNU,hostos(%rip)
- jmp 0f
_start: test %rdi,%rdi
cmovnz %rdi,%rsp
jz 0f
movb $FREEBSD,hostos(%rip)
-0: movslq (%rsp),%r12 # argc
- lea 8(%rsp),%r13 # argv
- lea 24(%rsp,%r12,8),%r14 # envp
+0: mov (%rsp),%ebx # argc
+ lea 8(%rsp),%rsi # argv
+ lea 24(%rsp,%rbx,8),%rdx # envp
+ .frame0
+ bofram 9f
.weak idata.iat,idata.iatend
ezlea missingno,ax # make win32 imps noop
ezlea idata.iat,di
@@ -48,9 +49,21 @@ _start: test %rdi,%rdi
rep stosq
xor %eax,%eax # find end of environ
or $-1,%ecx
- mov %r14,%rdi
+ mov %rdx,%rdi
repnz scasq
- mov %rdi,%r15 # auxv
- jmp __executive
- .endfn _start,weak,hidden
+ mov %rdi,%rcx # auxv
+ mov %ebx,%edi
+ call _executive
+9: .endfn _start,weak,hidden
+
+ ud2
+
+/ Macintosh userspace program entrypoint.
+/
+/ @param rsp is [n,argv₀..argvₙ₋₁,0,envp₀..,0,auxv₀..,0,..]
+/ @note FreeBSD is special (see freebsd/lib/csu/amd64/...)
+/ @noreturn
+_start_xnu:
+ movb $XNU,hostos(%rip)
+ jmp 0b
.endfn _start_xnu,weak,hidden
diff --git a/libc/crypto/rijndael.h b/libc/crypto/rijndael.h
index 09c63091b..87a6236f3 100644
--- a/libc/crypto/rijndael.h
+++ b/libc/crypto/rijndael.h
@@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_CRYPTO_RIJNDAEL_H_
#define COSMOPOLITAN_LIBC_CRYPTO_RIJNDAEL_H_
+#ifndef __STRICT_ANSI__
#include "libc/str/str.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@@ -46,11 +47,12 @@ aes_block_t unrijndael(uint32_t, aes_block_t, const struct Rijndael *);
│ cosmopolitan § cryptography » implementation details ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│*/
-aligned(64) extern const uint8_t kAesSbox[256];
-aligned(64) extern const uint8_t kAesSboxInverse[256];
+extern const uint8_t kAesSbox[256] aligned(64);
+extern const uint8_t kAesSboxInverse[256] aligned(64);
aes_block_t InvMixColumns(aes_block_t) hidden;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_CRYPTO_RIJNDAEL_H_ */
diff --git a/libc/dce.h b/libc/dce.h
index 557473934..606d5c11b 100644
--- a/libc/dce.h
+++ b/libc/dce.h
@@ -17,6 +17,7 @@
* offer marginal improvements in terms of code size and performance, at
* the cost of portability.
*/
+#define HOSTOS (hostos & SUPPORT_VECTOR)
#ifndef SUPPORT_VECTOR
#define SUPPORT_VECTOR 0b11111111
#endif
@@ -93,13 +94,13 @@
((SUPPORT_VECTOR & (LINUX | METAL | XNU | OPENBSD | FREEBSD)) != 0)
#ifndef __ASSEMBLER__
-#define IsLinux() ((hostos & LINUX) == LINUX)
-#define IsMetal() ((hostos & METAL) == METAL)
-#define IsWindows() ((hostos & WINDOWS) == WINDOWS)
-#define IsBsd() ((hostos & (XNU | FREEBSD | OPENBSD)) != 0)
-#define IsXnu() ((hostos & XNU) == XNU)
-#define IsFreebsd() ((hostos & FREEBSD) == FREEBSD)
-#define IsOpenbsd() ((hostos & OPENBSD) == OPENBSD)
+#define IsLinux() ((HOSTOS & LINUX) == LINUX)
+#define IsMetal() ((HOSTOS & METAL) == METAL)
+#define IsWindows() ((HOSTOS & WINDOWS) == WINDOWS)
+#define IsBsd() ((HOSTOS & (XNU | FREEBSD | OPENBSD)) != 0)
+#define IsXnu() ((HOSTOS & XNU) == XNU)
+#define IsFreebsd() ((HOSTOS & FREEBSD) == FREEBSD)
+#define IsOpenbsd() ((HOSTOS & OPENBSD) == OPENBSD)
#else
/* clang-format off */
#define IsLinux() $LINUX,hostos(%rip)
@@ -115,8 +116,7 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
-#define hostos (__hostos & SUPPORT_VECTOR)
-extern const int __hostos asm("hostos");
+extern const int hostos;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/dns/dnsquestion.h b/libc/dns/dnsquestion.h
index d6dd7e4f0..bb9161397 100644
--- a/libc/dns/dnsquestion.h
+++ b/libc/dns/dnsquestion.h
@@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_DNS_DNSQUESTION_H_
#define COSMOPOLITAN_LIBC_DNS_DNSQUESTION_H_
+#ifndef __STRICT_ANSI__
#include "libc/dns/dns.h"
#include "libc/sysv/errfuns.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
@@ -29,4 +30,5 @@ forceinline int serializednsquestion(uint8_t *buf, size_t size,
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_DNS_DNSQUESTION_H_ */
diff --git a/libc/dns/getaddrinfo.c b/libc/dns/getaddrinfo.c
index 77ca0cbe4..94ae35416 100644
--- a/libc/dns/getaddrinfo.c
+++ b/libc/dns/getaddrinfo.c
@@ -18,6 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/safemacros.h"
+#include "libc/calls/calls.h"
#include "libc/conv/conv.h"
#include "libc/dns/dns.h"
#include "libc/dns/hoststxt.h"
@@ -25,12 +26,11 @@
#include "libc/mem/mem.h"
#include "libc/sock/sock.h"
#include "libc/str/str.h"
-#include "libc/calls/calls.h"
-#include "libc/sysv/errfuns.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/ai.h"
#include "libc/sysv/consts/eai.h"
#include "libc/sysv/consts/inaddr.h"
+#include "libc/sysv/errfuns.h"
/**
* Resolves address for internet name.
@@ -44,20 +44,15 @@
*/
int getaddrinfo(const char *name, const char *service,
const struct addrinfo *hints, struct addrinfo **res) {
- int port = 0;
+ int rc, port;
+ const char *canon;
struct addrinfo *ai;
- if ((!name && !service) || (service && (port = parseport(service)) == -1)) {
- return EAI_NONAME;
- }
- if (!name && (hints->ai_flags & AI_CANONNAME) == AI_CANONNAME) {
- return EAI_BADFLAGS;
- }
- if (!(ai = newaddrinfo(port))) {
- return EAI_MEMORY;
- }
- if (service) {
- ai->ai_addr4->sin_port = htons(port);
- }
+ port = 0;
+ if (!name && !service) return EAI_NONAME;
+ if (service && (port = parseport(service)) == -1) return EAI_NONAME;
+ if (!name && (hints->ai_flags & AI_CANONNAME)) return EAI_BADFLAGS;
+ if (!(ai = newaddrinfo(port))) return EAI_MEMORY;
+ if (service) ai->ai_addr4->sin_port = htons(port);
if (hints) {
ai->ai_socktype = hints->ai_socktype;
ai->ai_protocol = hints->ai_protocol;
@@ -69,7 +64,6 @@ int getaddrinfo(const char *name, const char *service,
: INADDR_LOOPBACK;
return 0;
}
- const char *canon;
if (inet_pton(AF_INET, name, &ai->ai_addr4->sin_addr.s_addr) == 1) {
*res = ai;
return 0;
@@ -82,8 +76,8 @@ int getaddrinfo(const char *name, const char *service,
*res = ai;
return 0;
} else {
- int rc = resolvedns(getresolvconf(), AF_INET, name, ai->ai_addr,
- sizeof(ai->ai_addr4));
+ rc = resolvedns(getresolvconf(), AF_INET, name, ai->ai_addr,
+ sizeof(ai->ai_addr4));
if (rc > 0) {
*res = ai;
return 0;
diff --git a/libc/dns/getresolvconf.c b/libc/dns/getresolvconf.c
index 38b8b9883..c6dafb9b5 100644
--- a/libc/dns/getresolvconf.c
+++ b/libc/dns/getresolvconf.c
@@ -36,15 +36,15 @@ static struct ResolvConfInitialStaticMemory {
* Returns singleton with DNS server address.
*/
const struct ResolvConf *getresolvconf(void) {
+ int rc;
+ FILE *f;
struct ResolvConfInitialStaticMemory *init = &g_resolvconf_init;
if (!g_resolvconf) {
g_resolvconf = &init->rv;
pushmov(&init->rv.nameservers.n, ARRAYLEN(init->nameservers));
init->rv.nameservers.p = init->nameservers;
__cxa_atexit(freeresolvconf, &g_resolvconf, NULL);
- int rc;
if (!IsWindows()) {
- FILE *f;
if ((f = fopen("/etc/resolv.conf", "r"))) {
rc = parseresolvconf(g_resolvconf, f);
} else {
diff --git a/libc/dns/parseresolvconf.c b/libc/dns/parseresolvconf.c
index 5dda632b6..6620b5810 100644
--- a/libc/dns/parseresolvconf.c
+++ b/libc/dns/parseresolvconf.c
@@ -56,9 +56,7 @@ int parseresolvconf(struct ResolvConf *resolv, struct FILE *f) {
if ((directive = strtok_r(line, " \t\r\n\v", &tok)) &&
(value = strtok_r(NULL, " \t\r\n\v", &tok))) {
if ((strcmp(directive, "nameserver") == 0 &&
- inet_pton(AF_INET, value, &nameserver.sin_addr.s_addr) == 1) ||
- (strcmp(directive, "search") == 0 && strcmp(value, "local") == 0 &&
- (nameserver.sin_addr.s_addr = htonl(INADDR_LOOPBACK)))) {
+ inet_pton(AF_INET, value, &nameserver.sin_addr.s_addr) == 1)) {
if (append(&resolv->nameservers, &nameserver) != -1) ++rc;
}
}
diff --git a/libc/elf/elf.h b/libc/elf/elf.h
index 97f5f0407..f897a43dc 100644
--- a/libc/elf/elf.h
+++ b/libc/elf/elf.h
@@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_ELF_H_
#define COSMOPOLITAN_LIBC_ELF_H_
+#ifndef __STRICT_ANSI__
#include "libc/bits/safemacros.h"
#include "libc/elf/def.h"
#include "libc/elf/struct/ehdr.h"
@@ -25,7 +26,7 @@ Elf64_Shdr *getelfsectionbyaddress(const Elf64_Ehdr *, size_t, void *);
forceinline void checkelfaddress(const Elf64_Ehdr *elf, size_t mapsize,
intptr_t addr, size_t addrsize) {
-#if !(TRUSTWORTHY + ELF_TRUSTWORTHY + 0)
+#if !(TRUSTWORTHY + ELF_TRUSTWORTHY + 0) || ELF_UNTRUSTWORTHY + 0
if (addr < (intptr_t)elf || addr + addrsize > (intptr_t)elf + mapsize) {
abort();
}
@@ -119,4 +120,5 @@ static inline const char *getelfsectionname(const Elf64_Ehdr *elf,
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_ELF_H_ */
diff --git a/libc/elf/struct/sym.h b/libc/elf/struct/sym.h
index 8b1baa0b3..c33f625b7 100644
--- a/libc/elf/struct/sym.h
+++ b/libc/elf/struct/sym.h
@@ -7,8 +7,9 @@ typedef struct Elf64_Sym {
Elf64_Word st_name;
/* ELF64_ST_TYPE(st_info) → STT_{NOTYPE,OBJECT,FUNC,SECTION,FILE,COMMON,...}
* ELF64_ST_BIND(st_info) → STB_{LOCAL,GLOBAL,WEAK,...} */
- unsigned char st_info;
- unsigned char st_other;
+ uint8_t st_info;
+ /* STV_{DEFAULT,INTERNAL,HIDDEN,PROTECTED} */
+ uint8_t st_other;
Elf64_Section st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
diff --git a/libc/errno.h b/libc/errno.h
index 16c36e0b3..d37c319b6 100644
--- a/libc/errno.h
+++ b/libc/errno.h
@@ -6,137 +6,138 @@
* @see libc/sysv/consts.sh for numbers
*/
-#define EPERM EPERM /* operation not permitted */
-#define ENOENT ENOENT /* no such file or directory */
-#define ESRCH ESRCH /* no such process */
-#define EINTR EINTR /* interrupted system call */
-#define EIO EIO /* input/output error */
-#define ENXIO ENXIO /* no such device or address */
-#define E2BIG E2BIG /* argument list too long */
-#define ENOEXEC ENOEXEC /* exec format error */
-#define EBADF EBADF /* bad file descriptor */
-#define ECHILD ECHILD /* no child processes */
-#define EAGAIN EAGAIN /* resource temporarily unavailable */
-#define ENOMEM ENOMEM /* not enough space */
-#define EACCES EACCES /* permission denied */
-#define EFAULT EFAULT /* bad address */
-#define ENOTBLK ENOTBLK /* block device required */
-#define EBUSY EBUSY /* device or resource busy */
-#define EEXIST EEXIST /* file exists */
-#define EXDEV EXDEV /* improper link */
-#define ENODEV ENODEV /* no such device */
-#define ENOTDIR ENOTDIR /* not a directory */
-#define EISDIR EISDIR /* is a directory */
-#define EINVAL EINVAL /* invalid argument */
-#define ENFILE ENFILE /* too many open files in system */
-#define EMFILE EMFILE /* too many open files */
-#define ENOTTY ENOTTY /* inappropriate I/O control op */
-#define ETXTBSY ETXTBSY /* text file busy */
-#define EFBIG EFBIG /* file too large */
-#define ENOSPC ENOSPC /* no space left on device */
-#define ESPIPE ESPIPE /* invalid seek */
-#define EROFS EROFS /* read-only filesystem */
-#define EMLINK EMLINK /* too many links */
-#define EPIPE EPIPE /* broken pipe */
-#define EDOM EDOM /* argument out of function domain */
-#define ERANGE ERANGE /* result too large */
-#define EDEADLK EDEADLK /* resource deadlock avoided */
-#define ENAMETOOLONG ENAMETOOLONG /* filename too long */
-#define ENOLCK ENOLCK /* no locks available */
-#define ENOSYS ENOSYS /* system call not implemented */
-#define ENOTEMPTY ENOTEMPTY /* directory not empty */
-#define ELOOP ELOOP /* too many levels of symbolic links */
-#define ENOMSG ENOMSG /* no message of the desired type */
-#define EIDRM EIDRM /* identifier removed */
-#define ECHRNG ECHRNG /* channel number out of range */
-#define EL2NSYNC EL2NSYNC /* level 2 not synchronized */
-#define EL3HLT EL3HLT /* level 3 halted */
-#define EL3RST EL3RST /* level 3 halted */
-#define ELNRNG ELNRNG /* link number out of range */
-#define EUNATCH EUNATCH /* protocol driver not attached */
-#define ENOCSI ENOCSI /* no csi structure available */
-#define EL2HLT EL2HLT /* level 2 halted */
-#define EBADE EBADE /* invalid exchange */
-#define EBADR EBADR /* invalid request descriptor */
-#define EXFULL EXFULL /* exchange full */
-#define ENOANO ENOANO /* no anode */
-#define EBADRQC EBADRQC /* invalid request code */
-#define EBADSLT EBADSLT /* invalid slot */
-#define ENOSTR ENOSTR /* no string */
-#define ENODATA ENODATA /* no data */
-#define ETIME ETIME /* timer expired */
-#define ENOSR ENOSR /* out of streams resources */
-#define ENONET ENONET /* no network */
-#define ENOPKG ENOPKG /* package not installed */
-#define EREMOTE EREMOTE /* object is remote */
-#define ENOLINK ENOLINK /* link severed */
-#define EADV EADV /* todo */
-#define ESRMNT ESRMNT /* todo */
-#define ECOMM ECOMM /* communication error on send */
-#define EPROTO EPROTO /* protocol error */
-#define EMULTIHOP EMULTIHOP /* multihop attempted */
-#define EDOTDOT EDOTDOT /* todo */
-#define EBADMSG EBADMSG /* bad message */
-#define EOVERFLOW EOVERFLOW /* value too large for type */
-#define ENOTUNIQ ENOTUNIQ /* name not unique on network */
-#define EBADFD EBADFD /* fd in bad *state* (cf. EBADF) */
-#define EREMCHG EREMCHG /* remote address changed */
-#define ELIBACC ELIBACC /* cannot access dso */
-#define ELIBBAD ELIBBAD /* corrupted shared library */
-#define ELIBSCN ELIBSCN /* a.out section corrupted */
-#define ELIBMAX ELIBMAX /* too many shared libraries */
-#define ELIBEXEC ELIBEXEC /* cannot exec a dso directly */
-#define EILSEQ EILSEQ /* invalid wide character */
-#define ERESTART ERESTART /* please restart syscall */
-#define ESTRPIPE ESTRPIPE /* streams pipe error */
-#define EUSERS EUSERS /* too many users */
-#define ENOTSOCK ENOTSOCK /* not a socket */
-#define EDESTADDRREQ EDESTADDRREQ /* dest address needed */
-#define EMSGSIZE EMSGSIZE /* message too long */
-#define EPROTOTYPE EPROTOTYPE /* protocol wrong for socket */
-#define ENOPROTOOPT ENOPROTOOPT /* protocol not available */
+#define EPERM EPERM /* operation not permitted */
+#define ENOENT ENOENT /* no such file or directory */
+#define ESRCH ESRCH /* no such process */
+#define EINTR EINTR /* interrupted system call */
+#define EIO EIO /* input/output error */
+#define ENXIO ENXIO /* no such device or address */
+#define E2BIG E2BIG /* argument list too long */
+#define ENOEXEC ENOEXEC /* exec format error */
+#define EBADF EBADF /* bad file descriptor */
+#define ECHILD ECHILD /* no child processes */
+#define EAGAIN EAGAIN /* resource temporarily unavailable */
+#define ENOMEM ENOMEM /* not enough space */
+#define EACCES EACCES /* permission denied */
+#define EFAULT EFAULT /* bad address */
+#define ENOTBLK ENOTBLK /* block device required */
+#define EBUSY EBUSY /* device or resource busy */
+#define EEXIST EEXIST /* file exists */
+#define EXDEV EXDEV /* improper link */
+#define ENODEV ENODEV /* no such device */
+#define ENOTDIR ENOTDIR /* not a directory */
+#define EISDIR EISDIR /* is a directory */
+#define EINVAL EINVAL /* invalid argument */
+#define ENFILE ENFILE /* too many open files in system */
+#define EMFILE EMFILE /* too many open files */
+#define ENOTTY ENOTTY /* inappropriate I/O control op */
+#define ETXTBSY ETXTBSY /* text file busy */
+#define EFBIG EFBIG /* file too large */
+#define ENOSPC ENOSPC /* no space left on device */
+#define ESPIPE ESPIPE /* invalid seek */
+#define EROFS EROFS /* read-only filesystem */
+#define EMLINK EMLINK /* too many links */
+#define EPIPE EPIPE /* broken pipe */
+#define EDOM EDOM /* argument out of function domain */
+#define ERANGE ERANGE /* result too large */
+#define EDEADLK EDEADLK /* resource deadlock avoided */
+#define ENAMETOOLONG ENAMETOOLONG /* filename too long */
+#define ENOLCK ENOLCK /* no locks available */
+#define ENOSYS ENOSYS /* system call not implemented */
+#define ENOTEMPTY ENOTEMPTY /* directory not empty */
+#define ELOOP ELOOP /* too many levels of symbolic links */
+#define ENOMSG ENOMSG /* no message of the desired type */
+#define EIDRM EIDRM /* identifier removed */
+#define ECHRNG ECHRNG /* channel number out of range */
+#define EL2NSYNC EL2NSYNC /* level 2 not synchronized */
+#define EL3HLT EL3HLT /* level 3 halted */
+#define EL3RST EL3RST /* level 3 halted */
+#define ELNRNG ELNRNG /* link number out of range */
+#define EUNATCH EUNATCH /* protocol driver not attached */
+#define ENOCSI ENOCSI /* no csi structure available */
+#define EL2HLT EL2HLT /* level 2 halted */
+#define EBADE EBADE /* invalid exchange */
+#define EBADR EBADR /* invalid request descriptor */
+#define EXFULL EXFULL /* exchange full */
+#define ENOANO ENOANO /* no anode */
+#define EBADRQC EBADRQC /* invalid request code */
+#define EBADSLT EBADSLT /* invalid slot */
+#define ENOSTR ENOSTR /* no string */
+#define ENODATA ENODATA /* no data */
+#define ETIME ETIME /* timer expired */
+#define ENOSR ENOSR /* out of streams resources */
+#define ENONET ENONET /* no network */
+#define ENOPKG ENOPKG /* package not installed */
+#define EREMOTE EREMOTE /* object is remote */
+#define ENOLINK ENOLINK /* link severed */
+#define EADV EADV /* todo */
+#define ESRMNT ESRMNT /* todo */
+#define ECOMM ECOMM /* communication error on send */
+#define EPROTO EPROTO /* protocol error */
+#define EMULTIHOP EMULTIHOP /* multihop attempted */
+#define EDOTDOT EDOTDOT /* todo */
+#define EBADMSG EBADMSG /* bad message */
+#define EOVERFLOW EOVERFLOW /* value too large for type */
+#define ENOTUNIQ ENOTUNIQ /* name not unique on network */
+#define EBADFD EBADFD /* fd in bad *state* (cf. EBADF) */
+#define EREMCHG EREMCHG /* remote address changed */
+#define ELIBACC ELIBACC /* cannot access dso */
+#define ELIBBAD ELIBBAD /* corrupted shared library */
+#define ELIBSCN ELIBSCN /* a.out section corrupted */
+#define ELIBMAX ELIBMAX /* too many shared libraries */
+#define ELIBEXEC ELIBEXEC /* cannot exec a dso directly */
+#define EILSEQ EILSEQ /* invalid wide character */
+#define ERESTART ERESTART /* please restart syscall */
+#define ESTRPIPE ESTRPIPE /* streams pipe error */
+#define EUSERS EUSERS /* too many users */
+#define ENOTSOCK ENOTSOCK /* not a socket */
+#define EDESTADDRREQ EDESTADDRREQ /* dest address needed */
+#define EMSGSIZE EMSGSIZE /* message too long */
+#define EPROTOTYPE EPROTOTYPE /* protocol wrong for socket */
+#define ENOPROTOOPT ENOPROTOOPT /* protocol not available */
#define EPROTONOSUPPORT EPROTONOSUPPORT /* protocol not supported */
#define ESOCKTNOSUPPORT ESOCKTNOSUPPORT /* socket type not supported */
-#define EOPNOTSUPP EOPNOTSUPP /* operation not supported on socket */
-#define EPFNOSUPPORT EPFNOSUPPORT /* protocol family not supported */
-#define EAFNOSUPPORT EAFNOSUPPORT /* address family not supported */
-#define EADDRINUSE EADDRINUSE /* address already in use */
-#define EADDRNOTAVAIL EADDRNOTAVAIL /* address not available */
-#define ENETDOWN ENETDOWN /* network is down */
-#define ENETUNREACH ENETUNREACH /* network unreachable */
-#define ENETRESET ENETRESET /* connection aborted by network */
-#define ECONNABORTED ECONNABORTED /* connection aborted */
-#define ECONNRESET ECONNRESET /* connection reset */
-#define ENOBUFS ENOBUFS /* no buffer space available */
-#define EISCONN EISCONN /* socket is connected */
-#define ENOTCONN ENOTCONN /* the socket is not connected */
-#define ESHUTDOWN ESHUTDOWN /* no send after endpoint shutdown */
-#define ETOOMANYREFS ETOOMANYREFS /* too many refs */
-#define ETIMEDOUT ETIMEDOUT /* connection timed out */
-#define ECONNREFUSED ECONNREFUSED /* connection refused */
-#define EHOSTDOWN EHOSTDOWN /* host is down */
-#define EHOSTUNREACH EHOSTUNREACH /* host is unreachable */
-#define EALREADY EALREADY /* connection already in progress */
-#define EINPROGRESS EINPROGRESS /* operation in progress */
-#define ESTALE ESTALE /* stale file handle */
-#define EUCLEAN EUCLEAN /* structure needs cleaning */
-#define ENOTNAM ENOTNAM /* todo */
-#define ENAVAIL ENAVAIL /* todo */
-#define EISNAM EISNAM /* is a named type file */
-#define EREMOTEIO EREMOTEIO /* remote i/o error */
-#define EDQUOT EDQUOT /* disk quota exceeded */
-#define ENOMEDIUM ENOMEDIUM /* no medium found */
-#define EMEDIUMTYPE EMEDIUMTYPE /* wrong medium type */
-#define ECANCELED ECANCELED /* operation canceled */
-#define ENOKEY ENOKEY /* required key not available */
-#define EKEYEXPIRED EKEYEXPIRED /* key has expired */
-#define EKEYREVOKED EKEYREVOKED /* key has been revoked */
-#define EKEYREJECTED EKEYREJECTED /* key was rejected by service */
-#define EOWNERDEAD EOWNERDEAD /* owner died */
+#define EOPNOTSUPP EOPNOTSUPP /* operation not supported on socket */
+#define EPFNOSUPPORT EPFNOSUPPORT /* protocol family not supported */
+#define EAFNOSUPPORT EAFNOSUPPORT /* address family not supported */
+#define EADDRINUSE EADDRINUSE /* address already in use */
+#define EADDRNOTAVAIL EADDRNOTAVAIL /* address not available */
+#define ENETDOWN ENETDOWN /* network is down */
+#define ENETUNREACH ENETUNREACH /* network unreachable */
+#define ENETRESET ENETRESET /* connection aborted by network */
+#define ECONNABORTED ECONNABORTED /* connection aborted */
+#define ECONNRESET ECONNRESET /* connection reset */
+#define ENOBUFS ENOBUFS /* no buffer space available */
+#define EISCONN EISCONN /* socket is connected */
+#define ENOTCONN ENOTCONN /* the socket is not connected */
+#define ESHUTDOWN ESHUTDOWN /* no send after endpoint shutdown */
+#define ETOOMANYREFS ETOOMANYREFS /* too many refs */
+#define ETIMEDOUT ETIMEDOUT /* connection timed out */
+#define ECONNREFUSED ECONNREFUSED /* connection refused */
+#define EHOSTDOWN EHOSTDOWN /* host is down */
+#define EHOSTUNREACH EHOSTUNREACH /* host is unreachable */
+#define EALREADY EALREADY /* connection already in progress */
+#define EINPROGRESS EINPROGRESS /* operation in progress */
+#define ESTALE ESTALE /* stale file handle */
+#define EUCLEAN EUCLEAN /* structure needs cleaning */
+#define ENOTNAM ENOTNAM /* todo */
+#define ENAVAIL ENAVAIL /* todo */
+#define EISNAM EISNAM /* is a named type file */
+#define EREMOTEIO EREMOTEIO /* remote i/o error */
+#define EDQUOT EDQUOT /* disk quota exceeded */
+#define ENOMEDIUM ENOMEDIUM /* no medium found */
+#define EMEDIUMTYPE EMEDIUMTYPE /* wrong medium type */
+#define ECANCELED ECANCELED /* operation canceled */
+#define ENOKEY ENOKEY /* required key not available */
+#define EKEYEXPIRED EKEYEXPIRED /* key has expired */
+#define EKEYREVOKED EKEYREVOKED /* key has been revoked */
+#define EKEYREJECTED EKEYREJECTED /* key was rejected by service */
+#define EOWNERDEAD EOWNERDEAD /* owner died */
#define ENOTRECOVERABLE ENOTRECOVERABLE /* state not recoverable */
-#define ERFKILL ERFKILL /* can't op b/c RF-kill */
-#define EHWPOISON EHWPOISON /* mempage has h/w error */
-#define EWOULDBLOCK EAGAIN /* poll fd and try again */
+#define ERFKILL ERFKILL /* can't op b/c RF-kill */
+#define EHWPOISON EHWPOISON /* mempage has h/w error */
+#define EWOULDBLOCK EAGAIN /* poll fd and try again */
+#define ENOTSUP ENOTSUP
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@@ -273,6 +274,7 @@ hidden extern const long EOWNERDEAD;
hidden extern const long ENOTRECOVERABLE;
hidden extern const long ERFKILL;
hidden extern const long EHWPOISON;
+hidden extern const long ENOTSUP;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/fmt/bing.h b/libc/fmt/bing.h
index 227d6ec7b..a3cf5ccde 100644
--- a/libc/fmt/bing.h
+++ b/libc/fmt/bing.h
@@ -1,7 +1,7 @@
#ifndef COSMOPOLITAN_LIBC_FMT_BING_H_
#define COSMOPOLITAN_LIBC_FMT_BING_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
-COSMOPOLITAN_C_START_
+#ifndef __cplusplus
int bing(int, int) nosideeffect;
int unbing(int) nosideeffect;
@@ -11,6 +11,6 @@ void *unhexbuf(void *, size_t, const char *);
void *unhexstr(const char *) mallocesque;
short *bingblit(int ys, int xs, unsigned char[ys][xs], int, int);
-COSMOPOLITAN_C_END_
+#endif /* __cplusplus */
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_FMT_BING_H_ */
diff --git a/libc/fmt/fmt.mk b/libc/fmt/fmt.mk
index 47407d569..53f5d164b 100644
--- a/libc/fmt/fmt.mk
+++ b/libc/fmt/fmt.mk
@@ -39,7 +39,8 @@ LIBC_FMT_A_DIRECTDEPS = \
LIBC_TINYMATH \
LIBC_NEXGEN32E \
LIBC_NT_KERNELBASE \
- LIBC_SYSV
+ LIBC_SYSV \
+ THIRD_PARTY_COMPILER_RT
LIBC_FMT_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_FMT_A_DIRECTDEPS),$($(x))))
diff --git a/libc/fmt/paland.inc b/libc/fmt/paland.inc
index e80b474b9..0f78f28bd 100644
--- a/libc/fmt/paland.inc
+++ b/libc/fmt/paland.inc
@@ -35,20 +35,6 @@ info@paland.com\"");
#include "libc/str/internal.h"
#include "libc/sysv/errfuns.h"
-/* 'ntoa' conversion buffer size, this must be big enough to hold one
- converted numeric number including padded zeros (dynamically created
- on stack) default: 64 byte */
-#ifndef PRINTF_NTOA_BUFFER_SIZE
-#define PRINTF_NTOA_BUFFER_SIZE 64
-#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 64
-#endif
-
#define FLAGS_ZEROPAD (1U << 0U)
#define FLAGS_LEFT (1U << 1U)
#define FLAGS_PLUS (1U << 2U)
diff --git a/libc/fmt/palandftoa.c b/libc/fmt/palandftoa.c
index 04b46a065..238f7e5c1 100644
--- a/libc/fmt/palandftoa.c
+++ b/libc/fmt/palandftoa.c
@@ -59,7 +59,7 @@ int ftoa(int out(int, void *), void *arg, long double value, unsigned long prec,
buf[2] = 'n';
buf[3] = '\0';
len += 3;
- } else if (isinf(value) || fabsl(value) > 0x7ffffffffffffffful) {
+ } else if (isinf(value) || (value && ilogbl(fabsl(value)) > 63)) {
buf[0] = 'f';
buf[1] = 'n';
buf[2] = 'i';
@@ -90,11 +90,11 @@ int ftoa(int out(int, void *), void *arg, long double value, unsigned long prec,
++whole;
}
} else if (diff < 0.5) {
- } else if ((frac == 0U) || (frac & 1U)) {
+ } else if (!frac || (frac & 1)) {
++frac; /* if halfway, round up if odd OR if last digit is 0 */
}
- if (prec == 0U) {
+ if (!prec) {
diff = fabsl(value) - whole;
if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
/* exactly 0.5 and ODD, then round up */
@@ -112,7 +112,7 @@ int ftoa(int out(int, void *), void *arg, long double value, unsigned long prec,
}
}
/* add extra 0s */
- while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
+ while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0)) {
buf[len++] = '0';
}
if (len < PRINTF_FTOA_BUFFER_SIZE) {
diff --git a/libc/fmt/palandntoa.c b/libc/fmt/palandntoa.c
index 9d636e59b..093a6ca4e 100644
--- a/libc/fmt/palandntoa.c
+++ b/libc/fmt/palandntoa.c
@@ -29,9 +29,11 @@
#include "libc/fmt/paland.inc"
#include "libc/fmt/palandprintf.h"
+uintmax_t __udivmodti4(uintmax_t, uintmax_t, uintmax_t *);
+
static int ntoaformat(int out(int, void *), void *arg, char *buf, unsigned len,
bool negative, unsigned log2base, unsigned prec,
- unsigned width, unsigned flags) {
+ unsigned width, unsigned char flags) {
unsigned i, idx;
idx = 0;
@@ -101,9 +103,10 @@ static int ntoaformat(int out(int, void *), void *arg, char *buf, unsigned len,
return 0;
}
-static int ntoa2(int out(int, void *), void *arg, uintmax_t value, bool neg,
- unsigned log2base, unsigned prec, unsigned width,
- unsigned flags, const char *alphabet) {
+int ntoa2(int out(int, void *), void *arg, uintmax_t value, bool neg,
+ unsigned log2base, unsigned prec, unsigned width, unsigned flags,
+ const char *alphabet) {
+ uintmax_t remainder;
unsigned len, count, digit;
char buf[PRINTF_NTOA_BUFFER_SIZE];
len = 0;
@@ -112,12 +115,13 @@ static int ntoa2(int out(int, void *), void *arg, uintmax_t value, bool neg,
count = 0;
do {
assert(len < PRINTF_NTOA_BUFFER_SIZE);
- if (log2base) {
+ if (!log2base) {
+ value = __udivmodti4(value, 10, &remainder);
+ digit = remainder;
+ } else {
digit = value;
digit &= (1u << log2base) - 1;
value >>= log2base;
- } else {
- value = div10(value, &digit);
}
if ((flags & FLAGS_GROUPING) && count == 3) {
buf[len++] = ',';
@@ -132,12 +136,12 @@ static int ntoa2(int out(int, void *), void *arg, uintmax_t value, bool neg,
}
int ntoa(int out(int, void *), void *arg, va_list va, unsigned char signbit,
- unsigned long log2base, unsigned long precision, unsigned long width,
- unsigned long flags, const char *alphabet) {
- bool negative;
+ unsigned long log2base, unsigned long prec, unsigned long width,
+ unsigned char flags, const char *lang) {
+ bool neg;
uintmax_t value, sign;
- /* ignore '0' flag when precision is given */
+ /* ignore '0' flag when prec is given */
if (flags & FLAGS_PRECISION) {
flags &= ~FLAGS_ZEROPAD;
}
@@ -153,24 +157,22 @@ int ntoa(int out(int, void *), void *arg, va_list va, unsigned char signbit,
value = va_arg(va, uint64_t);
}
- negative = false;
- sign = (uintmax_t)1 << signbit;
- if (value > (sign | (sign - 1))) erange();
+ neg = 0;
+ sign = 1;
+ sign <<= signbit;
+ value &= sign | (sign - 1);
if (flags & FLAGS_ISSIGNED) {
if (value != sign) {
if (value & sign) {
value = ~value + 1;
- negative = true;
+ value &= sign | (sign - 1);
+ neg = 1;
}
value &= sign - 1;
+ } else {
+ neg = 1;
}
- } else {
- value &= sign | (sign - 1);
}
- if (ntoa2(out, arg, value, negative, log2base, precision, width, flags,
- alphabet) == -1) {
- return -1;
- }
- return 0;
+ return ntoa2(out, arg, value, neg, log2base, prec, width, flags, lang);
}
diff --git a/libc/fmt/palandprintf.c b/libc/fmt/palandprintf.c
index 407658c11..ab1d57369 100644
--- a/libc/fmt/palandprintf.c
+++ b/libc/fmt/palandprintf.c
@@ -37,6 +37,7 @@
└─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/conv/conv.h"
#include "libc/escape/escape.h"
#include "libc/fmt/fmt.h"
@@ -47,10 +48,12 @@
#include "libc/str/str.h"
#include "libc/sysv/errfuns.h"
-static unsigned ppatoi(const char **str) {
- unsigned i;
- i = 0;
- while (isdigit(**str)) i = i * 10u + (unsigned)(*((*str)++) - '0');
+static int ppatoi(const char **str) {
+ int i;
+ for (i = 0; '0' <= **str && **str <= '9'; ++*str) {
+ i *= 10;
+ i += **str - '0';
+ }
return i;
}
@@ -104,9 +107,14 @@ static unsigned ppatoi(const char **str) {
* @see printf() for wordier documentation
*/
hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) {
+ void *p;
+ char qchar;
+ long double ldbl;
+ wchar_t charbuf[3];
+ const char *alphabet;
int (*out)(int, void *);
- unsigned flags, width, precision;
- int lasterr;
+ unsigned char signbit, log2base;
+ int w, rc, flags, width, lasterr, precision;
lasterr = errno;
out = fn ? fn : (int (*)(int, void *))missingno;
@@ -161,7 +169,7 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) {
if (isdigit(*format)) {
width = ppatoi(&format);
} else if (*format == '*') {
- const int w = va_arg(va, int);
+ w = va_arg(va, int);
if (w < 0) {
flags |= FLAGS_LEFT; /* reverse padding */
width = -w;
@@ -179,14 +187,16 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) {
if (isdigit(*format)) {
precision = ppatoi(&format);
} else if (*format == '*') {
- const int prec = (int)va_arg(va, int);
- precision = prec > 0 ? prec : 0;
+ precision = va_arg(va, int);
format++;
}
}
+ if (precision < 0) {
+ precision = 0;
+ }
/* evaluate length field */
- unsigned char signbit = 31;
+ signbit = 31;
switch (*format) {
case 'j': /* intmax_t */
format++;
@@ -196,8 +206,8 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) {
if (format[1] == 'l') format++;
/* fallthrough */
case 't': /* ptrdiff_t */
- case 'Z': /* size_t */
case 'z': /* size_t */
+ case 'Z': /* size_t */
case 'L': /* long double */
format++;
signbit = 63;
@@ -216,12 +226,9 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) {
}
/* evaluate specifier */
- void *p;
- const char *alphabet = "0123456789abcdef";
- unsigned log2base = 0;
- wchar_t charbuf[3];
- int rc;
- char qchar = '"';
+ alphabet = "0123456789abcdef";
+ log2base = 0;
+ qchar = '"';
switch (*format++) {
case 'p':
flags |= FLAGS_ZEROPAD;
@@ -256,23 +263,21 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) {
}
case 'f':
- case 'F': {
- long double value;
+ case 'F':
if (signbit == 63) {
- value = va_arg(va, long double);
+ ldbl = va_arg(va, long double);
} else {
- value = va_arg(va, double);
+ ldbl = va_arg(va, double);
}
- if (weaken(ftoa)(out, arg, value, precision, width, flags) == -1) {
+ if (weaken(ftoa)(out, arg, ldbl, precision, width, flags) == -1) {
return -1;
}
break;
- }
case 'c':
qchar = '\'';
p = charbuf;
- charbuf[0] = (wchar_t)va_arg(va, int); /* @assume little endian */
+ charbuf[0] = va_arg(va, int);
charbuf[1] = L'\0';
goto showstr;
diff --git a/libc/fmt/palandprintf.h b/libc/fmt/palandprintf.h
index ac202e016..7c65f304e 100644
--- a/libc/fmt/palandprintf.h
+++ b/libc/fmt/palandprintf.h
@@ -1,5 +1,9 @@
#ifndef COSMOPOLITAN_LIBC_FMT_PALANDPRINTF_H_
#define COSMOPOLITAN_LIBC_FMT_PALANDPRINTF_H_
+
+#define PRINTF_NTOA_BUFFER_SIZE 144
+#define PRINTF_FTOA_BUFFER_SIZE 64
+
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@@ -9,7 +13,7 @@ int ftoa(int(int, void *), void *, long double, unsigned long, unsigned long,
int stoa(int(int, void *), void *, void *, unsigned long, unsigned long,
unsigned long, unsigned char, unsigned char) hidden;
int ntoa(int(int, void *), void *, va_list, unsigned char, unsigned long,
- unsigned long, unsigned long, unsigned long, const char *) hidden;
+ unsigned long, unsigned long, unsigned char, const char *) hidden;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/fmt/sscanf.c b/libc/fmt/sscanf.c
index eac689bce..06323409e 100644
--- a/libc/fmt/sscanf.c
+++ b/libc/fmt/sscanf.c
@@ -18,7 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/dce.h"
-#include "libc/fmt/vsscanf.h"
+#include "libc/fmt/fmt.h"
/**
* String decoder.
@@ -28,11 +28,7 @@ int(sscanf)(const char *str, const char *fmt, ...) {
int rc;
va_list va;
va_start(va, fmt);
- if (IsTiny()) {
- rc = (vsscanf)(str, fmt, va);
- } else {
- rc = __vsscanf(str, fmt, va, __vcscanf);
- }
+ rc = (vsscanf)(str, fmt, va);
va_end(va);
return rc;
}
diff --git a/libc/fmt/stoa.c b/libc/fmt/stoa.c
index 2ce5b5c20..7b99132ce 100644
--- a/libc/fmt/stoa.c
+++ b/libc/fmt/stoa.c
@@ -19,6 +19,7 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/bits.h"
#include "libc/bits/safemacros.h"
+#include "libc/bits/weaken.h"
#include "libc/escape/escape.h"
#include "libc/fmt/fmt.h"
#include "libc/fmt/paland.inc"
@@ -176,6 +177,7 @@ int stoa(int out(int, void *), void *arg, void *data, unsigned long flags,
if (w <= width && (flags & FLAGS_LEFT)) {
if (spacepad(out, arg, width - w) == -1) return -1;
}
+
if (!(flags & FLAGS_NOQUOTE) && (flags & FLAGS_REPR)) {
if (out(qchar, arg) == -1) return -1;
}
diff --git a/libc/fmt/strerror.c b/libc/fmt/strerror.c
index cce887729..7077c5d92 100644
--- a/libc/fmt/strerror.c
+++ b/libc/fmt/strerror.c
@@ -26,5 +26,5 @@
char *strerror(int err) {
alignas(1) static char buf[512];
strerror_r(err, buf, sizeof(buf));
- return &buf[0];
+ return buf;
}
diff --git a/libc/fmt/strerror_r.c b/libc/fmt/strerror_r.c
index b2e6f9986..b5013f2c0 100644
--- a/libc/fmt/strerror_r.c
+++ b/libc/fmt/strerror_r.c
@@ -27,14 +27,15 @@
#include "libc/nt/runtime.h"
#include "libc/str/str.h"
-static const char *geterrname(int code) {
+const char *geterrname(int code) {
extern const char kErrnoNames[];
const long *e;
- const char *s;
- size_t i;
- for (i = 0, e = &E2BIG; e <= &EXFULL; ++e, ++i) {
- if (code == *e && (s = indexdoublenulstring(&kErrnoNames[0], i))) {
- return s;
+ size_t i, n;
+ e = &E2BIG;
+ n = &EXFULL + 1 - e;
+ for (i = 0; i < n; ++i) {
+ if (code == e[i]) {
+ return indexdoublenulstring(kErrnoNames, i);
}
}
return NULL;
@@ -46,8 +47,13 @@ static const char *geterrname(int code) {
*/
int strerror_r(int err, char *buf, size_t size) {
const char *s;
- s = (err == -1 || IsTiny()) ? "?" : firstnonnull(geterrname(err), "?");
+ if (err == -1 || IsTiny()) {
+ s = "?";
+ } else {
+ s = firstnonnull(geterrname(err), "?");
+ }
if (!SupportsWindows()) {
+ DebugBreak();
snprintf(buf, size, "E%s[%d]", s, err);
} else {
char16_t buf16[100];
diff --git a/libc/fmt/vcscanf.c b/libc/fmt/vcscanf.c
index f918a2174..52760ad79 100644
--- a/libc/fmt/vcscanf.c
+++ b/libc/fmt/vcscanf.c
@@ -17,13 +17,276 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/bits/weaken.h"
+#include "libc/conv/conv.h"
#include "libc/fmt/fmt.h"
-#include "libc/fmt/vcscanf.h"
+#include "libc/mem/mem.h"
+#include "libc/runtime/runtime.h"
+#include "libc/str/str.h"
+#include "libc/str/tpdecodecb.h"
+#include "libc/sysv/errfuns.h"
/**
- * Linkable callback-driven string / file / stream decoder.
- * @see libc/fmt/vcscanf.h (for docs and implementation)
+ * String / file / stream decoder.
+ *
+ * This scanf implementation is able to tokenize strings containing
+ * 8-bit through 128-bit integers (with validation), floating point
+ * numbers, etc. It can also be used to convert UTF-8 to UTF-16/32.
+ *
+ * - `%d` parses integer
+ * - `%ms` parses string allocating buffer assigning pointer
+ *
+ * @param callback supplies UTF-8 characters using -1 sentinel
+ * @param fmt is a computer program embedded inside a c string, written
+ * in a domain-specific programming language that, by design, lacks
+ * Turing-completeness
+ * @param va points to the variadic argument state
+ * @see libc/fmt/pflink.h (dynamic memory is not a requirement)
*/
-int(vcscanf)(int callback(void *), void *arg, const char *fmt, va_list ap) {
- return __vcscanf(callback, arg, fmt, ap);
+int vcscanf(int callback(void *), void *arg, const char *fmt, va_list va) {
+ struct FreeMe {
+ struct FreeMe *next;
+ void *ptr;
+ } *freeme = NULL;
+ const unsigned char *p = (const unsigned char *)fmt;
+ unsigned i = 0;
+ int items = 0;
+ int c = callback(arg);
+ while (c != -1) {
+ switch (p[i++]) {
+ case '\0':
+ return items;
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ case '\v':
+ while (isspace(c)) c = callback(arg);
+ break;
+ case '%': {
+ uintmax_t number;
+ void *buf;
+ size_t bufsize;
+ unsigned width = 0;
+ unsigned char bits = 32;
+ unsigned char charbytes = sizeof(char);
+ unsigned char diglet;
+ unsigned char base;
+ unsigned char prefix;
+ bool rawmode = false;
+ bool issigned = false;
+ bool ismalloc = false;
+ bool isneg = false;
+ bool thousands = false;
+ bool discard = false;
+ for (;;) {
+ switch (p[i++]) {
+ case '%': /* %% → % */
+ goto NonDirectiveCharacter;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ width *= 10;
+ width += p[i - 1] - '0';
+ break;
+ case '*':
+ discard = true;
+ break;
+ case 'm':
+ ismalloc = true;
+ break;
+ case 'c':
+ rawmode = true;
+ if (!width) width = 1;
+ /* εpsilon transition */
+ case 's':
+ goto DecodeString;
+ case '\'':
+ thousands = true;
+ break;
+ case 'j': /* 128-bit */
+ bits = sizeof(intmax_t) * 8;
+ break;
+ case 'l': /* long */
+ case 'L': /* loooong */
+ charbytes = sizeof(wchar_t);
+ /* fallthrough */
+ case 't': /* ptrdiff_t */
+ case 'Z': /* size_t */
+ case 'z': /* size_t */
+ bits = 64;
+ break;
+ case 'h': /* short and char */
+ charbytes = sizeof(char16_t);
+ bits >>= 1;
+ break;
+ case 'b': /* binary */
+ base = 2;
+ prefix = 'b';
+ goto ConsumeBasePrefix;
+ case 'p': /* pointer (NexGen32e) */
+ bits = 48;
+ /* fallthrough */
+ case 'x':
+ case 'X': /* hexadecimal */
+ base = 16;
+ prefix = 'x';
+ goto ConsumeBasePrefix;
+ case 'o': /* octal */
+ base = 8;
+ goto DecodeNumber;
+ case 'd': /* decimal */
+ case 'n': /* TODO(jart): flexidecimal */
+ issigned = true;
+ if (c == '+' || (isneg = c == '-')) {
+ c = callback(arg);
+ }
+ /* εpsilon transition */
+ case 'u':
+ base = 10;
+ goto DecodeNumber;
+ default:
+ items = einval();
+ goto Done;
+ }
+ }
+ ConsumeBasePrefix:
+ if (c == '0') {
+ c = callback(arg);
+ if (c == prefix || c == prefix + ('a' - 'A')) {
+ c = callback(arg);
+ }
+ }
+ DecodeNumber:
+ if (c != -1) {
+ number = 0;
+ do {
+ diglet = kBase36[(unsigned char)c];
+ if (1 <= diglet && diglet <= base) {
+ number *= base;
+ number += diglet - 1;
+ } else if (thousands && diglet == ',') {
+ /* ignore */
+ } else {
+ break;
+ }
+ } while ((c = callback(arg)) != -1);
+ if (!discard) {
+ uintmax_t bane = (uintmax_t)1 << (bits - 1);
+ if (!(number & ~((bane - 1) | (issigned ? 0 : bane))) ||
+ (issigned && number == bane /* two's complement bane */)) {
+ ++items;
+ } else {
+ items = erange();
+ goto Done;
+ }
+ if (issigned && isneg) {
+ number = ~number + 1;
+ }
+ void *out = va_arg(va, void *);
+ switch (bits) {
+ case sizeof(uintmax_t) * CHAR_BIT:
+ *(uintmax_t *)out = number;
+ break;
+ case 48:
+ case 64:
+ *(uint64_t *)out = (uint64_t)number;
+ break;
+ case 32:
+ *(uint32_t *)out = (uint32_t)number;
+ break;
+ case 16:
+ *(uint16_t *)out = (uint16_t)number;
+ break;
+ case 8:
+ default:
+ *(uint8_t *)out = (uint8_t)number;
+ break;
+ }
+ }
+ }
+ continue;
+ DecodeString:
+ bufsize = !width ? 32 : rawmode ? width : width + 1;
+ if (discard) {
+ buf = NULL;
+ } else if (ismalloc) {
+ buf = weaken(malloc)(bufsize * charbytes);
+ struct FreeMe *entry;
+ if (buf && (entry = weaken(calloc)(1, sizeof(struct FreeMe)))) {
+ entry->ptr = buf;
+ entry->next = freeme;
+ freeme = entry;
+ }
+ } else {
+ buf = va_arg(va, void *);
+ }
+ if (buf) {
+ size_t j = 0;
+ for (;;) {
+ if (ismalloc && !width && j + 2 + 1 >= bufsize &&
+ !weaken(grow)(&buf, &bufsize, charbytes, 0)) {
+ width = bufsize - 1;
+ }
+ if (c != -1 && j + !rawmode < bufsize && (rawmode || !isspace(c))) {
+ if (charbytes == 1) {
+ ((unsigned char *)buf)[j++] = (unsigned char)c;
+ c = callback(arg);
+ } else if (tpdecodecb((wint_t *)&c, c, (void *)callback, arg) !=
+ -1) {
+ if (charbytes == sizeof(char16_t)) {
+ j += abs(pututf16(&((char16_t *)buf)[j], bufsize - j - 1, c,
+ false));
+ } else {
+ ((wchar_t *)buf)[j++] = (wchar_t)c;
+ }
+ c = callback(arg);
+ }
+ } else {
+ if (!rawmode && j < bufsize) {
+ if (charbytes == sizeof(char)) {
+ ((unsigned char *)buf)[j] = '\0';
+ } else if (charbytes == sizeof(char16_t)) {
+ ((char16_t *)buf)[j] = u'\0';
+ } else if (charbytes == sizeof(wchar_t)) {
+ ((wchar_t *)buf)[j] = L'\0';
+ }
+ }
+ break;
+ }
+ }
+ ++items;
+ if (ismalloc) {
+ *va_arg(va, char **) = buf;
+ }
+ } else {
+ do {
+ if (isspace(c)) break;
+ } while ((c = callback(arg)) != -1);
+ }
+ break;
+ }
+ default:
+ NonDirectiveCharacter:
+ c = (c == p[i - 1]) ? callback(arg) : -1;
+ break;
+ }
+ }
+Done:
+ while (freeme) {
+ struct FreeMe *entry = freeme;
+ freeme = entry->next;
+ if (items == -1) {
+ weaken(free_s)((void **)&entry->ptr);
+ }
+ weaken(free_s)((void **)&entry);
+ }
+ return items;
}
diff --git a/libc/fmt/vcscanf.h b/libc/fmt/vcscanf.h
deleted file mode 100644
index 23209e051..000000000
--- a/libc/fmt/vcscanf.h
+++ /dev/null
@@ -1,297 +0,0 @@
-/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
-│vi: set net ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#ifndef COSMOPOLITAN_LIBC_FMT_VCSSCANF_H_
-#define COSMOPOLITAN_LIBC_FMT_VCSSCANF_H_
-#include "libc/conv/conv.h"
-#include "libc/mem/mem.h"
-#include "libc/runtime/runtime.h"
-#include "libc/str/str.h"
-#include "libc/str/tpdecodecb.h"
-#include "libc/sysv/errfuns.h"
-#if !(__ASSEMBLER__ + __LINKER__ + 0)
-
-/**
- * String / file / stream decoder.
- *
- * This scanf implementation is able to tokenize strings containing
- * 8-bit through 128-bit integers (with validation), floating point
- * numbers, etc. It can also be used to convert UTF-8 to UTF-16/32.
- *
- * - `%d` parses integer
- * - `%ms` parses string allocating buffer assigning pointer
- *
- * @param callback supplies UTF-8 characters using -1 sentinel
- * @param fmt is a computer program embedded inside a c string, written
- * in a domain-specific programming language that, by design, lacks
- * Turing-completeness
- * @param va points to the variadic argument state
- * @see libc/fmt/pflink.h (dynamic memory is not a requirement)
- */
-forceinline int __vcscanf(int callback(void *), void *arg, const char *fmt,
- va_list va) {
- struct FreeMe {
- struct FreeMe *next;
- void *ptr;
- } *freeme = NULL;
- const unsigned char *p = (const unsigned char *)fmt;
- unsigned i = 0;
- int items = 0;
- int c = callback(arg);
- while (c != -1) {
- switch (p[i++]) {
- case '\0':
- return items;
- case ' ':
- case '\t':
- case '\n':
- case '\r':
- case '\v':
- while (isspace(c)) c = callback(arg);
- break;
- case '%': {
- uintmax_t number;
- void *buf;
- size_t bufsize;
- unsigned width = 0;
- unsigned char bits = 32;
- unsigned char charbytes = sizeof(char);
- unsigned char diglet;
- unsigned char base;
- unsigned char prefix;
- bool rawmode = false;
- bool issigned = false;
- bool ismalloc = false;
- bool isneg = false;
- bool thousands = false;
- bool discard = false;
- for (;;) {
- switch (p[i++]) {
- case '%': /* %% → % */
- goto NonDirectiveCharacter;
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- width *= 10;
- width += p[i - 1] - '0';
- break;
- case '*':
- discard = true;
- break;
- case 'm':
- ismalloc = true;
- break;
- case 'c':
- rawmode = true;
- if (!width) width = 1;
- /* εpsilon transition */
- case 's':
- goto DecodeString;
- case '\'':
- thousands = true;
- break;
- case 'j': /* 128-bit */
- bits = sizeof(intmax_t) * 8;
- break;
- case 'l': /* long */
- case 'L': /* loooong */
- charbytes = sizeof(wchar_t);
- /* fallthrough */
- case 't': /* ptrdiff_t */
- case 'Z': /* size_t */
- case 'z': /* size_t */
- bits = 64;
- break;
- case 'h': /* short and char */
- charbytes = sizeof(char16_t);
- bits >>= 1;
- break;
- case 'b': /* binary */
- base = 2;
- prefix = 'b';
- goto ConsumeBasePrefix;
- case 'p': /* pointer (NexGen32e) */
- bits = 48;
- /* fallthrough */
- case 'x':
- case 'X': /* hexadecimal */
- base = 16;
- prefix = 'x';
- goto ConsumeBasePrefix;
- case 'o': /* octal */
- base = 8;
- goto DecodeNumber;
- case 'd': /* decimal */
- case 'n': /* TODO(jart): flexidecimal */
- issigned = true;
- if (c == '+' || (isneg = c == '-')) {
- c = callback(arg);
- }
- /* εpsilon transition */
- case 'u':
- base = 10;
- goto DecodeNumber;
- default:
- items = einval();
- goto Done;
- }
- }
- ConsumeBasePrefix:
- if (c == '0') {
- c = callback(arg);
- if (c == prefix || c == prefix + ('a' - 'A')) {
- c = callback(arg);
- }
- }
- DecodeNumber:
- if (c != -1) {
- number = 0;
- do {
- diglet = kBase36[(unsigned char)c];
- if (1 <= diglet && diglet <= base) {
- number *= base;
- number += diglet - 1;
- } else if (thousands && diglet == ',') {
- /* ignore */
- } else {
- break;
- }
- } while ((c = callback(arg)) != -1);
- if (!discard) {
- uintmax_t bane = (uintmax_t)1 << (bits - 1);
- if (!(number & ~((bane - 1) | (issigned ? 0 : bane))) ||
- (issigned && number == bane /* two's complement bane */)) {
- ++items;
- } else {
- items = erange();
- goto Done;
- }
- if (issigned && isneg) {
- number = ~number + 1;
- }
- void *out = va_arg(va, void *);
- switch (bits) {
- case sizeof(uintmax_t) * CHAR_BIT:
- *(uintmax_t *)out = number;
- break;
- case 48:
- case 64:
- *(uint64_t *)out = (uint64_t)number;
- break;
- case 32:
- *(uint32_t *)out = (uint32_t)number;
- break;
- case 16:
- *(uint16_t *)out = (uint16_t)number;
- break;
- case 8:
- default:
- *(uint8_t *)out = (uint8_t)number;
- break;
- }
- }
- }
- continue;
- DecodeString:
- bufsize = !width ? 32 : rawmode ? width : width + 1;
- if (discard) {
- buf = NULL;
- } else if (ismalloc) {
- buf = weaken(malloc)(bufsize * charbytes);
- struct FreeMe *entry;
- if (buf && (entry = weaken(calloc)(1, sizeof(struct FreeMe)))) {
- entry->ptr = buf;
- entry->next = freeme;
- freeme = entry;
- }
- } else {
- buf = va_arg(va, void *);
- }
- if (buf) {
- size_t j = 0;
- for (;;) {
- if (ismalloc && !width && j + 2 + 1 >= bufsize &&
- !weaken(grow)(&buf, &bufsize, charbytes, 0)) {
- width = bufsize - 1;
- }
- if (c != -1 && j + !rawmode < bufsize && (rawmode || !isspace(c))) {
- if (charbytes == 1) {
- ((unsigned char *)buf)[j++] = (unsigned char)c;
- c = callback(arg);
- } else if (tpdecodecb((wint_t *)&c, c, (void *)callback, arg) !=
- -1) {
- if (charbytes == sizeof(char16_t)) {
- j += abs(pututf16(&((char16_t *)buf)[j], bufsize - j - 1, c,
- false));
- } else {
- ((wchar_t *)buf)[j++] = (wchar_t)c;
- }
- c = callback(arg);
- }
- } else {
- if (!rawmode && j < bufsize) {
- if (charbytes == sizeof(char)) {
- ((unsigned char *)buf)[j] = '\0';
- } else if (charbytes == sizeof(char16_t)) {
- ((char16_t *)buf)[j] = u'\0';
- } else if (charbytes == sizeof(wchar_t)) {
- ((wchar_t *)buf)[j] = L'\0';
- }
- }
- break;
- }
- }
- ++items;
- if (ismalloc) {
- *va_arg(va, char **) = buf;
- }
- } else {
- do {
- if (isspace(c)) break;
- } while ((c = callback(arg)) != -1);
- }
- break;
- }
- default:
- NonDirectiveCharacter:
- c = (c == p[i - 1]) ? callback(arg) : -1;
- break;
- }
- }
-Done:
- while (freeme) {
- struct FreeMe *entry = freeme;
- freeme = entry->next;
- if (items == -1) {
- weaken(free_s)((void **)&entry->ptr);
- }
- weaken(free_s)((void **)&entry);
- }
- return items;
-}
-
-#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#endif /* COSMOPOLITAN_LIBC_FMT_VCSSCANF_H_ */
diff --git a/libc/fmt/vsscanf.c b/libc/fmt/vsscanf.c
index 67e286b70..1f273c556 100644
--- a/libc/fmt/vsscanf.c
+++ b/libc/fmt/vsscanf.c
@@ -18,7 +18,24 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/dce.h"
-#include "libc/fmt/vsscanf.h"
+#include "libc/fmt/fmt.h"
+
+struct StringScannerState {
+ const unsigned char *s;
+ size_t i;
+};
+
+static int vsscanfcb(void *arg) {
+ int res;
+ struct StringScannerState *state;
+ state = arg;
+ if ((res = state->s[state->i])) {
+ state->i++;
+ } else {
+ res = -1;
+ }
+ return res;
+}
/**
* Decodes string.
@@ -29,5 +46,6 @@
* a small code size penalty to using both
*/
int(vsscanf)(const char *str, const char *fmt, va_list va) {
- return __vsscanf(str, fmt, va, IsTiny() ? vcscanf : __vcscanf);
+ struct StringScannerState state = {(const unsigned char *)str, 0};
+ return vcscanf(vsscanfcb, &state, fmt, va);
}
diff --git a/libc/fmt/vsscanf.h b/libc/fmt/vsscanf.h
deleted file mode 100644
index dd811c1a9..000000000
--- a/libc/fmt/vsscanf.h
+++ /dev/null
@@ -1,39 +0,0 @@
-#ifndef COSMOPOLITAN_LIBC_INTERNAL_VSSCANF_H_
-#define COSMOPOLITAN_LIBC_INTERNAL_VSSCANF_H_
-#include "libc/fmt/fmt.h"
-#include "libc/fmt/vcscanf.h"
-#if !(__ASSEMBLER__ + __LINKER__ + 0)
-
-struct StringScannerState {
- const unsigned char *s;
- size_t i;
-};
-
-static inline int vsscanfcb(void *arg) {
- struct StringScannerState *state = arg;
- int res;
- if ((res = state->s[state->i])) {
- state->i++;
- } else {
- res = -1;
- }
- return res;
-}
-
-/**
- * String decoder builder.
- *
- * This macro grants sscanf() and vsscanf() the choice to either link or
- * inline the full vcscanf() implementation.
- *
- * @see libc/fmt/vcscanf.h (for docs and implementation)
- */
-static inline int __vsscanf(const char *str, const char *fmt, va_list ap,
- int impl(int callback(void *), void *arg,
- const char *fmt, va_list ap)) {
- struct StringScannerState state = {(const unsigned char *)str, 0};
- return impl(vsscanfcb, &state, fmt, ap);
-}
-
-#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#endif /* COSMOPOLITAN_LIBC_INTERNAL_VSSCANF_H_ */
diff --git a/libc/integral/c.inc b/libc/integral/c.inc
index 5e3ea3e44..eebeda142 100644
--- a/libc/integral/c.inc
+++ b/libc/integral/c.inc
@@ -123,7 +123,7 @@ typedef _Bool bool;
#endif
#endif
-#ifndef __cplusplus
+#if !defined(__cplusplus) && !defined(__STRICT_ANSI__)
typedef __WCHAR_TYPE__ wchar_t;
typedef __CHAR16_TYPE__ char16_t;
typedef __CHAR32_TYPE__ char32_t;
@@ -205,7 +205,7 @@ typedef uint64_t uintmax_t;
#define strlenesque libcesque nosideeffect paramsnonnull()
#define vallocesque \
libcesque nodiscard returnsaligned((PAGESIZE)) returnspointerwithnoaliases
-#define reallocesque libcesque nodiscard returnsaligned((__BIGGEST_ALIGNMENT__))
+#define reallocesque libcesque returnsaligned((__BIGGEST_ALIGNMENT__))
#define mallocesque reallocesque returnspointerwithnoaliases
#define interruptfn nocallersavedregisters forcealignargpointer
@@ -530,6 +530,8 @@ typedef uint64_t uintmax_t;
/**
* Asks compiler to not optimize function definition.
+ *
+ * @todo this is dangerous delete?
*/
#ifndef nooptimize
#ifndef __STRICT_ANSI__
@@ -547,6 +549,8 @@ typedef uint64_t uintmax_t;
* Asks compiler to generate as little code as possible for function.
*
* This does the same thing as relegated, but without relocation.
+ *
+ * @todo this is dangerous delete?
*/
#ifndef optimizesize
#ifndef __STRICT_ANSI__
@@ -566,6 +570,8 @@ typedef uint64_t uintmax_t;
* This keyword provides an alternative to build flag tuning, in cases
* where the compiler is reluctant to vectorize mathematical code that's
* written in standards-compliant C rather than GCC extensions.
+ *
+ * @todo this is dangerous delete?
*/
#ifndef optimizespeed
#if !defined(__STRICT_ANSI__) && \
@@ -602,7 +608,7 @@ typedef uint64_t uintmax_t;
#endif
/**
- * Associates debug info with caller of inline function.
+ * Associates debug information with call site.
* @see nodebuginfo
*/
#ifndef artificial
@@ -772,11 +778,11 @@ typedef uint64_t uintmax_t;
#define /* TODO(jart): delete */ assume(x) __builtin_assume(x)
#endif
-#ifndef /* TODO(jart): delete */ likely
+#ifndef likely
#define likely(expr) __builtin_expect(!!(expr), 1)
#endif
-#ifndef /* TODO(jart): delete */ unlikely
+#ifndef unlikely
#define unlikely(expr) __builtin_expect(!!(expr), 0)
#endif
@@ -804,8 +810,8 @@ typedef uint64_t uintmax_t;
#ifndef __STRICT_ANSI__
#define testonly noinline _Section(".test")
-#define textstartup _Section(".text.startup")
-#define textexit _Section(".text.exit")
+#define textstartup _Section(".text.startup") noinstrument
+#define textexit _Section(".text.exit") noinstrument
#define textreal _Section(".text.real")
#define textwindows _Section(".text.windows")
#define antiquity _Section(".text.antiquity")
@@ -860,6 +866,7 @@ typedef uint64_t uintmax_t;
/**
* Systemic suppressions.
*/
+#ifndef __STRICT_ANSI__
#if defined(__GNUC__) || defined(__llvm__)
#pragma GCC diagnostic ignored "-Wsign-compare" /* lint needs to change */
#pragma GCC diagnostic ignored "-Wtype-limits" /* makes macros unsafe */
@@ -885,6 +892,9 @@ typedef uint64_t uintmax_t;
#pragma GCC diagnostic ignored /* tidy */ "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored /* tidy */ "-Wunused-but-set-parameter"
#endif /* GCC6+ */
+#if __GNUC__ + 0 >= 9
+#pragma GCC diagnostic ignored /* "always true" breaks dce */ "-Waddress"
+#endif /* GCC9+ */
#endif /* !C++ */
#endif /* GCC && !LLVM */
#ifdef __llvm__
@@ -899,6 +909,7 @@ typedef uint64_t uintmax_t;
#pragma clang diagnostic ignored \
"-Wincompatible-pointer-types-discards-qualifiers"
#endif /* !GCC && LLVM */
+#endif /* ANSI */
/**
* Elevate warnings of material consequence.
@@ -912,6 +923,7 @@ typedef uint64_t uintmax_t;
* time, e.g. 1's complement, big endian, under 32bit word size, etc.
*/
#ifndef __W__
+#ifndef __STRICT_ANSI__
#if defined(__GNUC__) || defined(__llvm__)
#pragma GCC diagnostic error "-Wpointer-arith"
#pragma GCC diagnostic error "-Wnonnull"
@@ -939,7 +951,7 @@ typedef uint64_t uintmax_t;
#pragma GCC diagnostic error "-Wredundant-decls"
#if __GNUC__ >= 6
#pragma GCC diagnostic error "-Wnonnull-compare"
-#ifndef /* we guarantee no stack overflow unless -D*/ STACK_FRAME_UNLIMITED
+#if !defined(MODE_DBG) && !defined(STACK_FRAME_UNLIMITED)
#pragma GCC diagnostic error "-Wframe-larger-than=4096"
#if __GNUC__ >= 9
#pragma GCC diagnostic error "-Walloca-larger-than=1024"
@@ -953,6 +965,7 @@ typedef uint64_t uintmax_t;
#ifdef __llvm__
#pragma clang diagnostic error "-Wassume"
#endif /* !GCC && LLVM */
+#endif /* ANSI */
#endif /* -w */
/**
diff --git a/libc/integral/normalize.inc b/libc/integral/normalize.inc
index adb7c344b..587d60085 100644
--- a/libc/integral/normalize.inc
+++ b/libc/integral/normalize.inc
@@ -62,7 +62,7 @@
#endif
#ifndef __BIGGEST_ALIGNMENT__
-#define __BIGGEST_ALIGNMENT__ 16 /* intrinsics practices need to change */
+#define __BIGGEST_ALIGNMENT__ 16
#endif
#define BIGPAGESIZE 0x200000
diff --git a/libc/internal/notice.h b/libc/internal/notice.h
index 25ffa1f9f..4bb9ab304 100644
--- a/libc/internal/notice.h
+++ b/libc/internal/notice.h
@@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_INTERNAL_NOTICE_H_
#define COSMOPOLITAN_LIBC_INTERNAL_NOTICE_H_
+#ifndef __STRICT_ANSI__
#ifdef __ASSEMBLER__
.include "libc/notice.inc"
@@ -7,4 +8,5 @@
asm(".include \"libc/notice.inc\"");
#endif
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_INTERNAL_NOTICE_H_ */
diff --git a/libc/intrin/intrin.mk b/libc/intrin/intrin.mk
index a9e9dcd8a..06594bb66 100644
--- a/libc/intrin/intrin.mk
+++ b/libc/intrin/intrin.mk
@@ -41,6 +41,10 @@ $(LIBC_INTRIN_A).pkg: \
$(LIBC_INTRIN_A_OBJS) \
$(foreach x,$(LIBC_INTRIN_A_DIRECTDEPS),$($(x)_A).pkg)
+$(LIBC_INTRIN_A_OBJS): \
+ OVERRIDE_CFLAGS += \
+ -fwrapv -O3
+
LIBC_INTRIN_LIBS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x)))
LIBC_INTRIN_HDRS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x)_HDRS))
LIBC_INTRIN_SRCS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x)_SRCS))
diff --git a/libc/intrin/macros.h b/libc/intrin/macros.h
index 01b167cb9..352574461 100644
--- a/libc/intrin/macros.h
+++ b/libc/intrin/macros.h
@@ -7,13 +7,13 @@
#define INTRIN_COMMUTATIVE "%"
#define INTRIN_NONCOMMUTATIVE
-#ifndef __STRICT_ANSI__
+#if defined(__x86_64__) && !defined(__STRICT_ANSI__)
-typedef char __intrin_xmm_t _Vector_size(16) mayalias;
+typedef char __intrin_xmm_t _Vector_size(16) aligned(16) mayalias;
#define INTRIN_SSEVEX_X_X_X_(PURE, ISA, OP, FLAGS, A, B, C) \
do { \
- if (!IsModeDbg() && X86_NEED(SSE) && X86_HAVE(ISA)) { \
+ if (!IsModeDbg() && X86_HAVE(ISA)) { \
__intrin_xmm_t *Xmm0 = (void *)(A); \
const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \
const __intrin_xmm_t *Xmm2 = (const __intrin_xmm_t *)(C); \
@@ -29,7 +29,37 @@ typedef char __intrin_xmm_t _Vector_size(16) mayalias;
#define INTRIN_SSEVEX_X_X_I_(PURE, ISA, OP, A, B, I) \
do { \
- if (!IsModeDbg() && X86_NEED(SSE) && X86_HAVE(ISA)) { \
+ if (!IsModeDbg() && X86_HAVE(ISA)) { \
+ __intrin_xmm_t *Xmm0 = (void *)(A); \
+ const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \
+ if (!X86_NEED(AVX)) { \
+ asm(OP "\t%2,%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1), "i"(I)); \
+ } else { \
+ asm("v" OP "\t%2,%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1), "i"(I)); \
+ } \
+ } else { \
+ PURE(A, B, I); \
+ } \
+ } while (0)
+
+#define INTRIN_SSEVEX_X_X_(PURE, ISA, OP, A, B) \
+ do { \
+ if (!IsModeDbg() && X86_HAVE(ISA)) { \
+ __intrin_xmm_t *Xmm0 = (void *)(A); \
+ const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \
+ if (!X86_NEED(AVX)) { \
+ asm(OP "\t%1,%0" : "=x"(*Xmm0) : "0"(*Xmm1)); \
+ } else { \
+ asm("v" OP "\t%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1)); \
+ } \
+ } else { \
+ PURE(A, B); \
+ } \
+ } while (0)
+
+#define INTRIN_SSEVEX_X_I_(PURE, ISA, OP, A, B, I) \
+ do { \
+ if (!IsModeDbg() && X86_HAVE(ISA)) { \
__intrin_xmm_t *Xmm0 = (void *)(A); \
const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \
if (!X86_NEED(AVX)) { \
@@ -45,7 +75,8 @@ typedef char __intrin_xmm_t _Vector_size(16) mayalias;
#else
#define INTRIN_SSEVEX_X_X_X_(PURE, ISA, OP, FLAGS, A, B, C) PURE(A, B, C)
#define INTRIN_SSEVEX_X_X_I_(PURE, ISA, OP, A, B, I) PURE(A, B, I)
-#endif /* ANSI */
+#define INTRIN_SSEVEX_X_I_(PURE, ISA, OP, A, B, I) PURE(A, B, I)
+#endif /* X86 && !ANSI */
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_MACROS_H_ */
diff --git a/libc/intrin/pabsb.c b/libc/intrin/pabsb.c
new file mode 100644
index 000000000..ec4ab9026
--- /dev/null
+++ b/libc/intrin/pabsb.c
@@ -0,0 +1,35 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pabsb.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Converts signed bytes to absolute values, 𝑎ᵢ ← |𝑏ᵢ|.
+ * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011)
+ */
+void(pabsb)(uint8_t a[16], const int8_t b[16]) {
+ unsigned i;
+ uint8_t r[16];
+ for (i = 0; i < 16; ++i) {
+ r[i] = ABS(b[i]);
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pabsb.h b/libc/intrin/pabsb.h
new file mode 100644
index 000000000..81e3cb82e
--- /dev/null
+++ b/libc/intrin/pabsb.h
@@ -0,0 +1,13 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PABSB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PABSB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pabsb(uint8_t[16], const int8_t[16]);
+
+#define pabsb(A, B) INTRIN_SSEVEX_X_X_(pabsb, SSSE3, "pabsb", A, B)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PABSB_H_ */
diff --git a/libc/intrin/pabsd.c b/libc/intrin/pabsd.c
new file mode 100644
index 000000000..0b9cf2e98
--- /dev/null
+++ b/libc/intrin/pabsd.c
@@ -0,0 +1,35 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pabsd.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Converts shorts to absolute values, 𝑎ᵢ ← |𝑏ᵢ|.
+ * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011)
+ */
+void(pabsd)(uint32_t a[4], const int32_t b[4]) {
+ unsigned i;
+ uint32_t r[4];
+ for (i = 0; i < 4; ++i) {
+ r[i] = ABS(b[i]);
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pabsd.h b/libc/intrin/pabsd.h
new file mode 100644
index 000000000..5988396dc
--- /dev/null
+++ b/libc/intrin/pabsd.h
@@ -0,0 +1,13 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PABSD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PABSD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pabsd(uint32_t[4], const int32_t[4]);
+
+#define pabsd(A, B) INTRIN_SSEVEX_X_X_(pabsd, SSSE3, "pabsd", A, B)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PABSD_H_ */
diff --git a/libc/intrin/pabsw.c b/libc/intrin/pabsw.c
new file mode 100644
index 000000000..59f948cae
--- /dev/null
+++ b/libc/intrin/pabsw.c
@@ -0,0 +1,35 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pabsw.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Converts shorts to absolute values, 𝑎ᵢ ← |𝑏ᵢ|.
+ * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011)
+ */
+void(pabsw)(uint16_t a[8], const int16_t b[8]) {
+ unsigned i;
+ uint16_t r[8];
+ for (i = 0; i < 8; ++i) {
+ r[i] = ABS(b[i]);
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pabsw.h b/libc/intrin/pabsw.h
new file mode 100644
index 000000000..f4fd38463
--- /dev/null
+++ b/libc/intrin/pabsw.h
@@ -0,0 +1,13 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PABSW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PABSW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pabsw(uint16_t[8], const int16_t[8]);
+
+#define pabsw(A, B) INTRIN_SSEVEX_X_X_(pabsw, SSSE3, "pabsw", A, B)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PABSW_H_ */
diff --git a/libc/nexgen32e/tinystrncmp.ncabi.c b/libc/intrin/packssdw.c
similarity index 84%
rename from libc/nexgen32e/tinystrncmp.ncabi.c
rename to libc/intrin/packssdw.c
index 31c28312e..3aa41288a 100644
--- a/libc/nexgen32e/tinystrncmp.ncabi.c
+++ b/libc/intrin/packssdw.c
@@ -17,19 +17,16 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/packssdw.h"
#include "libc/limits.h"
-#include "libc/nexgen32e/tinystrcmp.h"
+#include "libc/macros.h"
/**
- * Compares strings w/ limit & no-clobber abi guarantee.
+ * Casts ints to shorts w/ saturation.
+ * @mayalias
*/
-int(tinystrncmp)(const char *s1, const char *s2, size_t n) {
- size_t i;
- if (!n) return 0;
- i = SIZE_MAX;
- while (s1[++i]) {
- if (s1[i] != s2[i]) break;
- if (!--n) break;
- }
- return (int)(unsigned char)s1[i] - (int)(unsigned char)s2[i];
+void(packssdw)(int16_t a[8], const int32_t b[4], const int32_t c[4]) {
+ unsigned i;
+ for (i = 0; i < 4; ++i) a[i + 0] = MIN(INT16_MAX, MAX(INT16_MIN, b[i]));
+ for (i = 0; i < 4; ++i) a[i + 4] = MIN(INT16_MAX, MAX(INT16_MIN, c[i]));
}
diff --git a/libc/intrin/packssdw.h b/libc/intrin/packssdw.h
new file mode 100644
index 000000000..4d014984c
--- /dev/null
+++ b/libc/intrin/packssdw.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PACKSSDW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PACKSSDW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void packssdw(int16_t[8], const int32_t[4], const int32_t[4]);
+
+#define packssdw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(packssdw, SSE2, "packssdw", INTRIN_NONCOMMUTATIVE, A, \
+ B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PACKSSDW_H_ */
diff --git a/test/libc/runtime/heapsortcar_test.c b/libc/intrin/packsswb.c
similarity index 78%
rename from test/libc/runtime/heapsortcar_test.c
rename to libc/intrin/packsswb.c
index fab29e3b0..53647567a 100644
--- a/test/libc/runtime/heapsortcar_test.c
+++ b/libc/intrin/packsswb.c
@@ -17,19 +17,23 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/alg/alg.h"
-#include "libc/bits/bits.h"
+#include "libc/intrin/packsswb.h"
+#include "libc/limits.h"
#include "libc/macros.h"
#include "libc/str/str.h"
-#include "libc/testlib/testlib.h"
-TEST(heapsortcar, test) {
- int32_t A[][2] = {{4, 'a'}, {65, 'b'}, {2, 'c'}, {-31, 'd'}, {0, 'e'},
- {99, 'f'}, {2, 'g'}, {83, 'h'}, {782, 'i'}, {1, 'j'}};
- const int32_t B[][2] = {{-31, 'd'}, {0, 'e'}, {1, 'j'}, {2, 'c'},
- {2, 'g'}, {4, 'a'}, {65, 'b'}, {83, 'h'},
- {99, 'f'}, {782, 'i'}};
- unsigned n = ARRAYLEN(A);
- heapsortcar(A, n);
- ASSERT_EQ(0, memcmp(&A[0], &B[0], sizeof(A)));
+/**
+ * Casts shorts to signed chars w/ saturation.
+ *
+ * 𝑎 ← {CLAMP[𝑏ᵢ]|𝑖∈[0,4)} ║ {CLAMP[𝑐ᵢ]|𝑖∈[4,8)}
+ *
+ * @see packuswb()
+ * @mayalias
+ */
+void(packsswb)(int8_t a[16], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ int8_t r[16];
+ for (i = 0; i < 8; ++i) r[i + 0] = MIN(INT8_MAX, MAX(INT8_MIN, b[i]));
+ for (i = 0; i < 8; ++i) r[i + 8] = MIN(INT8_MAX, MAX(INT8_MIN, c[i]));
+ memcpy(a, r, 16);
}
diff --git a/libc/intrin/packsswb.h b/libc/intrin/packsswb.h
index fa72939a5..c39b7f749 100644
--- a/libc/intrin/packsswb.h
+++ b/libc/intrin/packsswb.h
@@ -1,30 +1,15 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_PACKSSWB_H_
#define COSMOPOLITAN_LIBC_INTRIN_PACKSSWB_H_
#include "libc/intrin/macros.h"
-#include "libc/macros.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
-/**
- * Casts shorts to signed chars w/ saturation.
- *
- * 𝑎 ← {CLAMP[𝑏ᵢ]|𝑖∈[0,4)} ║ {CLAMP[𝑐ᵢ]|𝑖∈[4,8)}
- *
- * @see packuswb()
- * @mayalias
- */
-static void packsswb(signed char a[16], const short b[8], const short c[8]) {
- int i;
- for (i = 0; i < 8; ++i) {
- a[i] = MIN(127, MAX(-128, b[i]));
- }
- for (i = 0; i < 8; ++i) {
- a[i + 8] = MIN(127, MAX(-128, c[i]));
- }
-}
+void packsswb(int8_t[16], const int16_t[8], const int16_t[8]);
#define packsswb(A, B, C) \
INTRIN_SSEVEX_X_X_X_(packsswb, SSE2, "packsswb", INTRIN_NONCOMMUTATIVE, A, \
B, C)
+COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_PACKSSWB_H_ */
diff --git a/libc/intrin/packusdw.c b/libc/intrin/packusdw.c
new file mode 100644
index 000000000..2be957d2d
--- /dev/null
+++ b/libc/intrin/packusdw.c
@@ -0,0 +1,35 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/packusdw.h"
+#include "libc/limits.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Casts ints to shorts w/ saturation.
+ * @mayalias
+ */
+void(packusdw)(uint16_t a[8], const int32_t b[4], const int32_t c[4]) {
+ unsigned i;
+ uint16_t r[8];
+ for (i = 0; i < 4; ++i) r[i + 0] = MIN(UINT16_MAX, MAX(UINT16_MIN, b[i]));
+ for (i = 0; i < 4; ++i) r[i + 4] = MIN(UINT16_MAX, MAX(UINT16_MIN, c[i]));
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/packusdw.h b/libc/intrin/packusdw.h
new file mode 100644
index 000000000..c9847ae87
--- /dev/null
+++ b/libc/intrin/packusdw.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PACKUSDW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PACKUSDW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void packusdw(uint16_t[8], const int32_t[4], const int32_t[4]);
+
+#define packusdw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(packusdw, SSE4_1, "packusdw", INTRIN_NONCOMMUTATIVE, A, \
+ B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PACKUSDW_H_ */
diff --git a/libc/intrin/packuswb.c b/libc/intrin/packuswb.c
new file mode 100644
index 000000000..359c67272
--- /dev/null
+++ b/libc/intrin/packuswb.c
@@ -0,0 +1,39 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/packuswb.h"
+#include "libc/limits.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Casts shorts to unsigned chars w/ saturation.
+ *
+ * 𝑎 ← {CLAMP[𝑏ᵢ]|𝑖∈[0,4)} ║ {CLAMP[𝑐ᵢ]|𝑖∈[4,8)}
+ *
+ * @see packsswb()
+ * @mayalias
+ */
+void(packuswb)(uint8_t a[16], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ uint8_t r[16];
+ for (i = 0; i < 8; ++i) r[i + 0] = MIN(UINT8_MAX, MAX(UINT8_MIN, b[i]));
+ for (i = 0; i < 8; ++i) r[i + 8] = MIN(UINT8_MAX, MAX(UINT8_MIN, c[i]));
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/packuswb.h b/libc/intrin/packuswb.h
index cb3553b2f..405de694b 100644
--- a/libc/intrin/packuswb.h
+++ b/libc/intrin/packuswb.h
@@ -1,30 +1,15 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_PACKUSWB_H_
#define COSMOPOLITAN_LIBC_INTRIN_PACKUSWB_H_
#include "libc/intrin/macros.h"
-#include "libc/macros.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
-/**
- * Casts shorts to unsigned chars w/ saturation.
- *
- * 𝑎 ← {CLAMP[𝑏ᵢ]|𝑖∈[0,4)} ║ {CLAMP[𝑐ᵢ]|𝑖∈[4,8)}
- *
- * @see packsswb()
- * @mayalias
- */
-static void packuswb(unsigned char a[16], const short b[8], const short c[8]) {
- int i;
- for (i = 0; i < 8; ++i) {
- a[i] = MIN(255, MAX(0, b[i]));
- }
- for (i = 0; i < 8; ++i) {
- a[i + 8] = MIN(255, MAX(0, c[i]));
- }
-}
+void packuswb(uint8_t[16], const int16_t[8], const int16_t[8]);
#define packuswb(A, B, C) \
INTRIN_SSEVEX_X_X_X_(packuswb, SSE2, "packuswb", INTRIN_NONCOMMUTATIVE, A, \
B, C)
+COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_PACKUSWB_H_ */
diff --git a/libc/intrin/paddb.c b/libc/intrin/paddb.c
new file mode 100644
index 000000000..2d58a7341
--- /dev/null
+++ b/libc/intrin/paddb.c
@@ -0,0 +1,36 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/paddb.h"
+#include "libc/str/str.h"
+
+/**
+ * Adds 8-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(paddb)(int8_t a[16], const int8_t b[16], const int8_t c[16]) {
+ unsigned i;
+ int8_t r[16];
+ for (i = 0; i < 16; ++i) r[i] = b[i] + c[i];
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/paddb.h b/libc/intrin/paddb.h
new file mode 100644
index 000000000..68698e3a2
--- /dev/null
+++ b/libc/intrin/paddb.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PADDB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void paddb(int8_t[16], const int8_t[16], const int8_t[16]);
+
+#define paddb(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(paddb, SSE2, "paddb", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDB_H_ */
diff --git a/libc/intrin/paddd.c b/libc/intrin/paddd.c
new file mode 100644
index 000000000..79ab1637c
--- /dev/null
+++ b/libc/intrin/paddd.c
@@ -0,0 +1,36 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/paddd.h"
+#include "libc/str/str.h"
+
+/**
+ * Adds 32-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(paddd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) {
+ unsigned i;
+ int32_t r[4];
+ for (i = 0; i < 4; ++i) r[i] = b[i] + c[i];
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/paddd.h b/libc/intrin/paddd.h
new file mode 100644
index 000000000..110d5807c
--- /dev/null
+++ b/libc/intrin/paddd.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PADDD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void paddd(int32_t[4], const int32_t[4], const int32_t[4]);
+
+#define paddd(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(paddd, SSE2, "paddd", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDD_H_ */
diff --git a/libc/intrin/paddq.c b/libc/intrin/paddq.c
new file mode 100644
index 000000000..f3e839f13
--- /dev/null
+++ b/libc/intrin/paddq.c
@@ -0,0 +1,36 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/paddq.h"
+#include "libc/str/str.h"
+
+/**
+ * Adds 64-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(paddq)(int64_t a[2], const int64_t b[2], const int64_t c[2]) {
+ unsigned i;
+ int64_t r[2];
+ for (i = 0; i < 2; ++i) r[i] = b[i] + c[i];
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/paddq.h b/libc/intrin/paddq.h
new file mode 100644
index 000000000..906f460b9
--- /dev/null
+++ b/libc/intrin/paddq.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDQ_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PADDQ_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void paddq(int64_t[2], const int64_t[2], const int64_t[2]);
+
+#define paddq(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(paddq, SSE2, "paddq", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDQ_H_ */
diff --git a/libc/intrin/paddsb.c b/libc/intrin/paddsb.c
new file mode 100644
index 000000000..710c1c3e1
--- /dev/null
+++ b/libc/intrin/paddsb.c
@@ -0,0 +1,40 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/paddsb.h"
+#include "libc/limits.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Adds signed 8-bit integers w/ saturation.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(paddsb)(int8_t a[16], const int8_t b[16], const int8_t c[16]) {
+ unsigned i;
+ int8_t r[16];
+ for (i = 0; i < 16; ++i) {
+ r[i] = MIN(INT8_MAX, MAX(INT8_MIN, b[i] + c[i]));
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/paddsb.h b/libc/intrin/paddsb.h
new file mode 100644
index 000000000..10cabb1a8
--- /dev/null
+++ b/libc/intrin/paddsb.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDSB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PADDSB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void paddsb(int8_t[16], const int8_t[16], const int8_t[16]);
+
+#define paddsb(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(paddsb, SSE2, "paddsb", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDSB_H_ */
diff --git a/libc/intrin/paddsw.c b/libc/intrin/paddsw.c
new file mode 100644
index 000000000..50fb9149a
--- /dev/null
+++ b/libc/intrin/paddsw.c
@@ -0,0 +1,40 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/paddsw.h"
+#include "libc/limits.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Adds signed 16-bit integers w/ saturation.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(paddsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ int16_t r[8];
+ for (i = 0; i < 8; ++i) {
+ r[i] = MIN(SHRT_MAX, MAX(SHRT_MIN, b[i] + c[i]));
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/paddsw.h b/libc/intrin/paddsw.h
index 71d0a49d5..a500e4364 100644
--- a/libc/intrin/paddsw.h
+++ b/libc/intrin/paddsw.h
@@ -1,28 +1,14 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDSW_H_
#define COSMOPOLITAN_LIBC_INTRIN_PADDSW_H_
#include "libc/intrin/macros.h"
-#include "libc/limits.h"
-#include "libc/macros.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
-/**
- * Adds signed 16-bit integers w/ saturation.
- *
- * @param 𝑎 [w/o] receives result
- * @param 𝑏 [r/o] supplies first input vector
- * @param 𝑐 [r/o] supplies second input vector
- * @see paddw()
- * @mayalias
- */
-static void paddsw(short a[8], const short b[8], const short c[8]) {
- int i;
- for (i = 0; i < 8; ++i) {
- a[i] = MIN(SHRT_MAX, MAX(SHRT_MIN, b[i] + c[i]));
- }
-}
+void paddsw(int16_t[8], const int16_t[8], const int16_t[8]);
#define paddsw(A, B, C) \
INTRIN_SSEVEX_X_X_X_(paddsw, SSE2, "paddsw", INTRIN_COMMUTATIVE, A, B, C)
+COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDSW_H_ */
diff --git a/libc/intrin/paddusb.c b/libc/intrin/paddusb.c
new file mode 100644
index 000000000..4904e0a7b
--- /dev/null
+++ b/libc/intrin/paddusb.c
@@ -0,0 +1,40 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/paddusb.h"
+#include "libc/limits.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Adds unsigned 8-bit integers w/ saturation.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(paddusb)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) {
+ unsigned i;
+ uint8_t r[16];
+ for (i = 0; i < 16; ++i) {
+ r[i] = MIN(UINT8_MAX, b[i] + c[i]);
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/paddusb.h b/libc/intrin/paddusb.h
new file mode 100644
index 000000000..8c6b300aa
--- /dev/null
+++ b/libc/intrin/paddusb.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDUSB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PADDUSB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void paddusb(uint8_t[16], const uint8_t[16], const uint8_t[16]);
+
+#define paddusb(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(paddusb, SSE2, "paddusb", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDUSB_H_ */
diff --git a/libc/intrin/paddusw.c b/libc/intrin/paddusw.c
new file mode 100644
index 000000000..ae46f00cc
--- /dev/null
+++ b/libc/intrin/paddusw.c
@@ -0,0 +1,40 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/paddusw.h"
+#include "libc/limits.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Adds unsigned 16-bit integers w/ saturation.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(paddusw)(uint16_t a[8], const uint16_t b[8], const uint16_t c[8]) {
+ unsigned i;
+ uint16_t r[8];
+ for (i = 0; i < 8; ++i) {
+ r[i] = MIN(UINT16_MAX, b[i] + c[i]);
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/paddusw.h b/libc/intrin/paddusw.h
new file mode 100644
index 000000000..1b47caf71
--- /dev/null
+++ b/libc/intrin/paddusw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDUSW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PADDUSW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void paddusw(uint16_t[8], const uint16_t[8], const uint16_t[8]);
+
+#define paddusw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(paddusw, SSE2, "paddusw", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDUSW_H_ */
diff --git a/libc/intrin/paddw.c b/libc/intrin/paddw.c
new file mode 100644
index 000000000..480081217
--- /dev/null
+++ b/libc/intrin/paddw.c
@@ -0,0 +1,40 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/paddw.h"
+#include "libc/str/str.h"
+
+/**
+ * Adds 16-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @note shorts can't overflow so ubsan won't report it when it happens
+ * @see paddsw()
+ * @mayalias
+ */
+void(paddw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ int16_t r[8];
+ for (i = 0; i < 8; ++i) {
+ r[i] = b[i] + c[i];
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/paddw.h b/libc/intrin/paddw.h
index d67c72a58..755d4c172 100644
--- a/libc/intrin/paddw.h
+++ b/libc/intrin/paddw.h
@@ -1,28 +1,14 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDW_H_
#define COSMOPOLITAN_LIBC_INTRIN_PADDW_H_
#include "libc/intrin/macros.h"
-#include "libc/str/str.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
-/**
- * Adds signed 16-bit integers.
- *
- * @param 𝑎 [w/o] receives result
- * @param 𝑏 [r/o] supplies first input vector
- * @param 𝑐 [r/o] supplies second input vector
- * @note shorts can't overflow so ubsan won't report it when it happens
- * @see paddsw()
- * @mayalias
- */
-static void paddw(short a[8], const short b[8], const short c[8]) {
- int i;
- for (i = 0; i < 8; ++i) {
- a[i] = b[i] + c[i];
- }
-}
+void paddw(int16_t[8], const int16_t[8], const int16_t[8]);
#define paddw(A, B, C) \
INTRIN_SSEVEX_X_X_X_(paddw, SSE2, "paddw", INTRIN_COMMUTATIVE, A, B, C)
+COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDW_H_ */
diff --git a/libc/intrin/palignr.c b/libc/intrin/palignr.c
new file mode 100644
index 000000000..c10b5c287
--- /dev/null
+++ b/libc/intrin/palignr.c
@@ -0,0 +1,44 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/assert.h"
+#include "libc/intrin/palignr.h"
+#include "libc/macros.h"
+
+/**
+ * Overlaps vectors.
+ *
+ * 𝑖= 0 means 𝑐←𝑎
+ * 0<𝑖<16 means 𝑐←𝑎║𝑏
+ * 𝑖=16 means 𝑐←𝑏
+ * 16<𝑖<32 means 𝑐←𝑏║0
+ * 𝑖≥32 means 𝑐←0
+ *
+ * @param 𝑖 goes faster as constexpr
+ * @note not compatible with mmx
+ * @see pvalignr()
+ * @mayalias
+ */
+void(palignr)(void *c, const void *b, const void *a, unsigned long i) {
+ char t[48];
+ memcpy(t, a, 16);
+ memcpy(t + 16, b, 16);
+ memset(t + 32, 0, 16);
+ memcpy(c, t + MIN(i, 32), 16);
+}
diff --git a/libc/intrin/palignr.h b/libc/intrin/palignr.h
index bba2a4d00..9ece960a0 100644
--- a/libc/intrin/palignr.h
+++ b/libc/intrin/palignr.h
@@ -1,55 +1,46 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_PALIGNR_H_
#define COSMOPOLITAN_LIBC_INTRIN_PALIGNR_H_
-#include "libc/assert.h"
#include "libc/intrin/macros.h"
-#include "libc/macros.h"
#include "libc/str/str.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
-void pvalignr(void *, const void *, const void *, size_t);
-
-/**
- * Overlaps vectors.
- *
- * 𝑖= 0 means 𝑐←𝑎
- * 0<𝑖<16 means 𝑐←𝑎║𝑏
- * 𝑖=16 means 𝑐←𝑏
- * 16<𝑖<32 means 𝑐←𝑏║0
- * 𝑖≥32 means 𝑐←0
- *
- * @param 𝑖 needs to be a literal, constexpr, or embedding
- * @see pvalignr()
- * @mayalias
- */
-static void palignr(void *c, const void *b, const void *a, size_t i) {
- char t[48];
- memcpy(t, a, 16);
- memcpy(t + 16, b, 16);
- memset(t + 32, 0, 16);
- memcpy(c, t + MIN(32, i), 16);
-}
+void palignr(void *, const void *, const void *, unsigned long);
#ifndef __STRICT_ANSI__
-#define palignr(C, B, A, I) \
- do { \
- if (!IsModeDbg() && X86_NEED(SSE) && X86_HAVE(SSSE3)) { \
- __intrin_xmm_t *Xmm0 = (void *)(C); \
- const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \
- const __intrin_xmm_t *Xmm2 = (const __intrin_xmm_t *)(A); \
- if (!X86_NEED(AVX)) { \
- asm("palignr\t%2,%1,%0" \
- : "=x"(*Xmm0) \
- : "x"(*Xmm2), "i"(I), "0"(*Xmm1)); \
- } else { \
- asm("vpalignr\t%3,%2,%1,%0" \
- : "=x"(*Xmm0) \
- : "x"(*Xmm1), "x"(*Xmm2), "i"(I)); \
- } \
- } else { \
- palignr(C, B, A, I); \
- } \
+__intrin_xmm_t __palignrs(__intrin_xmm_t, __intrin_xmm_t);
+#define palignr(C, B, A, I) \
+ do { \
+ if (likely(!IsModeDbg() && X86_NEED(SSE) && X86_HAVE(SSSE3))) { \
+ __intrin_xmm_t *Xmm0 = (void *)(C); \
+ const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \
+ const __intrin_xmm_t *Xmm2 = (const __intrin_xmm_t *)(A); \
+ if (isconstant(I)) { \
+ if (!X86_NEED(AVX)) { \
+ asm("palignr\t%2,%1,%0" \
+ : "=x"(*Xmm0) \
+ : "x"(*Xmm2), "i"(I), "0"(*Xmm1)); \
+ } else { \
+ asm("vpalignr\t%3,%2,%1,%0" \
+ : "=x"(*Xmm0) \
+ : "x"(*Xmm1), "x"(*Xmm2), "i"(I)); \
+ } \
+ } else { \
+ unsigned long Vimm = (I); \
+ typeof(__palignrs) *Fn; \
+ if (likely(Vimm < 32)) { \
+ Fn = (typeof(__palignrs) *)((uintptr_t)&__palignrs + Vimm * 8); \
+ *Xmm0 = Fn(*Xmm1, *Xmm2); \
+ } else { \
+ memset(Xmm0, 0, 16); \
+ } \
+ } \
+ } else { \
+ palignr(C, B, A, I); \
+ } \
} while (0)
#endif
+COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_PALIGNR_H_ */
diff --git a/libc/intrin/palignrs.S b/libc/intrin/palignrs.S
new file mode 100644
index 000000000..9119bce5d
--- /dev/null
+++ b/libc/intrin/palignrs.S
@@ -0,0 +1,126 @@
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/macros.h"
+
+/ Jump table for palignr() with non-constexpr immediate parameter.
+/
+/ @note needs ssse3 cf. prescott c. 2004 cf. bulldozer c. 2011
+/ @see palignr()
+ .align 8
+__palignrs:
+ palignr $0,%xmm1,%xmm0
+ ret
+ nop
+ palignr $1,%xmm1,%xmm0
+ ret
+ nop
+ palignr $2,%xmm1,%xmm0
+ ret
+ nop
+ palignr $3,%xmm1,%xmm0
+ ret
+ nop
+ palignr $4,%xmm1,%xmm0
+ ret
+ nop
+ palignr $5,%xmm1,%xmm0
+ ret
+ nop
+ palignr $6,%xmm1,%xmm0
+ ret
+ nop
+ palignr $7,%xmm1,%xmm0
+ ret
+ nop
+ palignr $8,%xmm1,%xmm0
+ ret
+ nop
+ palignr $9,%xmm1,%xmm0
+ ret
+ nop
+ palignr $10,%xmm1,%xmm0
+ ret
+ nop
+ palignr $11,%xmm1,%xmm0
+ ret
+ nop
+ palignr $12,%xmm1,%xmm0
+ ret
+ nop
+ palignr $13,%xmm1,%xmm0
+ ret
+ nop
+ palignr $14,%xmm1,%xmm0
+ ret
+ nop
+ palignr $15,%xmm1,%xmm0
+ ret
+ nop
+ palignr $16,%xmm1,%xmm0
+ ret
+ nop
+ palignr $17,%xmm1,%xmm0
+ ret
+ nop
+ palignr $18,%xmm1,%xmm0
+ ret
+ nop
+ palignr $19,%xmm1,%xmm0
+ ret
+ nop
+ palignr $20,%xmm1,%xmm0
+ ret
+ nop
+ palignr $21,%xmm1,%xmm0
+ ret
+ nop
+ palignr $22,%xmm1,%xmm0
+ ret
+ nop
+ palignr $23,%xmm1,%xmm0
+ ret
+ nop
+ palignr $24,%xmm1,%xmm0
+ ret
+ nop
+ palignr $25,%xmm1,%xmm0
+ ret
+ nop
+ palignr $26,%xmm1,%xmm0
+ ret
+ nop
+ palignr $27,%xmm1,%xmm0
+ ret
+ nop
+ palignr $28,%xmm1,%xmm0
+ ret
+ nop
+ palignr $29,%xmm1,%xmm0
+ ret
+ nop
+ palignr $30,%xmm1,%xmm0
+ ret
+ nop
+ palignr $31,%xmm1,%xmm0
+ ret
+ .if . - __palignrs != 8 * 32 - 1
+ .error "bad assemblage"
+ .endif
+ .endfn __palignrs,globl
diff --git a/libc/intrin/pand.c b/libc/intrin/pand.c
new file mode 100644
index 000000000..3017343bc
--- /dev/null
+++ b/libc/intrin/pand.c
@@ -0,0 +1,35 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pand.h"
+
+/**
+ * Nands 128-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pand)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) {
+ unsigned i;
+ for (i = 0; i < 2; ++i) {
+ a[i] = b[i] & c[i];
+ }
+}
diff --git a/libc/intrin/pand.h b/libc/intrin/pand.h
new file mode 100644
index 000000000..0edfee7c7
--- /dev/null
+++ b/libc/intrin/pand.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PAND_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PAND_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pand(uint64_t[2], const uint64_t[2], const uint64_t[2]);
+
+#define pand(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pand, SSE2, "pand", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PAND_H_ */
diff --git a/libc/intrin/pandn.c b/libc/intrin/pandn.c
new file mode 100644
index 000000000..3ac1ed737
--- /dev/null
+++ b/libc/intrin/pandn.c
@@ -0,0 +1,35 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pandn.h"
+
+/**
+ * Nands 128-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pandn)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) {
+ unsigned i;
+ for (i = 0; i < 2; ++i) {
+ a[i] = ~b[i] & c[i];
+ }
+}
diff --git a/libc/intrin/pandn.h b/libc/intrin/pandn.h
new file mode 100644
index 000000000..474d77a9a
--- /dev/null
+++ b/libc/intrin/pandn.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PANDN_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PANDN_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pandn(uint64_t[2], const uint64_t[2], const uint64_t[2]);
+
+#define pandn(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pandn, SSE2, "pandn", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PANDN_H_ */
diff --git a/libc/intrin/pavgb.c b/libc/intrin/pavgb.c
new file mode 100644
index 000000000..0c3ab7dbe
--- /dev/null
+++ b/libc/intrin/pavgb.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pavgb.h"
+#include "libc/str/str.h"
+
+/**
+ * Averages packed 8-bit unsigned integers w/ rounding.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pavgb)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) {
+ unsigned i;
+ uint8_t r[16];
+ for (i = 0; i < 16; ++i) {
+ r[i] = (b[i] + c[i] + 1) >> 1;
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pavgb.h b/libc/intrin/pavgb.h
new file mode 100644
index 000000000..8f68e6550
--- /dev/null
+++ b/libc/intrin/pavgb.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PAVGB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PAVGB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pavgb(uint8_t[16], const uint8_t[16], const uint8_t[16]);
+
+#define pavgb(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pavgb, SSE2, "pavgb", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PAVGB_H_ */
diff --git a/libc/intrin/pavgw.c b/libc/intrin/pavgw.c
new file mode 100644
index 000000000..3e2fed024
--- /dev/null
+++ b/libc/intrin/pavgw.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pavgw.h"
+#include "libc/str/str.h"
+
+/**
+ * Averages packed 16-bit unsigned integers w/ rounding.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pavgw)(uint16_t a[8], const uint16_t b[8], const uint16_t c[8]) {
+ unsigned i;
+ int16_t r[8];
+ for (i = 0; i < 8; ++i) {
+ r[i] = (b[i] + c[i] + 1) >> 1;
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pavgw.h b/libc/intrin/pavgw.h
new file mode 100644
index 000000000..d13662e61
--- /dev/null
+++ b/libc/intrin/pavgw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PAVGW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PAVGW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pavgw(uint16_t[8], const uint16_t[8], const uint16_t[8]);
+
+#define pavgw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pavgw, SSE2, "pavgw", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PAVGW_H_ */
diff --git a/libc/intrin/pcmpeqb.c b/libc/intrin/pcmpeqb.c
new file mode 100644
index 000000000..5e81ea416
--- /dev/null
+++ b/libc/intrin/pcmpeqb.c
@@ -0,0 +1,36 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pcmpeqb.h"
+#include "libc/str/str.h"
+
+/**
+ * Compares signed 8-bit integers w/ equal to predicate.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pcmpeqb)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) {
+ unsigned i;
+ uint8_t r[16];
+ for (i = 0; i < 16; ++i) r[i] = -(b[i] == c[i]);
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pcmpeqb.h b/libc/intrin/pcmpeqb.h
new file mode 100644
index 000000000..1c42f60e7
--- /dev/null
+++ b/libc/intrin/pcmpeqb.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PCMPEQB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PCMPEQB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pcmpeqb(uint8_t[16], const uint8_t[16], const uint8_t[16]);
+
+#define pcmpeqb(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pcmpeqb, SSE2, "pcmpeqb", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PCMPEQB_H_ */
diff --git a/libc/intrin/pcmpeqd.c b/libc/intrin/pcmpeqd.c
new file mode 100644
index 000000000..ff5e6bb7a
--- /dev/null
+++ b/libc/intrin/pcmpeqd.c
@@ -0,0 +1,36 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pcmpeqd.h"
+#include "libc/str/str.h"
+
+/**
+ * Compares signed 32-bit integers w/ equal to predicate.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pcmpeqd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) {
+ unsigned i;
+ int32_t r[4];
+ for (i = 0; i < 4; ++i) r[i] = -(b[i] == c[i]);
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pcmpeqd.h b/libc/intrin/pcmpeqd.h
new file mode 100644
index 000000000..87f2bba03
--- /dev/null
+++ b/libc/intrin/pcmpeqd.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PCMPEQD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PCMPEQD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pcmpeqd(int32_t[4], const int32_t[4], const int32_t[4]);
+
+#define pcmpeqd(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pcmpeqd, SSE2, "pcmpeqd", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PCMPEQD_H_ */
diff --git a/libc/intrin/pcmpeqw.c b/libc/intrin/pcmpeqw.c
new file mode 100644
index 000000000..a5666c4ad
--- /dev/null
+++ b/libc/intrin/pcmpeqw.c
@@ -0,0 +1,36 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pcmpeqw.h"
+#include "libc/str/str.h"
+
+/**
+ * Compares signed 16-bit integers w/ equal to predicate.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pcmpeqw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ int16_t r[8];
+ for (i = 0; i < 8; ++i) r[i] = -(b[i] == c[i]);
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pcmpeqw.h b/libc/intrin/pcmpeqw.h
new file mode 100644
index 000000000..acd33799c
--- /dev/null
+++ b/libc/intrin/pcmpeqw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PCMPEQW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PCMPEQW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pcmpeqw(int16_t[8], const int16_t[8], const int16_t[8]);
+
+#define pcmpeqw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pcmpeqw, SSE2, "pcmpeqw", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PCMPEQW_H_ */
diff --git a/libc/intrin/pcmpgtb.c b/libc/intrin/pcmpgtb.c
new file mode 100644
index 000000000..3dd7c3ae1
--- /dev/null
+++ b/libc/intrin/pcmpgtb.c
@@ -0,0 +1,36 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pcmpgtb.h"
+#include "libc/str/str.h"
+
+/**
+ * Compares signed 8-bit integers w/ greater than predicate.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pcmpgtb)(int8_t a[16], const int8_t b[16], const int8_t c[16]) {
+ unsigned i;
+ int8_t r[16];
+ for (i = 0; i < 16; ++i) r[i] = -(b[i] > c[i]);
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pcmpgtb.h b/libc/intrin/pcmpgtb.h
new file mode 100644
index 000000000..c9f4c4812
--- /dev/null
+++ b/libc/intrin/pcmpgtb.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PCMPGTB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PCMPGTB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pcmpgtb(int8_t[16], const int8_t[16], const int8_t[16]);
+
+#define pcmpgtb(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pcmpgtb, SSE2, "pcmpgtb", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PCMPGTB_H_ */
diff --git a/libc/intrin/pcmpgtd.c b/libc/intrin/pcmpgtd.c
new file mode 100644
index 000000000..c434acdb6
--- /dev/null
+++ b/libc/intrin/pcmpgtd.c
@@ -0,0 +1,36 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pcmpgtd.h"
+#include "libc/str/str.h"
+
+/**
+ * Compares signed 32-bit integers w/ greater than predicate.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pcmpgtd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) {
+ unsigned i;
+ int32_t r[4];
+ for (i = 0; i < 4; ++i) r[i] = -(b[i] > c[i]);
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pcmpgtd.h b/libc/intrin/pcmpgtd.h
new file mode 100644
index 000000000..728824e42
--- /dev/null
+++ b/libc/intrin/pcmpgtd.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PCMPGTD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PCMPGTD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pcmpgtd(int32_t[4], const int32_t[4], const int32_t[4]);
+
+#define pcmpgtd(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pcmpgtd, SSE2, "pcmpgtd", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PCMPGTD_H_ */
diff --git a/libc/intrin/pcmpgtw.c b/libc/intrin/pcmpgtw.c
new file mode 100644
index 000000000..669f53131
--- /dev/null
+++ b/libc/intrin/pcmpgtw.c
@@ -0,0 +1,36 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pcmpgtw.h"
+#include "libc/str/str.h"
+
+/**
+ * Compares signed 16-bit integers w/ greater than predicate.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pcmpgtw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ int16_t r[8];
+ for (i = 0; i < 8; ++i) r[i] = -(b[i] > c[i]);
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pcmpgtw.h b/libc/intrin/pcmpgtw.h
new file mode 100644
index 000000000..a518acaca
--- /dev/null
+++ b/libc/intrin/pcmpgtw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PCMPGTW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PCMPGTW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pcmpgtw(int16_t[8], const int16_t[8], const int16_t[8]);
+
+#define pcmpgtw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pcmpgtw, SSE2, "pcmpgtw", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PCMPGTW_H_ */
diff --git a/libc/intrin/pdep.h b/libc/intrin/pdep.h
index b780837b0..1ae356724 100644
--- a/libc/intrin/pdep.h
+++ b/libc/intrin/pdep.h
@@ -1,6 +1,7 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_PDEP_H_
#define COSMOPOLITAN_LIBC_INTRIN_PDEP_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
/* TODO(jart): Implement polyfill. */
#define pdep(NUMBER, BITMASK) \
@@ -10,5 +11,6 @@
ShuffledBits; \
})
+COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_PDEP_H_ */
diff --git a/libc/intrin/pext.h b/libc/intrin/pext.h
index cab29eaaa..00dcf15aa 100644
--- a/libc/intrin/pext.h
+++ b/libc/intrin/pext.h
@@ -1,6 +1,7 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_PEXT_H_
#define COSMOPOLITAN_LIBC_INTRIN_PEXT_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
/* TODO(jart): Implement polyfill. */
#define pext(NUMBER, BITMASK) \
@@ -10,5 +11,6 @@
ShuffledBits; \
})
+COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_PEXT_H_ */
diff --git a/libc/intrin/phaddd.c b/libc/intrin/phaddd.c
new file mode 100644
index 000000000..c2d8957f6
--- /dev/null
+++ b/libc/intrin/phaddd.c
@@ -0,0 +1,39 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/phaddd.h"
+#include "libc/str/str.h"
+
+/**
+ * Adds adjacent 32-bit integers.
+ *
+ * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 concatenated
+ * @param 𝑏 [r/o] supplies two pairs of ints
+ * @param 𝑐 [r/o] supplies two pairs of ints
+ * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011)
+ * @mayalias
+ */
+void(phaddd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) {
+ int32_t t[4];
+ t[0] = b[0] + b[1];
+ t[1] = b[2] + b[3];
+ t[2] = c[0] + c[1];
+ t[3] = c[2] + c[3];
+ memcpy(a, t, sizeof(t));
+}
diff --git a/libc/intrin/phaddd.h b/libc/intrin/phaddd.h
new file mode 100644
index 000000000..96e68bd8a
--- /dev/null
+++ b/libc/intrin/phaddd.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PHADDD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PHADDD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void phaddd(int32_t[4], const int32_t[4], const int32_t[4]);
+
+#define phaddd(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(phaddd, SSSE3, "phaddd", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PHADDD_H_ */
diff --git a/libc/intrin/phaddsw.c b/libc/intrin/phaddsw.c
index f1d47626e..0728ac79e 100644
--- a/libc/intrin/phaddsw.c
+++ b/libc/intrin/phaddsw.c
@@ -30,7 +30,7 @@
* @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011)
* @mayalias
*/
-void(phaddsw)(short a[8], const short b[8], const short c[8]) {
+void(phaddsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
int i, t[8];
t[0] = b[0] + b[1];
t[1] = b[2] + b[3];
diff --git a/libc/intrin/phaddsw.h b/libc/intrin/phaddsw.h
index 5d0ec5739..e85db81cb 100644
--- a/libc/intrin/phaddsw.h
+++ b/libc/intrin/phaddsw.h
@@ -2,12 +2,14 @@
#define COSMOPOLITAN_LIBC_INTRIN_PHADDSW_H_
#include "libc/intrin/macros.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
-void phaddsw(short[8], const short[8], const short[8]);
+void phaddsw(int16_t[8], const int16_t[8], const int16_t[8]);
#define phaddsw(A, B, C) \
INTRIN_SSEVEX_X_X_X_(phaddsw, SSSE3, "phaddsw", INTRIN_NONCOMMUTATIVE, A, B, \
C)
+COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_PHADDSW_H_ */
diff --git a/libc/intrin/phaddw.c b/libc/intrin/phaddw.c
index 529a750c8..5f70262ef 100644
--- a/libc/intrin/phaddw.c
+++ b/libc/intrin/phaddw.c
@@ -18,9 +18,10 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/intrin/phaddw.h"
+#include "libc/str/str.h"
/**
- * Adds adjacent signed 16-bit integers.
+ * Adds adjacent 16-bit integers.
*
* @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 concatenated
* @param 𝑏 [r/o] supplies four pairs of shorts
@@ -28,8 +29,8 @@
* @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011)
* @mayalias
*/
-void(phaddw)(short a[8], const short b[8], const short c[8]) {
- short t[8];
+void(phaddw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ int16_t t[8];
t[0] = b[0] + b[1];
t[1] = b[2] + b[3];
t[2] = b[4] + b[5];
diff --git a/libc/intrin/phaddw.h b/libc/intrin/phaddw.h
index b491f9137..efebaef56 100644
--- a/libc/intrin/phaddw.h
+++ b/libc/intrin/phaddw.h
@@ -1,13 +1,14 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_PHADDW_H_
#define COSMOPOLITAN_LIBC_INTRIN_PHADDW_H_
#include "libc/intrin/macros.h"
-#include "libc/str/str.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
-void phaddw(short[8], const short[8], const short[8]);
+void phaddw(int16_t[8], const int16_t[8], const int16_t[8]);
#define phaddw(A, B, C) \
INTRIN_SSEVEX_X_X_X_(phaddw, SSSE3, "phaddw", INTRIN_NONCOMMUTATIVE, A, B, C)
+COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_PHADDW_H_ */
diff --git a/libc/intrin/phsubd.c b/libc/intrin/phsubd.c
new file mode 100644
index 000000000..81775ba2a
--- /dev/null
+++ b/libc/intrin/phsubd.c
@@ -0,0 +1,39 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/phsubd.h"
+#include "libc/str/str.h"
+
+/**
+ * Subtracts adjacent 32-bit integers.
+ *
+ * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 concatenated
+ * @param 𝑏 [r/o] supplies two pairs of ints
+ * @param 𝑐 [r/o] supplies two pairs of ints
+ * @note goes fast w/ ssse3
+ * @mayalias
+ */
+void(phsubd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) {
+ int32_t t[4];
+ t[0] = b[0] - b[1];
+ t[1] = b[2] - b[3];
+ t[2] = c[0] - c[1];
+ t[3] = c[2] - c[3];
+ memcpy(a, t, sizeof(t));
+}
diff --git a/libc/intrin/phsubd.h b/libc/intrin/phsubd.h
new file mode 100644
index 000000000..641272fb9
--- /dev/null
+++ b/libc/intrin/phsubd.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PHSUBD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PHSUBD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void phsubd(int32_t[4], const int32_t[4], const int32_t[4]);
+
+#define phsubd(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(phsubd, SSSE3, "phsubd", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PHSUBD_H_ */
diff --git a/test/libc/intrin/paddw_test.c b/libc/intrin/phsubsw.c
similarity index 73%
rename from test/libc/intrin/paddw_test.c
rename to libc/intrin/phsubsw.c
index 8c0c0b82e..1f4154519 100644
--- a/test/libc/intrin/paddw_test.c
+++ b/libc/intrin/phsubsw.c
@@ -17,39 +17,30 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/intrin/paddsw.h"
-#include "libc/intrin/paddw.h"
+#include "libc/intrin/phsubsw.h"
#include "libc/limits.h"
-#include "libc/testlib/testlib.h"
+#include "libc/macros.h"
-TEST(paddw, test) {
- short A[8] = {7};
- short B[8] = {11};
- short C[8];
- paddw(C, A, B);
- EXPECT_EQ(18, C[0]);
-}
-
-TEST(paddsw, test) {
- short A[8] = {7};
- short B[8] = {11};
- short C[8];
- paddsw(C, A, B);
- EXPECT_EQ(18, C[0]);
-}
-
-TEST(paddw, testOverflow_wrapsAround) {
- short A[8] = {SHRT_MAX, SHRT_MIN};
- short B[8] = {1, -1};
- paddw(A, A, B);
- EXPECT_EQ(SHRT_MIN, A[0]);
- EXPECT_EQ(SHRT_MAX, A[1]);
-}
-
-TEST(paddsw, testOverflow_saturates) {
- short A[8] = {SHRT_MAX, SHRT_MIN};
- short B[8] = {1, -1};
- paddsw(A, A, B);
- EXPECT_EQ(SHRT_MAX, A[0]);
- EXPECT_EQ(SHRT_MIN, A[1]);
+/**
+ * Subtracts adjacent shorts w/ saturation.
+ *
+ * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 concatenated
+ * @param 𝑏 [r/o] supplies four pairs of shorts
+ * @param 𝑐 [r/o] supplies four pairs of shorts
+ * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011)
+ * @mayalias
+ */
+void(phsubsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ int i, t[8];
+ t[0] = b[0] - b[1];
+ t[1] = b[2] - b[3];
+ t[2] = b[4] - b[5];
+ t[3] = b[6] - b[7];
+ t[4] = c[0] - c[1];
+ t[5] = c[2] - c[3];
+ t[6] = c[4] - c[5];
+ t[7] = c[6] - c[7];
+ for (i = 0; i < 8; ++i) {
+ a[i] = MIN(SHRT_MAX, MAX(SHRT_MIN, t[i]));
+ }
}
diff --git a/libc/intrin/phsubsw.h b/libc/intrin/phsubsw.h
new file mode 100644
index 000000000..8c81d7496
--- /dev/null
+++ b/libc/intrin/phsubsw.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PHSUBSW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PHSUBSW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void phsubsw(int16_t[8], const int16_t[8], const int16_t[8]);
+
+#define phsubsw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(phsubsw, SSSE3, "phsubsw", INTRIN_NONCOMMUTATIVE, A, B, \
+ C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PHSUBSW_H_ */
diff --git a/libc/intrin/phsubw.c b/libc/intrin/phsubw.c
new file mode 100644
index 000000000..4eb9afd56
--- /dev/null
+++ b/libc/intrin/phsubw.c
@@ -0,0 +1,43 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/phsubw.h"
+#include "libc/str/str.h"
+
+/**
+ * Subtracts adjacent 16-bit integers.
+ *
+ * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 concatenated
+ * @param 𝑏 [r/o] supplies four pairs of shorts
+ * @param 𝑐 [r/o] supplies four pairs of shorts
+ * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011)
+ * @mayalias
+ */
+void(phsubw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ int16_t t[8];
+ t[0] = b[0] - b[1];
+ t[1] = b[2] - b[3];
+ t[2] = b[4] - b[5];
+ t[3] = b[6] - b[7];
+ t[4] = c[0] - c[1];
+ t[5] = c[2] - c[3];
+ t[6] = c[4] - c[5];
+ t[7] = c[6] - c[7];
+ memcpy(a, t, sizeof(t));
+}
diff --git a/libc/intrin/phsubw.h b/libc/intrin/phsubw.h
new file mode 100644
index 000000000..0c21fa050
--- /dev/null
+++ b/libc/intrin/phsubw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PHSUBW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PHSUBW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void phsubw(int16_t[8], const int16_t[8], const int16_t[8]);
+
+#define phsubw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(phsubw, SSSE3, "phsubw", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PHSUBW_H_ */
diff --git a/libc/intrin/pmaddubsw.c b/libc/intrin/pmaddubsw.c
index 1eed8d7f4..1931b26af 100644
--- a/libc/intrin/pmaddubsw.c
+++ b/libc/intrin/pmaddubsw.c
@@ -20,6 +20,7 @@
#include "libc/intrin/pmaddubsw.h"
#include "libc/limits.h"
#include "libc/macros.h"
+#include "libc/str/str.h"
/**
* Multiplies bytes and adds adjacent results w/ short saturation.
@@ -33,9 +34,8 @@
* @note greatest simd op, like, ever
* @mayalias
*/
-void(pmaddubsw)(short w[8], const unsigned char b[16],
- const signed char c[16]) {
- int i;
+void(pmaddubsw)(int16_t w[8], const uint8_t b[16], const int8_t c[16]) {
+ unsigned i;
for (i = 0; i < 8; ++i) {
w[i] = MIN(SHRT_MAX, MAX(SHRT_MIN, (c[i * 2 + 0] * b[i * 2 + 0] +
c[i * 2 + 1] * b[i * 2 + 1])));
diff --git a/libc/intrin/pmaddubsw.h b/libc/intrin/pmaddubsw.h
index 498a7ad46..c5224e1ce 100644
--- a/libc/intrin/pmaddubsw.h
+++ b/libc/intrin/pmaddubsw.h
@@ -2,12 +2,14 @@
#define COSMOPOLITAN_LIBC_INTRIN_PMADDUBSW_H_
#include "libc/intrin/macros.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
-void pmaddubsw(short[8], const unsigned char[16], const signed char[16]);
+void pmaddubsw(int16_t[8], const uint8_t[16], const int8_t[16]);
#define pmaddubsw(W, B, C) \
INTRIN_SSEVEX_X_X_X_(pmaddubsw, SSSE3, "pmaddubsw", INTRIN_NONCOMMUTATIVE, \
W, B, C)
+COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_PMADDUBSW_H_ */
diff --git a/libc/intrin/pmaddwd.c b/libc/intrin/pmaddwd.c
new file mode 100644
index 000000000..05b60f36d
--- /dev/null
+++ b/libc/intrin/pmaddwd.c
@@ -0,0 +1,35 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pmaddwd.h"
+
+/**
+ * Multiplies 16-bit signed integers and adds adjacent results.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pmaddwd)(int32_t a[4], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ for (i = 0; i < 4; ++i) {
+ a[i] = b[i * 2] * c[i * 2] + b[i * 2 + 1] * c[i * 2 + 1];
+ }
+}
diff --git a/libc/intrin/pmaddwd.h b/libc/intrin/pmaddwd.h
new file mode 100644
index 000000000..45ae5dfb7
--- /dev/null
+++ b/libc/intrin/pmaddwd.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PMADDWD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PMADDWD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pmaddwd(int32_t[4], const int16_t[8], const int16_t[8]);
+
+#define pmaddwd(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pmaddwd, SSE2, "pmaddwd", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PMADDWD_H_ */
diff --git a/libc/intrin/pmaxsw.c b/libc/intrin/pmaxsw.c
new file mode 100644
index 000000000..54a425711
--- /dev/null
+++ b/libc/intrin/pmaxsw.c
@@ -0,0 +1,39 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pmaxsw.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Gets maximum of signed 16-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pmaxsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ int16_t r[8];
+ for (i = 0; i < 8; ++i) {
+ r[i] = MAX(b[i], c[i]);
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pmaxsw.h b/libc/intrin/pmaxsw.h
new file mode 100644
index 000000000..406a607bc
--- /dev/null
+++ b/libc/intrin/pmaxsw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PMAXSW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PMAXSW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pmaxsw(int16_t[8], const int16_t[8], const int16_t[8]);
+
+#define pmaxsw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pmaxsw, SSE2, "pmaxsw", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PMAXSW_H_ */
diff --git a/libc/intrin/pmaxub.c b/libc/intrin/pmaxub.c
new file mode 100644
index 000000000..69e1e9f8f
--- /dev/null
+++ b/libc/intrin/pmaxub.c
@@ -0,0 +1,37 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pmaxub.h"
+#include "libc/macros.h"
+
+/**
+ * Returns minimum of 8-bit unsigned integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pmaxub)(unsigned char a[16], const unsigned char b[16],
+ const unsigned char c[16]) {
+ unsigned i;
+ for (i = 0; i < 16; ++i) {
+ a[i] = MAX(b[i], c[i]);
+ }
+}
diff --git a/libc/intrin/pmaxub.h b/libc/intrin/pmaxub.h
new file mode 100644
index 000000000..939bbea50
--- /dev/null
+++ b/libc/intrin/pmaxub.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PMAXUB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PMAXUB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pmaxub(unsigned char[16], const unsigned char[16],
+ const unsigned char[16]);
+
+#define pmaxub(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pmaxub, SSE2, "pmaxub", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PMAXUB_H_ */
diff --git a/libc/intrin/pminsw.c b/libc/intrin/pminsw.c
new file mode 100644
index 000000000..e650ca8b5
--- /dev/null
+++ b/libc/intrin/pminsw.c
@@ -0,0 +1,39 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pminsw.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Gets minimum of signed 16-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pminsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ int16_t r[8];
+ for (i = 0; i < 8; ++i) {
+ r[i] = MIN(b[i], c[i]);
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pminsw.h b/libc/intrin/pminsw.h
new file mode 100644
index 000000000..6675076f8
--- /dev/null
+++ b/libc/intrin/pminsw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PMINSW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PMINSW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pminsw(int16_t[8], const int16_t[8], const int16_t[8]);
+
+#define pminsw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pminsw, SSE2, "pminsw", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PMINSW_H_ */
diff --git a/libc/intrin/pminub.c b/libc/intrin/pminub.c
new file mode 100644
index 000000000..db9474afa
--- /dev/null
+++ b/libc/intrin/pminub.c
@@ -0,0 +1,37 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pminub.h"
+#include "libc/macros.h"
+
+/**
+ * Returns minimum of 8-bit unsigned integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pminub)(unsigned char a[16], const unsigned char b[16],
+ const unsigned char c[16]) {
+ unsigned i;
+ for (i = 0; i < 16; ++i) {
+ a[i] = MIN(b[i], c[i]);
+ }
+}
diff --git a/libc/intrin/pminub.h b/libc/intrin/pminub.h
new file mode 100644
index 000000000..8f7e29d38
--- /dev/null
+++ b/libc/intrin/pminub.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PMINUB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PMINUB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pminub(unsigned char[16], const unsigned char[16],
+ const unsigned char[16]);
+
+#define pminub(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pminub, SSE2, "pminub", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PMINUB_H_ */
diff --git a/libc/runtime/exit.c b/libc/intrin/pmovmskb.c
similarity index 90%
rename from libc/runtime/exit.c
rename to libc/intrin/pmovmskb.c
index 083cb574e..5dcee3f09 100644
--- a/libc/runtime/exit.c
+++ b/libc/intrin/pmovmskb.c
@@ -17,14 +17,12 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/runtime/internal.h"
-#include "libc/runtime/runtime.h"
+#include "libc/intrin/pmovmskb.h"
-/**
- * Exits program with grace.
- */
-noreturn textexit void exit(int rc) {
- __cxa_finalize(NULL);
- _exit(rc);
- unreachable;
+uint32_t(pmovmskb)(const uint8_t p[16]) {
+ uint32_t i, m;
+ for (m = i = 0; i < 16; ++i) {
+ if (p[i] & 0x80) m |= 1 << i;
+ }
+ return m;
}
diff --git a/libc/intrin/pmovmskb.h b/libc/intrin/pmovmskb.h
new file mode 100644
index 000000000..a90039ee3
--- /dev/null
+++ b/libc/intrin/pmovmskb.h
@@ -0,0 +1,27 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PMOVMSKB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PMOVMSKB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+uint32_t pmovmskb(const uint8_t[16]);
+
+#define pmovmskb(A) \
+ ({ \
+ uint32_t Mask; \
+ if (!IsModeDbg() && X86_HAVE(SSE2)) { \
+ const __intrin_xmm_t *Xmm = (const __intrin_xmm_t *)(A); \
+ if (!X86_NEED(AVX)) { \
+ asm("pmovmskb\t%1,%0" : "=r"(Mask) : "x"(*Xmm)); \
+ } else { \
+ asm("vpmovmskb\t%1,%0" : "=r"(Mask) : "x"(*Xmm)); \
+ } \
+ } else { \
+ Mask = pmovmskb(A); \
+ } \
+ Mask; \
+ })
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PMOVMSKB_H_ */
diff --git a/libc/intrin/pmulhrsw.c b/libc/intrin/pmulhrsw.c
index 8afe6832c..ceea7a75a 100644
--- a/libc/intrin/pmulhrsw.c
+++ b/libc/intrin/pmulhrsw.c
@@ -18,17 +18,19 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/intrin/pmulhrsw.h"
+#include "libc/str/str.h"
/**
* Multiplies Q15 numbers.
*
+ * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011)
* @note a.k.a. packed multiply high w/ round & scale
* @see Q2F(15,𝑥), F2Q(15,𝑥)
* @mayalias
*/
-void(pmulhrsw)(short a[8], const short b[8], const short c[8]) {
- int i;
- for (i = 0; i < 8; ++i) {
- a[i] = (((b[i] * c[i]) >> 14) + 1) >> 1;
- }
+void(pmulhrsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ int16_t r[8];
+ for (i = 0; i < 8; ++i) r[i] = (((b[i] * c[i]) >> 14) + 1) >> 1;
+ memcpy(a, r, 16);
}
diff --git a/libc/intrin/pmulhrsw.h b/libc/intrin/pmulhrsw.h
index f642a007d..7c133cae1 100644
--- a/libc/intrin/pmulhrsw.h
+++ b/libc/intrin/pmulhrsw.h
@@ -2,11 +2,13 @@
#define COSMOPOLITAN_LIBC_INTRIN_PMULHRSW_H_
#include "libc/intrin/macros.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
-void pmulhrsw(short a[8], const short b[8], const short c[8]);
+void pmulhrsw(int16_t a[8], const int16_t b[8], const int16_t c[8]);
#define pmulhrsw(A, B, C) \
INTRIN_SSEVEX_X_X_X_(pmulhrsw, SSSE3, "pmulhrsw", INTRIN_COMMUTATIVE, A, B, C)
+COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_PMULHRSW_H_ */
diff --git a/libc/intrin/pmulhuw.c b/libc/intrin/pmulhuw.c
new file mode 100644
index 000000000..1f68fdc17
--- /dev/null
+++ b/libc/intrin/pmulhuw.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pmulhuw.h"
+#include "libc/str/str.h"
+
+/**
+ * Multiplies 16-bit unsigned integers and stores high word.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pmulhuw)(uint16_t a[8], const uint16_t b[8], const uint16_t c[8]) {
+ unsigned i;
+ uint16_t r[8];
+ for (i = 0; i < 8; ++i) {
+ r[i] = ((b[i] * c[i]) & 0xffff0000) >> 16;
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pmulhuw.h b/libc/intrin/pmulhuw.h
new file mode 100644
index 000000000..341dd20ab
--- /dev/null
+++ b/libc/intrin/pmulhuw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PMULHUW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PMULHUW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pmulhuw(uint16_t[8], const uint16_t[8], const uint16_t[8]);
+
+#define pmulhuw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pmulhuw, SSE2, "pmulhuw", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PMULHUW_H_ */
diff --git a/libc/intrin/pmulhw.c b/libc/intrin/pmulhw.c
new file mode 100644
index 000000000..dcd8676fe
--- /dev/null
+++ b/libc/intrin/pmulhw.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pmulhw.h"
+#include "libc/str/str.h"
+
+/**
+ * Multiplies 16-bit signed integers and stores high word.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pmulhw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ int16_t r[8];
+ for (i = 0; i < 8; ++i) {
+ r[i] = ((b[i] * c[i]) & 0xffff0000) >> 16;
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pmulhw.h b/libc/intrin/pmulhw.h
new file mode 100644
index 000000000..ee405a7d3
--- /dev/null
+++ b/libc/intrin/pmulhw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PMULHW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PMULHW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pmulhw(int16_t[8], const int16_t[8], const int16_t[8]);
+
+#define pmulhw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pmulhw, SSE2, "pmulhw", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PMULHW_H_ */
diff --git a/libc/intrin/pmulld.c b/libc/intrin/pmulld.c
new file mode 100644
index 000000000..34566a105
--- /dev/null
+++ b/libc/intrin/pmulld.c
@@ -0,0 +1,39 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pmulld.h"
+#include "libc/str/str.h"
+
+/**
+ * Multiplies 32-bit signed integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @see pmuludq()
+ * @mayalias
+ */
+void(pmulld)(int32_t a[4], const int32_t b[4], const int32_t c[4]) {
+ unsigned i;
+ int32_t r[4];
+ for (i = 0; i < 4; ++i) {
+ r[i] = b[i] * c[i];
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pmulld.h b/libc/intrin/pmulld.h
new file mode 100644
index 000000000..8365f5b1c
--- /dev/null
+++ b/libc/intrin/pmulld.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PMULLD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PMULLD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pmulld(int32_t[4], const int32_t[4], const int32_t[4]);
+
+#define pmulld(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pmulld, SSE4_1, "pmulld", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PMULLD_H_ */
diff --git a/libc/intrin/pmullw.c b/libc/intrin/pmullw.c
new file mode 100644
index 000000000..e5ae3f217
--- /dev/null
+++ b/libc/intrin/pmullw.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pmullw.h"
+#include "libc/str/str.h"
+
+/**
+ * Multiplies 16-bit signed integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pmullw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ int16_t r[8];
+ for (i = 0; i < 8; ++i) {
+ r[i] = b[i] * c[i];
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pmullw.h b/libc/intrin/pmullw.h
new file mode 100644
index 000000000..991653a8d
--- /dev/null
+++ b/libc/intrin/pmullw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PMULLW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PMULLW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pmullw(int16_t[8], const int16_t[8], const int16_t[8]);
+
+#define pmullw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pmullw, SSE2, "pmullw", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PMULLW_H_ */
diff --git a/libc/intrin/pmuludq.c b/libc/intrin/pmuludq.c
new file mode 100644
index 000000000..589c652a1
--- /dev/null
+++ b/libc/intrin/pmuludq.c
@@ -0,0 +1,37 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pmuludq.h"
+#include "libc/str/str.h"
+
+/**
+ * Multiplies 32-bit unsigned integers w/ promotion.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @see pmulld()
+ * @mayalias
+ */
+void(pmuludq)(uint64_t a[2], const uint32_t b[4], const uint32_t c[4]) {
+ unsigned i;
+ for (i = 0; i < 2; ++i) {
+ a[i] = (uint64_t)b[i * 2] * c[i * 2];
+ }
+}
diff --git a/libc/intrin/pmuludq.h b/libc/intrin/pmuludq.h
new file mode 100644
index 000000000..849eeb408
--- /dev/null
+++ b/libc/intrin/pmuludq.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PMULUDQ_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PMULUDQ_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pmuludq(uint64_t[2], const uint32_t[4], const uint32_t[4]);
+
+#define pmuludq(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pmuludq, SSE2, "pmuludq", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PMULUDQ_H_ */
diff --git a/libc/intrin/por.c b/libc/intrin/por.c
new file mode 100644
index 000000000..58fc1ba1b
--- /dev/null
+++ b/libc/intrin/por.c
@@ -0,0 +1,35 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/por.h"
+
+/**
+ * Ors 128-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(por)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) {
+ unsigned i;
+ for (i = 0; i < 2; ++i) {
+ a[i] = b[i] | c[i];
+ }
+}
diff --git a/libc/intrin/por.h b/libc/intrin/por.h
new file mode 100644
index 000000000..62fede4b6
--- /dev/null
+++ b/libc/intrin/por.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_POR_H_
+#define COSMOPOLITAN_LIBC_INTRIN_POR_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void por(uint64_t[2], const uint64_t[2], const uint64_t[2]);
+
+#define por(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(por, SSE2, "por", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_POR_H_ */
diff --git a/libc/intrin/psadbw.c b/libc/intrin/psadbw.c
new file mode 100644
index 000000000..5df661dc0
--- /dev/null
+++ b/libc/intrin/psadbw.c
@@ -0,0 +1,37 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psadbw.h"
+#include "libc/macros.h"
+
+/**
+ * Computes sum of absolute differences.
+ *
+ * @param 𝑎 [w/o] receives sum at first index and rest is zero'd
+ * @param 𝑏 [r/o] is your first unsigned byte array
+ * @param 𝑐 [r/o] is your second unsigned byte array
+ * @mayalias
+ */
+void(psadbw)(uint64_t a[2], const uint8_t b[16], const uint8_t c[16]) {
+ unsigned i, x, y;
+ for (x = i = 0; i < 8; ++i) x += ABS(b[i] - c[i]);
+ for (y = 0; i < 16; ++i) y += ABS(b[i] - c[i]);
+ a[0] = x;
+ a[1] = y;
+}
diff --git a/libc/intrin/psadbw.h b/libc/intrin/psadbw.h
new file mode 100644
index 000000000..2eb739af5
--- /dev/null
+++ b/libc/intrin/psadbw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSADBW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSADBW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psadbw(uint64_t[2], const uint8_t[16], const uint8_t[16]);
+
+#define psadbw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psadbw, SSE2, "psadbw", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSADBW_H_ */
diff --git a/libc/intrin/pshufb.c b/libc/intrin/pshufb.c
new file mode 100644
index 000000000..40cad591b
--- /dev/null
+++ b/libc/intrin/pshufb.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pshufb.h"
+#include "libc/str/str.h"
+
+/**
+ * Shuffles and/or clears 8-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies byte vector
+ * @param 𝑐 [r/o] supplies mask vector
+ * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011)
+ * @note doesn't perfectly emulate mmx
+ * @mayalias
+ */
+void(pshufb)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) {
+ unsigned i;
+ uint8_t r[16];
+ for (i = 0; i < 16; ++i) r[i] = (c[i] & 0x80) ? 0 : b[c[i] & 0x0F];
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/pshufb.h b/libc/intrin/pshufb.h
new file mode 100644
index 000000000..8adefc6ae
--- /dev/null
+++ b/libc/intrin/pshufb.h
@@ -0,0 +1,13 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSHUFB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSHUFB_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pshufb(uint8_t[16], const uint8_t[16], const uint8_t[16]);
+
+#define pshufb(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pshufb, SSSE3, "pshufb", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSHUFB_H_ */
diff --git a/libc/intrin/pshufd.c b/libc/intrin/pshufd.c
new file mode 100644
index 000000000..f21781951
--- /dev/null
+++ b/libc/intrin/pshufd.c
@@ -0,0 +1,37 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pshufd.h"
+
+/**
+ * Shuffles int vector.
+ * @param 𝑚 needs to be a literal, constexpr, or embedding
+ * @mayalias
+ */
+void(pshufd)(int32_t b[4], const int32_t a[4], uint8_t m) {
+ int32_t t[4];
+ t[0] = a[(m & 0b00000011) >> 0];
+ t[1] = a[(m & 0b00001100) >> 2];
+ t[2] = a[(m & 0b00110000) >> 4];
+ t[3] = a[(m & 0b11000000) >> 6];
+ b[0] = t[0];
+ b[1] = t[1];
+ b[2] = t[2];
+ b[3] = t[3];
+}
diff --git a/libc/intrin/pshufd.h b/libc/intrin/pshufd.h
new file mode 100644
index 000000000..e7dd60dd9
--- /dev/null
+++ b/libc/intrin/pshufd.h
@@ -0,0 +1,13 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSHUFD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSHUFD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pshufd(int32_t[4], const int32_t[4], uint8_t);
+
+#define pshufd(A, B, I) INTRIN_SSEVEX_X_X_I_(pshufd, SSE2, "pshufd", A, B, I)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSHUFD_H_ */
diff --git a/libc/intrin/pshufhw.c b/libc/intrin/pshufhw.c
new file mode 100644
index 000000000..d41b9e369
--- /dev/null
+++ b/libc/intrin/pshufhw.c
@@ -0,0 +1,41 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pshufhw.h"
+
+/**
+ * Shuffles lower half of word vector.
+ * @param 𝑚 needs to be a literal, constexpr, or embedding
+ * @mayalias
+ */
+void(pshufhw)(int16_t b[8], const int16_t a[8], uint8_t m) {
+ int16_t t[4];
+ t[0] = a[4 + ((m & 0b00000011) >> 0)];
+ t[1] = a[4 + ((m & 0b00001100) >> 2)];
+ t[2] = a[4 + ((m & 0b00110000) >> 4)];
+ t[3] = a[4 + ((m & 0b11000000) >> 6)];
+ b[0] = a[0];
+ b[1] = a[1];
+ b[2] = a[2];
+ b[3] = a[3];
+ b[4] = t[0];
+ b[5] = t[1];
+ b[6] = t[2];
+ b[7] = t[3];
+}
diff --git a/libc/intrin/pshufhw.h b/libc/intrin/pshufhw.h
new file mode 100644
index 000000000..051c1b6f1
--- /dev/null
+++ b/libc/intrin/pshufhw.h
@@ -0,0 +1,13 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSHUFHW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSHUFHW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pshufhw(int16_t[8], const int16_t[8], uint8_t);
+
+#define pshufhw(A, B, I) INTRIN_SSEVEX_X_X_I_(pshufhw, SSE2, "pshufhw", A, B, I)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSHUFHW_H_ */
diff --git a/libc/intrin/pshuflw.c b/libc/intrin/pshuflw.c
new file mode 100644
index 000000000..8de1a2f91
--- /dev/null
+++ b/libc/intrin/pshuflw.c
@@ -0,0 +1,41 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pshuflw.h"
+
+/**
+ * Shuffles lower half of word vector.
+ * @param 𝑚 needs to be a literal, constexpr, or embedding
+ * @mayalias
+ */
+void(pshuflw)(int16_t b[8], const int16_t a[8], uint8_t m) {
+ int16_t t[4];
+ t[0] = a[(m & 0b00000011) >> 0];
+ t[1] = a[(m & 0b00001100) >> 2];
+ t[2] = a[(m & 0b00110000) >> 4];
+ t[3] = a[(m & 0b11000000) >> 6];
+ b[0] = t[0];
+ b[1] = t[1];
+ b[2] = t[2];
+ b[3] = t[3];
+ b[4] = a[4];
+ b[5] = a[5];
+ b[6] = a[6];
+ b[7] = a[7];
+}
diff --git a/libc/intrin/pshuflw.h b/libc/intrin/pshuflw.h
new file mode 100644
index 000000000..1c2457b6e
--- /dev/null
+++ b/libc/intrin/pshuflw.h
@@ -0,0 +1,13 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSHUFLW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSHUFLW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pshuflw(int16_t[8], const int16_t[8], uint8_t);
+
+#define pshuflw(A, B, I) INTRIN_SSEVEX_X_X_I_(pshuflw, SSE2, "pshuflw", A, B, I)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSHUFLW_H_ */
diff --git a/libc/intrin/pshufw.c b/libc/intrin/pshufw.c
new file mode 100644
index 000000000..25198e6f7
--- /dev/null
+++ b/libc/intrin/pshufw.c
@@ -0,0 +1,37 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pshufw.h"
+
+/**
+ * Shuffles mmx vector.
+ * @param 𝑚 needs to be a literal, constexpr, or embedding
+ * @mayalias
+ */
+void(pshufw)(int16_t b[4], const int16_t a[4], uint8_t m) {
+ int16_t t[4];
+ t[0] = a[(m & 0b00000011) >> 0];
+ t[1] = a[(m & 0b00001100) >> 2];
+ t[2] = a[(m & 0b00110000) >> 4];
+ t[3] = a[(m & 0b11000000) >> 6];
+ b[0] = t[0];
+ b[1] = t[1];
+ b[2] = t[2];
+ b[3] = t[3];
+}
diff --git a/libc/intrin/pshufw.h b/libc/intrin/pshufw.h
new file mode 100644
index 000000000..d5c6eb195
--- /dev/null
+++ b/libc/intrin/pshufw.h
@@ -0,0 +1,10 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSHUFW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSHUFW_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pshufw(int16_t[4], const int16_t[4], uint8_t);
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSHUFW_H_ */
diff --git a/libc/intrin/psignb.c b/libc/intrin/psignb.c
new file mode 100644
index 000000000..b60ccd68d
--- /dev/null
+++ b/libc/intrin/psignb.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psignb.h"
+#include "libc/str/str.h"
+
+/**
+ * Conditionally negates or zeroes signed bytes.
+ * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011)
+ */
+void(psignb)(int8_t a[16], const int8_t b[16], const int8_t c[16]) {
+ unsigned i;
+ for (i = 0; i < 16; ++i) {
+ if (!c[i]) {
+ a[i] = 0;
+ } else if (c[i] < 0) {
+ a[i] = -b[i];
+ } else {
+ a[i] = b[i];
+ }
+ }
+}
diff --git a/libc/intrin/psignb.h b/libc/intrin/psignb.h
new file mode 100644
index 000000000..92f013219
--- /dev/null
+++ b/libc/intrin/psignb.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSIGNB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSIGNB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psignb(int8_t[16], const int8_t[16], const int8_t[16]);
+
+#define psignb(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psignb, SSSE3, "psignb", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSIGNB_H_ */
diff --git a/libc/intrin/psignd.c b/libc/intrin/psignd.c
new file mode 100644
index 000000000..33809e9b0
--- /dev/null
+++ b/libc/intrin/psignd.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psignd.h"
+#include "libc/str/str.h"
+
+/**
+ * Conditionally negates or zeroes ints.
+ * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011)
+ */
+void(psignd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) {
+ unsigned i;
+ for (i = 0; i < 4; ++i) {
+ if (!c[i]) {
+ a[i] = 0;
+ } else if (c[i] < 0) {
+ a[i] = -b[i];
+ } else {
+ a[i] = b[i];
+ }
+ }
+}
diff --git a/libc/intrin/psignd.h b/libc/intrin/psignd.h
new file mode 100644
index 000000000..e22cb12a5
--- /dev/null
+++ b/libc/intrin/psignd.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSIGND_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSIGND_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psignd(int32_t[4], const int32_t[4], const int32_t[4]);
+
+#define psignd(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psignd, SSSE3, "psignd", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSIGND_H_ */
diff --git a/libc/intrin/psignw.c b/libc/intrin/psignw.c
new file mode 100644
index 000000000..a0020aaad
--- /dev/null
+++ b/libc/intrin/psignw.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psignw.h"
+#include "libc/str/str.h"
+
+/**
+ * Conditionally negates or zeroes shorts.
+ * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011)
+ */
+void(psignw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ for (i = 0; i < 8; ++i) {
+ if (!c[i]) {
+ a[i] = 0;
+ } else if (c[i] < 0) {
+ a[i] = -b[i];
+ } else {
+ a[i] = b[i];
+ }
+ }
+}
diff --git a/libc/intrin/psignw.h b/libc/intrin/psignw.h
new file mode 100644
index 000000000..ad0dc50b2
--- /dev/null
+++ b/libc/intrin/psignw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSIGNW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSIGNW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psignw(int16_t[8], const int16_t[8], const int16_t[8]);
+
+#define psignw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psignw, SSSE3, "psignw", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSIGNW_H_ */
diff --git a/libc/intrin/pslld.c b/libc/intrin/pslld.c
new file mode 100644
index 000000000..be0c72246
--- /dev/null
+++ b/libc/intrin/pslld.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pslld.h"
+#include "libc/str/str.h"
+
+/**
+ * Multiplies ints by two power.
+ *
+ * @note c needs to be a literal, asmconstexpr, or linkconstsym
+ * @mayalias
+ */
+void(pslld)(uint32_t a[4], const uint32_t b[4], unsigned char c) {
+ if (c <= 31) {
+ unsigned i;
+ for (i = 0; i < 4; ++i) {
+ a[i] = b[i] << c;
+ }
+ } else {
+ memset(a, 0, 16);
+ }
+}
diff --git a/libc/intrin/pslld.h b/libc/intrin/pslld.h
new file mode 100644
index 000000000..845f83266
--- /dev/null
+++ b/libc/intrin/pslld.h
@@ -0,0 +1,16 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSLLD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSLLD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pslld(uint32_t[4], const uint32_t[4], unsigned char);
+void pslldv(uint32_t[4], const uint32_t[4], const uint64_t[2]);
+
+#define pslld(A, B, I) INTRIN_SSEVEX_X_I_(pslld, SSE2, "pslld", A, B, I)
+#define pslldv(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pslldv, SSE2, "pslld", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSLLD_H_ */
diff --git a/libc/intrin/pslldq.c b/libc/intrin/pslldq.c
new file mode 100644
index 000000000..028078641
--- /dev/null
+++ b/libc/intrin/pslldq.c
@@ -0,0 +1,35 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pslldq.h"
+#include "libc/str/str.h"
+
+/**
+ * Shifts vector right by n bytes w/ zero-fill.
+ *
+ * @param a is input vector
+ * @param b receives output
+ * @mayalias
+ */
+void(pslldq)(uint8_t b[16], const uint8_t a[16], unsigned long n) {
+ unsigned i;
+ if (n > 16) n = 16;
+ memmove(b + n, a, 16 - n);
+ memset(b, 0, n);
+}
diff --git a/libc/intrin/pslldq.h b/libc/intrin/pslldq.h
new file mode 100644
index 000000000..d7c19b3a6
--- /dev/null
+++ b/libc/intrin/pslldq.h
@@ -0,0 +1,37 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSLLDQ_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSLLDQ_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pslldq(uint8_t[16], const uint8_t[16], unsigned long);
+
+#ifndef __STRICT_ANSI__
+__intrin_xmm_t __pslldqs(__intrin_xmm_t);
+#define pslldq(B, A, I) \
+ do { \
+ if (likely(!IsModeDbg() && X86_NEED(SSE) && X86_HAVE(SSE2))) { \
+ __intrin_xmm_t *Xmm0 = (void *)(B); \
+ const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(A); \
+ if (isconstant(I)) { \
+ if (!X86_NEED(AVX)) { \
+ asm("pslldq\t%1,%0" : "=x"(*Xmm0) : "i"(I), "0"(*Xmm1)); \
+ } else { \
+ asm("vpslldq\t%2,%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1), "i"(I)); \
+ } \
+ } else { \
+ unsigned long Vimm = (I); \
+ typeof(__pslldqs) *Fn; \
+ if (Vimm > 16) Vimm = 16; \
+ Fn = (typeof(__pslldqs) *)((uintptr_t)&__pslldqs + Vimm * 8); \
+ *Xmm0 = Fn(*Xmm1); \
+ } \
+ } else { \
+ pslldq(B, A, I); \
+ } \
+ } while (0)
+#endif
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSLLDQ_H_ */
diff --git a/libc/intrin/pslldqs.S b/libc/intrin/pslldqs.S
new file mode 100644
index 000000000..1b7c35678
--- /dev/null
+++ b/libc/intrin/pslldqs.S
@@ -0,0 +1,94 @@
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/macros.h"
+
+/ Jump table for pslldq() with non-constexpr immediate parameter.
+ .align 8
+__pslldqs:
+ pslldq $0,%xmm0
+ ret
+ nop
+ nop
+ pslldq $1,%xmm0
+ ret
+ nop
+ nop
+ pslldq $2,%xmm0
+ ret
+ nop
+ nop
+ pslldq $3,%xmm0
+ ret
+ nop
+ nop
+ pslldq $4,%xmm0
+ ret
+ nop
+ nop
+ pslldq $5,%xmm0
+ ret
+ nop
+ nop
+ pslldq $6,%xmm0
+ ret
+ nop
+ nop
+ pslldq $7,%xmm0
+ ret
+ nop
+ nop
+ pslldq $8,%xmm0
+ ret
+ nop
+ nop
+ pslldq $9,%xmm0
+ ret
+ nop
+ nop
+ pslldq $10,%xmm0
+ ret
+ nop
+ nop
+ pslldq $11,%xmm0
+ ret
+ nop
+ nop
+ pslldq $12,%xmm0
+ ret
+ nop
+ nop
+ pslldq $13,%xmm0
+ ret
+ nop
+ nop
+ pslldq $14,%xmm0
+ ret
+ nop
+ nop
+ pslldq $15,%xmm0
+ ret
+ nop
+ nop
+ pslldq $16,%xmm0
+ ret
+ .if . - __pslldqs != 8 * 17 - 2
+ .error "bad assemblage"
+ .endif
+ .endfn __pslldqs,globl
diff --git a/libc/intrin/pslldv.c b/libc/intrin/pslldv.c
new file mode 100644
index 000000000..1d5f52999
--- /dev/null
+++ b/libc/intrin/pslldv.c
@@ -0,0 +1,36 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pslld.h"
+#include "libc/str/str.h"
+
+/**
+ * Multiplies ints by two power.
+ * @mayalias
+ */
+void(pslldv)(uint32_t a[4], const uint32_t b[4], const uint64_t c[2]) {
+ unsigned i;
+ if (c[0] <= 31) {
+ for (i = 0; i < 4; ++i) {
+ a[i] = b[i] << c[0];
+ }
+ } else {
+ memset(a, 0, 16);
+ }
+}
diff --git a/libc/intrin/psllq.c b/libc/intrin/psllq.c
new file mode 100644
index 000000000..903b5e189
--- /dev/null
+++ b/libc/intrin/psllq.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psllq.h"
+#include "libc/str/str.h"
+
+/**
+ * Multiplies longs by two power.
+ *
+ * @note c needs to be a literal, asmconstexpr, or linkconstsym
+ * @mayalias
+ */
+void(psllq)(uint64_t a[2], const uint64_t b[2], unsigned char c) {
+ unsigned i;
+ if (c <= 63) {
+ for (i = 0; i < 2; ++i) {
+ a[i] = b[i] << c;
+ }
+ } else {
+ memset(a, 0, 16);
+ }
+}
diff --git a/libc/intrin/psllq.h b/libc/intrin/psllq.h
new file mode 100644
index 000000000..7c04acb0a
--- /dev/null
+++ b/libc/intrin/psllq.h
@@ -0,0 +1,16 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSLLQ_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSLLQ_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psllq(uint64_t[2], const uint64_t[2], unsigned char);
+void psllqv(uint64_t[2], const uint64_t[2], const uint64_t[2]);
+
+#define psllq(A, B, I) INTRIN_SSEVEX_X_I_(psllq, SSE2, "psllq", A, B, I)
+#define psllqv(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psllqv, SSE2, "psllq", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSLLQ_H_ */
diff --git a/libc/intrin/psllqv.c b/libc/intrin/psllqv.c
new file mode 100644
index 000000000..481475b57
--- /dev/null
+++ b/libc/intrin/psllqv.c
@@ -0,0 +1,36 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psllq.h"
+#include "libc/str/str.h"
+
+/**
+ * Multiplies longs by two power.
+ * @mayalias
+ */
+void(psllqv)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) {
+ unsigned i;
+ if (c[0] <= 63) {
+ for (i = 0; i < 2; ++i) {
+ a[i] = b[i] << c[0];
+ }
+ } else {
+ memset(a, 0, 16);
+ }
+}
diff --git a/libc/intrin/psllw.c b/libc/intrin/psllw.c
new file mode 100644
index 000000000..e6c692fad
--- /dev/null
+++ b/libc/intrin/psllw.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psllw.h"
+#include "libc/str/str.h"
+
+/**
+ * Multiplies shorts by two power.
+ *
+ * @note c needs to be a literal, asmconstexpr, or linkconstsym
+ * @mayalias
+ */
+void(psllw)(uint16_t a[8], const uint16_t b[8], unsigned char c) {
+ unsigned i;
+ if (c <= 15) {
+ for (i = 0; i < 8; ++i) {
+ a[i] = b[i] << c;
+ }
+ } else {
+ memset(a, 0, 16);
+ }
+}
diff --git a/libc/intrin/psllw.h b/libc/intrin/psllw.h
new file mode 100644
index 000000000..0a38ceef5
--- /dev/null
+++ b/libc/intrin/psllw.h
@@ -0,0 +1,16 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSLLW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSLLW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psllw(uint16_t[8], const uint16_t[8], unsigned char);
+void psllwv(uint16_t[8], const uint16_t[8], const uint64_t[2]);
+
+#define psllw(A, B, I) INTRIN_SSEVEX_X_I_(psllw, SSE2, "psllw", A, B, I)
+#define psllwv(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psllwv, SSE2, "psllw", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSLLW_H_ */
diff --git a/libc/intrin/psllwv.c b/libc/intrin/psllwv.c
new file mode 100644
index 000000000..9089dad12
--- /dev/null
+++ b/libc/intrin/psllwv.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psllw.h"
+
+/**
+ * Multiplies shorts by two power.
+ *
+ * @mayalias
+ */
+void(psllwv)(uint16_t a[8], const uint16_t b[8], const uint64_t c[2]) {
+ unsigned i;
+ if (c[0] > 15) {
+ for (i = 0; i < 8; ++i) {
+ a[i] = 0;
+ }
+ } else {
+ for (i = 0; i < 8; ++i) {
+ a[i] = b[i] << c[0];
+ }
+ }
+}
diff --git a/libc/intrin/psrad.c b/libc/intrin/psrad.c
new file mode 100644
index 000000000..6c6e0a8c0
--- /dev/null
+++ b/libc/intrin/psrad.c
@@ -0,0 +1,40 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psrad.h"
+
+/**
+ * Divides ints by two power.
+ *
+ * @note c needs to be a literal, asmconstexpr, or linkconstsym
+ * @note arithmetic shift right will sign extend negatives
+ * @mayalias
+ */
+void(psrad)(int32_t a[4], const int32_t b[4], unsigned char k) {
+ unsigned i, x, m;
+ if (k > 31) k = 31;
+ for (i = 0; i < 4; ++i) {
+ m = 0;
+ x = b[i];
+ if (x & 0x80000000) m = ~(0xffffffffu >> k);
+ x >>= k;
+ x |= m;
+ a[i] = x;
+ }
+}
diff --git a/libc/intrin/psrad.h b/libc/intrin/psrad.h
new file mode 100644
index 000000000..1a91b00c2
--- /dev/null
+++ b/libc/intrin/psrad.h
@@ -0,0 +1,16 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSRAD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSRAD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psrad(int32_t[4], const int32_t[4], unsigned char);
+void psradv(int32_t[4], const int32_t[4], const uint64_t[2]);
+
+#define psrad(A, B, I) INTRIN_SSEVEX_X_I_(psrad, SSE2, "psrad", A, B, I)
+#define psradv(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psradv, SSE2, "psrad", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSRAD_H_ */
diff --git a/libc/intrin/psradv.c b/libc/intrin/psradv.c
new file mode 100644
index 000000000..08f2bac2f
--- /dev/null
+++ b/libc/intrin/psradv.c
@@ -0,0 +1,40 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psrad.h"
+
+/**
+ * Divides shorts by two powers.
+ *
+ * @note arithmetic shift right will sign extend negatives
+ * @mayalias
+ */
+void(psradv)(int32_t a[4], const int32_t b[4], const uint64_t c[2]) {
+ unsigned char k;
+ unsigned i, x, m;
+ k = c[0] > 31 ? 31 : c[0];
+ for (i = 0; i < 4; ++i) {
+ m = 0;
+ x = b[i];
+ if (x & 0x80000000u) m = ~(0xffffffffu >> k);
+ x >>= k;
+ x |= m;
+ a[i] = x & 0xffffffffu;
+ }
+}
diff --git a/libc/intrin/psraw.c b/libc/intrin/psraw.c
new file mode 100644
index 000000000..cdcefc7ef
--- /dev/null
+++ b/libc/intrin/psraw.c
@@ -0,0 +1,40 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psraw.h"
+
+/**
+ * Divides shorts by two power.
+ *
+ * @note c needs to be a literal, asmconstexpr, or linkconstsym
+ * @note arithmetic shift right will sign extend negatives
+ * @mayalias
+ */
+void(psraw)(int16_t a[8], const int16_t b[8], unsigned char k) {
+ unsigned i, x, m;
+ if (k > 15) k = 15;
+ for (i = 0; i < 8; ++i) {
+ m = 0;
+ x = b[i];
+ if (x & 0x8000) m = ~(0xffff >> k);
+ x >>= k;
+ x |= m;
+ a[i] = x;
+ }
+}
diff --git a/libc/intrin/psraw.h b/libc/intrin/psraw.h
index 4b03c5db5..59e6e2811 100644
--- a/libc/intrin/psraw.h
+++ b/libc/intrin/psraw.h
@@ -1,23 +1,16 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_PSRAW_H_
#define COSMOPOLITAN_LIBC_INTRIN_PSRAW_H_
-#include "libc/bits/bits.h"
#include "libc/intrin/macros.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
-/**
- * Divides shorts by two power.
- *
- * @note c needs to be a literal, asmconstexpr, or linkconstsym
- * @mayalias
- */
-static void psraw(short a[8], const short b[8], char c) {
- int i;
- for (i = 0; i < 8; ++i) {
- a[i] = SAR(b[i], c);
- }
-}
+void psraw(int16_t[8], const int16_t[8], unsigned char);
+void psrawv(int16_t[8], const int16_t[8], const uint64_t[2]);
-#define psraw(A, B, I) INTRIN_SSEVEX_X_X_I_(psraw, SSE2, "psraw", A, B, I)
+#define psraw(A, B, I) INTRIN_SSEVEX_X_I_(psraw, SSE2, "psraw", A, B, I)
+#define psrawv(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psrawv, SSE2, "psraw", INTRIN_NONCOMMUTATIVE, A, B, C)
+COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_PSRAW_H_ */
diff --git a/test/libc/intrin/psraw_test.c b/libc/intrin/psrawv.c
similarity index 83%
rename from test/libc/intrin/psraw_test.c
rename to libc/intrin/psrawv.c
index 505efb43f..516913f76 100644
--- a/test/libc/intrin/psraw_test.c
+++ b/libc/intrin/psrawv.c
@@ -18,21 +18,23 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/intrin/psraw.h"
-#include "libc/limits.h"
-#include "libc/testlib/testlib.h"
-TEST(psraw, testPositive) {
- short A[8] = {1, 2, SHRT_MAX};
- psraw(A, A, 1);
- EXPECT_EQ(0, A[0]);
- EXPECT_EQ(1, A[1]);
- EXPECT_EQ(SHRT_MAX / 2, A[2]);
-}
-
-TEST(psraw, testNegative) {
- short A[8] = {-1, -2, SHRT_MIN};
- psraw(A, A, 1);
- EXPECT_EQ(-1, A[0]);
- EXPECT_EQ(-1, A[1]);
- EXPECT_EQ(SHRT_MIN / 2, A[2]);
+/**
+ * Divides shorts by two power.
+ *
+ * @note arithmetic shift right will sign extend negatives
+ * @mayalias
+ */
+void(psrawv)(int16_t a[8], const int16_t b[8], const uint64_t c[2]) {
+ unsigned char k;
+ unsigned i, x, m;
+ k = c[0] > 15 ? 15 : c[0];
+ for (i = 0; i < 8; ++i) {
+ m = 0;
+ x = b[i];
+ if (x & 0x8000) m = ~(0xffffu >> k);
+ x >>= k;
+ x |= m;
+ a[i] = x & 0xffffu;
+ }
}
diff --git a/libc/intrin/psrld.c b/libc/intrin/psrld.c
new file mode 100644
index 000000000..c9dba0694
--- /dev/null
+++ b/libc/intrin/psrld.c
@@ -0,0 +1,39 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psrld.h"
+#include "libc/str/str.h"
+
+/**
+ * Divides unsigned ints by two power.
+ *
+ * @note c needs to be a literal, asmconstexpr, or linkconstsym
+ * @note logical shift does not sign extend negatives
+ * @mayalias
+ */
+void(psrld)(uint32_t a[4], const uint32_t b[4], unsigned char c) {
+ unsigned i;
+ if (c <= 31) {
+ for (i = 0; i < 4; ++i) {
+ a[i] = b[i] >> c;
+ }
+ } else {
+ memset(a, 0, 16);
+ }
+}
diff --git a/libc/intrin/psrld.h b/libc/intrin/psrld.h
new file mode 100644
index 000000000..9667b8f98
--- /dev/null
+++ b/libc/intrin/psrld.h
@@ -0,0 +1,16 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSRLD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSRLD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psrld(uint32_t[4], const uint32_t[4], unsigned char);
+void psrldv(uint32_t[4], const uint32_t[4], const uint64_t[2]);
+
+#define psrld(A, B, I) INTRIN_SSEVEX_X_I_(psrld, SSE2, "psrld", A, B, I)
+#define psrldv(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psrldv, SSE2, "psrld", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSRLD_H_ */
diff --git a/libc/intrin/psrldq.c b/libc/intrin/psrldq.c
new file mode 100644
index 000000000..cfd6638d1
--- /dev/null
+++ b/libc/intrin/psrldq.c
@@ -0,0 +1,35 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psrldq.h"
+#include "libc/str/str.h"
+
+/**
+ * Shifts vector left by n bytes w/ zero-fill.
+ *
+ * @param a is input vector
+ * @param b receives output
+ * @mayalias
+ */
+void(psrldq)(uint8_t b[16], const uint8_t a[16], unsigned long n) {
+ unsigned i;
+ if (n > 16) n = 16;
+ memcpy(b, a + n, 16 - n);
+ memset(b + (16 - n), 0, n);
+}
diff --git a/libc/intrin/psrldq.h b/libc/intrin/psrldq.h
new file mode 100644
index 000000000..534ce5fd4
--- /dev/null
+++ b/libc/intrin/psrldq.h
@@ -0,0 +1,37 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSRLDQ_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSRLDQ_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psrldq(uint8_t[16], const uint8_t[16], unsigned long);
+
+#ifndef __STRICT_ANSI__
+__intrin_xmm_t __psrldqs(__intrin_xmm_t);
+#define psrldq(B, A, I) \
+ do { \
+ if (likely(!IsModeDbg() && X86_NEED(SSE) && X86_HAVE(SSE2))) { \
+ __intrin_xmm_t *Xmm0 = (void *)(B); \
+ const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(A); \
+ if (isconstant(I)) { \
+ if (!X86_NEED(AVX)) { \
+ asm("psrldq\t%1,%0" : "=x"(*Xmm0) : "i"(I), "0"(*Xmm1)); \
+ } else { \
+ asm("vpsrldq\t%2,%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1), "i"(I)); \
+ } \
+ } else { \
+ unsigned long Vimm = (I); \
+ typeof(__psrldqs) *Fn; \
+ if (Vimm > 16) Vimm = 16; \
+ Fn = (typeof(__psrldqs) *)((uintptr_t)&__psrldqs + Vimm * 8); \
+ *Xmm0 = Fn(*Xmm1); \
+ } \
+ } else { \
+ psrldq(B, A, I); \
+ } \
+ } while (0)
+#endif
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSRLDQ_H_ */
diff --git a/libc/intrin/psrldqs.S b/libc/intrin/psrldqs.S
new file mode 100644
index 000000000..fbe4b5837
--- /dev/null
+++ b/libc/intrin/psrldqs.S
@@ -0,0 +1,94 @@
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/macros.h"
+
+/ Jump table for psrldq() with non-constexpr immediate parameter.
+ .align 8
+__psrldqs:
+ psrldq $0,%xmm0
+ ret
+ nop
+ nop
+ psrldq $1,%xmm0
+ ret
+ nop
+ nop
+ psrldq $2,%xmm0
+ ret
+ nop
+ nop
+ psrldq $3,%xmm0
+ ret
+ nop
+ nop
+ psrldq $4,%xmm0
+ ret
+ nop
+ nop
+ psrldq $5,%xmm0
+ ret
+ nop
+ nop
+ psrldq $6,%xmm0
+ ret
+ nop
+ nop
+ psrldq $7,%xmm0
+ ret
+ nop
+ nop
+ psrldq $8,%xmm0
+ ret
+ nop
+ nop
+ psrldq $9,%xmm0
+ ret
+ nop
+ nop
+ psrldq $10,%xmm0
+ ret
+ nop
+ nop
+ psrldq $11,%xmm0
+ ret
+ nop
+ nop
+ psrldq $12,%xmm0
+ ret
+ nop
+ nop
+ psrldq $13,%xmm0
+ ret
+ nop
+ nop
+ psrldq $14,%xmm0
+ ret
+ nop
+ nop
+ psrldq $15,%xmm0
+ ret
+ nop
+ nop
+ psrldq $16,%xmm0
+ ret
+ .if . - __psrldqs != 8 * 17 - 2
+ .error "bad assemblage"
+ .endif
+ .endfn __psrldqs,globl
diff --git a/libc/intrin/psrldv.c b/libc/intrin/psrldv.c
new file mode 100644
index 000000000..7b7ffd691
--- /dev/null
+++ b/libc/intrin/psrldv.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psrld.h"
+#include "libc/str/str.h"
+
+/**
+ * Divides ints by two power.
+ *
+ * @note logical shift does not sign extend negatives
+ * @mayalias
+ */
+void(psrldv)(uint32_t a[4], const uint32_t b[4], const uint64_t c[2]) {
+ unsigned i;
+ if (c[0] <= 31) {
+ for (i = 0; i < 4; ++i) {
+ a[i] = b[i] >> c[0];
+ }
+ } else {
+ memset(a, 0, 16);
+ }
+}
diff --git a/libc/intrin/psrlq.c b/libc/intrin/psrlq.c
new file mode 100644
index 000000000..6a918ae92
--- /dev/null
+++ b/libc/intrin/psrlq.c
@@ -0,0 +1,39 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psrlq.h"
+#include "libc/str/str.h"
+
+/**
+ * Divides unsigned longs by two power.
+ *
+ * @note c needs to be a literal, asmconstexpr, or linkconstsym
+ * @note logical shift does not sign extend negatives
+ * @mayalias
+ */
+void(psrlq)(uint64_t a[2], const uint64_t b[2], unsigned char c) {
+ unsigned i;
+ if (c <= 63) {
+ for (i = 0; i < 2; ++i) {
+ a[i] = b[i] >> c;
+ }
+ } else {
+ memset(a, 0, 16);
+ }
+}
diff --git a/libc/intrin/psrlq.h b/libc/intrin/psrlq.h
new file mode 100644
index 000000000..96abe9c4d
--- /dev/null
+++ b/libc/intrin/psrlq.h
@@ -0,0 +1,16 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSRLQ_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSRLQ_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psrlq(uint64_t[2], const uint64_t[2], unsigned char);
+void psrlqv(uint64_t[2], const uint64_t[2], const uint64_t[2]);
+
+#define psrlq(A, B, I) INTRIN_SSEVEX_X_I_(psrlq, SSE2, "psrlq", A, B, I)
+#define psrlqv(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psrlqv, SSE2, "psrlq", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSRLQ_H_ */
diff --git a/libc/intrin/psrlqv.c b/libc/intrin/psrlqv.c
new file mode 100644
index 000000000..6a47308b2
--- /dev/null
+++ b/libc/intrin/psrlqv.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psrlq.h"
+#include "libc/str/str.h"
+
+/**
+ * Divides unsigned longs by two power.
+ *
+ * @note logical shift does not sign extend negatives
+ * @mayalias
+ */
+void(psrlqv)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) {
+ unsigned i;
+ if (c[0] <= 63) {
+ for (i = 0; i < 2; ++i) {
+ a[i] = b[i] >> c[0];
+ }
+ } else {
+ memset(a, 0, 16);
+ }
+}
diff --git a/libc/intrin/psrlw.c b/libc/intrin/psrlw.c
new file mode 100644
index 000000000..faf20466d
--- /dev/null
+++ b/libc/intrin/psrlw.c
@@ -0,0 +1,39 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psrlw.h"
+#include "libc/str/str.h"
+
+/**
+ * Divides unsigned shorts by two power.
+ *
+ * @note c needs to be a literal, asmconstexpr, or linkconstsym
+ * @note logical shift does not sign extend negatives
+ * @mayalias
+ */
+void(psrlw)(uint16_t a[8], const uint16_t b[8], unsigned char c) {
+ unsigned i;
+ if (c < 16) {
+ for (i = 0; i < 8; ++i) {
+ a[i] = b[i] >> c;
+ }
+ } else {
+ memset(a, 0, 16);
+ }
+}
diff --git a/libc/intrin/psrlw.h b/libc/intrin/psrlw.h
new file mode 100644
index 000000000..25dee7f9c
--- /dev/null
+++ b/libc/intrin/psrlw.h
@@ -0,0 +1,16 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSRLW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSRLW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psrlw(uint16_t[8], const uint16_t[8], unsigned char);
+void psrlwv(uint16_t[8], const uint16_t[8], const uint64_t[2]);
+
+#define psrlw(A, B, I) INTRIN_SSEVEX_X_I_(psrlw, SSE2, "psrlw", A, B, I)
+#define psrlwv(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psrlwv, SSE2, "psrlw", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSRLW_H_ */
diff --git a/libc/intrin/psrlwv.c b/libc/intrin/psrlwv.c
new file mode 100644
index 000000000..dd3c2f055
--- /dev/null
+++ b/libc/intrin/psrlwv.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psrlw.h"
+#include "libc/str/str.h"
+
+/**
+ * Divides unsigned shorts by two power.
+ *
+ * @note logical shift does not sign extend negatives
+ * @mayalias
+ */
+void(psrlwv)(uint16_t a[8], const uint16_t b[8], const uint64_t c[2]) {
+ unsigned i;
+ if (c[0] < 16) {
+ for (i = 0; i < 8; ++i) {
+ a[i] = b[i] >> c[0];
+ }
+ } else {
+ memset(a, 0, 16);
+ }
+}
diff --git a/libc/intrin/psubb.c b/libc/intrin/psubb.c
new file mode 100644
index 000000000..1f02b6f52
--- /dev/null
+++ b/libc/intrin/psubb.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psubb.h"
+#include "libc/str/str.h"
+
+/**
+ * Subtracts 8-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(psubb)(int8_t a[16], const int8_t b[16], const int8_t c[16]) {
+ unsigned i;
+ int8_t r[16];
+ for (i = 0; i < 16; ++i) {
+ r[i] = b[i] - c[i];
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/psubb.h b/libc/intrin/psubb.h
new file mode 100644
index 000000000..fd6e0f46f
--- /dev/null
+++ b/libc/intrin/psubb.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSUBB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psubb(int8_t[16], const int8_t[16], const int8_t[16]);
+
+#define psubb(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psubb, SSE2, "psubb", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBB_H_ */
diff --git a/libc/intrin/psubd.c b/libc/intrin/psubd.c
new file mode 100644
index 000000000..5da83b9e2
--- /dev/null
+++ b/libc/intrin/psubd.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psubd.h"
+#include "libc/str/str.h"
+
+/**
+ * Subtracts 32-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(psubd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) {
+ unsigned i;
+ int32_t r[4];
+ for (i = 0; i < 4; ++i) {
+ r[i] = b[i] - c[i];
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/psubd.h b/libc/intrin/psubd.h
new file mode 100644
index 000000000..c8f396da4
--- /dev/null
+++ b/libc/intrin/psubd.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSUBD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psubd(int32_t[4], const int32_t[4], const int32_t[4]);
+
+#define psubd(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psubd, SSE2, "psubd", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBD_H_ */
diff --git a/libc/intrin/psubq.c b/libc/intrin/psubq.c
new file mode 100644
index 000000000..ddfc3401b
--- /dev/null
+++ b/libc/intrin/psubq.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psubq.h"
+#include "libc/str/str.h"
+
+/**
+ * Subtracts 64-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(psubq)(int64_t a[2], const int64_t b[2], const int64_t c[2]) {
+ unsigned i;
+ int64_t r[2];
+ for (i = 0; i < 2; ++i) {
+ r[i] = b[i] - c[i];
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/psubq.h b/libc/intrin/psubq.h
new file mode 100644
index 000000000..d08bb6f7c
--- /dev/null
+++ b/libc/intrin/psubq.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBQ_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSUBQ_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psubq(int64_t[2], const int64_t[2], const int64_t[2]);
+
+#define psubq(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psubq, SSE2, "psubq", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBQ_H_ */
diff --git a/libc/intrin/psubsb.c b/libc/intrin/psubsb.c
new file mode 100644
index 000000000..b48fb0e6d
--- /dev/null
+++ b/libc/intrin/psubsb.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psubsb.h"
+#include "libc/limits.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Subtracts signed 8-bit integers w/ saturation.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(psubsb)(int8_t a[16], const int8_t b[16], const int8_t c[16]) {
+ unsigned i;
+ int8_t r[16];
+ for (i = 0; i < 16; ++i) r[i] = MIN(INT8_MAX, MAX(INT8_MIN, b[i] - c[i]));
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/psubsb.h b/libc/intrin/psubsb.h
new file mode 100644
index 000000000..4e263c0d9
--- /dev/null
+++ b/libc/intrin/psubsb.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBSB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSUBSB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psubsb(int8_t[16], const int8_t[16], const int8_t[16]);
+
+#define psubsb(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psubsb, SSE2, "psubsb", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBSB_H_ */
diff --git a/libc/intrin/psubsw.c b/libc/intrin/psubsw.c
new file mode 100644
index 000000000..feb10439f
--- /dev/null
+++ b/libc/intrin/psubsw.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psubsw.h"
+#include "libc/limits.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Subtracts signed 16-bit integers w/ saturation.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(psubsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ int16_t r[8];
+ for (i = 0; i < 8; ++i) r[i] = MIN(INT16_MAX, MAX(INT16_MIN, b[i] - c[i]));
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/psubsw.h b/libc/intrin/psubsw.h
new file mode 100644
index 000000000..a6994ac37
--- /dev/null
+++ b/libc/intrin/psubsw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBSW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSUBSW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psubsw(int16_t[8], const int16_t[8], const int16_t[8]);
+
+#define psubsw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psubsw, SSE2, "psubsw", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBSW_H_ */
diff --git a/libc/runtime/findmapping.c b/libc/intrin/psubusb.c
similarity index 78%
rename from libc/runtime/findmapping.c
rename to libc/intrin/psubusb.c
index cd4ba45b0..9470e3c51 100644
--- a/libc/runtime/findmapping.c
+++ b/libc/intrin/psubusb.c
@@ -17,33 +17,24 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/runtime/mappings.h"
+#include "libc/intrin/psubusb.h"
+#include "libc/limits.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
-size_t findmapping_(int32_t k, const struct MemoryCoord *coords, size_t count) {
- size_t l, r, m;
- l = 0;
- r = count;
- while (l < r) {
- m = (l + r) >> 1;
- if (coords[m].x > k) {
- r = m;
- } else {
- l = m + 1;
- }
+/**
+ * Subtracts unsigned 8-bit integers w/ saturation.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(psubusb)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) {
+ unsigned i;
+ uint8_t r[16];
+ for (i = 0; i < 16; ++i) {
+ r[i] = MIN(UINT8_MAX, MAX(UINT8_MIN, b[i] - c[i]));
}
- return l;
-}
-
-/**
- * Returns appropriate rightmost index.
- */
-size_t findmapping(int32_t k) {
- return findmapping_(k, _mm.p, _mm.i);
-}
-
-/**
- * Returns appropriate rightmost index.
- */
-size_t pickmapping(int32_t k) {
- return findmapping_(k, _mm.p, _mm.i);
+ memcpy(a, r, 16);
}
diff --git a/libc/intrin/psubusb.h b/libc/intrin/psubusb.h
new file mode 100644
index 000000000..b93050cd3
--- /dev/null
+++ b/libc/intrin/psubusb.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBUSB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSUBUSB_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psubusb(uint8_t[16], const uint8_t[16], const uint8_t[16]);
+
+#define psubusb(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psubusb, SSE2, "psubusb", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBUSB_H_ */
diff --git a/libc/intrin/psubusw.c b/libc/intrin/psubusw.c
new file mode 100644
index 000000000..250295aab
--- /dev/null
+++ b/libc/intrin/psubusw.c
@@ -0,0 +1,40 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psubusw.h"
+#include "libc/limits.h"
+#include "libc/macros.h"
+#include "libc/str/str.h"
+
+/**
+ * Subtracts unsigned 16-bit integers w/ saturation.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(psubusw)(uint16_t a[8], const uint16_t b[8], const uint16_t c[8]) {
+ unsigned i;
+ uint16_t r[8];
+ for (i = 0; i < 8; ++i) {
+ r[i] = MIN(UINT16_MAX, MAX(UINT16_MIN, b[i] - c[i]));
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/psubusw.h b/libc/intrin/psubusw.h
new file mode 100644
index 000000000..2a96ba9ed
--- /dev/null
+++ b/libc/intrin/psubusw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBUSW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSUBUSW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void psubusw(uint16_t[8], const uint16_t[8], const uint16_t[8]);
+
+#define psubusw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psubusw, SSE2, "psubusw", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBUSW_H_ */
diff --git a/libc/intrin/psubw.c b/libc/intrin/psubw.c
new file mode 100644
index 000000000..e553f49cd
--- /dev/null
+++ b/libc/intrin/psubw.c
@@ -0,0 +1,38 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/psubw.h"
+#include "libc/str/str.h"
+
+/**
+ * Subtracts 16-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(psubw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) {
+ unsigned i;
+ int16_t r[8];
+ for (i = 0; i < 8; ++i) {
+ r[i] = b[i] - c[i];
+ }
+ memcpy(a, r, 16);
+}
diff --git a/libc/intrin/psubw.h b/libc/intrin/psubw.h
new file mode 100644
index 000000000..52b218706
--- /dev/null
+++ b/libc/intrin/psubw.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PSUBW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void(psubw)(int16_t[8], const int16_t[8], const int16_t[8]);
+
+#define psubw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(psubw, SSE2, "psubw", INTRIN_NONCOMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBW_H_ */
diff --git a/libc/intrin/punpckhbw.c b/libc/intrin/punpckhbw.c
new file mode 100644
index 000000000..a01323884
--- /dev/null
+++ b/libc/intrin/punpckhbw.c
@@ -0,0 +1,47 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/punpckhbw.h"
+
+/**
+ * Interleaves high bytes.
+ *
+ * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved
+ * @param 𝑏 [r/o] supplies eight words
+ * @param 𝑐 [r/o] supplies eight words
+ * @mayalias
+ */
+void(punpckhbw)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) {
+ a[0x0] = b[0x8];
+ a[0x1] = c[0x8];
+ a[0x2] = b[0x9];
+ a[0x3] = c[0x9];
+ a[0x4] = b[0xa];
+ a[0x5] = c[0xa];
+ a[0x6] = b[0xb];
+ a[0x7] = c[0xb];
+ a[0x8] = b[0xc];
+ a[0x9] = c[0xc];
+ a[0xa] = b[0xd];
+ a[0xb] = c[0xd];
+ a[0xc] = b[0xe];
+ a[0xd] = c[0xe];
+ a[0xe] = b[0xf];
+ a[0xf] = c[0xf];
+}
diff --git a/libc/intrin/punpckhbw.h b/libc/intrin/punpckhbw.h
new file mode 100644
index 000000000..69ca1fc4e
--- /dev/null
+++ b/libc/intrin/punpckhbw.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKHBW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKHBW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void punpckhbw(uint8_t[16], const uint8_t[16], const uint8_t[16]);
+
+#define punpckhbw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(punpckhbw, SSE2, "punpckhbw", INTRIN_NONCOMMUTATIVE, A, \
+ B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKHBW_H_ */
diff --git a/libc/intrin/punpckhdq.c b/libc/intrin/punpckhdq.c
new file mode 100644
index 000000000..5b751b901
--- /dev/null
+++ b/libc/intrin/punpckhdq.c
@@ -0,0 +1,43 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/punpckhdq.h"
+
+/**
+ * Interleaves high doublewords.
+ *
+ * 0 1 2 3
+ * B aaaa bbbb CCCC DDDD
+ * C eeee ffff GGGG HHHH
+ * └┬─┘ └─┬┘
+ * ┌────┘ │
+ * ┌─────┴─┐ ┌──────┴┐
+ * → A CCCC GGGG DDDD HHHH
+ *
+ * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved
+ * @param 𝑏 [r/o] supplies four doublewords
+ * @param 𝑐 [r/o] supplies four doublewords
+ * @mayalias
+ */
+void(punpckhdq)(uint32_t a[4], const uint32_t b[4], const uint32_t c[4]) {
+ a[0] = b[2];
+ a[1] = c[2];
+ a[2] = b[3];
+ a[3] = c[3];
+}
diff --git a/libc/intrin/punpckhdq.h b/libc/intrin/punpckhdq.h
new file mode 100644
index 000000000..b25165852
--- /dev/null
+++ b/libc/intrin/punpckhdq.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKHDQ_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKHDQ_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void punpckhdq(uint32_t[4], const uint32_t[4], const uint32_t[4]);
+
+#define punpckhdq(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(punpckhdq, SSE2, "punpckhdq", INTRIN_NONCOMMUTATIVE, A, \
+ B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKHDQ_H_ */
diff --git a/libc/intrin/punpckhqdq.c b/libc/intrin/punpckhqdq.c
new file mode 100644
index 000000000..51a8e6ac2
--- /dev/null
+++ b/libc/intrin/punpckhqdq.c
@@ -0,0 +1,33 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/punpckhqdq.h"
+
+/**
+ * Interleaves high quadwords.
+ *
+ * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved
+ * @param 𝑏 [r/o] supplies two quadwords
+ * @param 𝑐 [r/o] supplies two quadwords
+ * @mayalias
+ */
+void(punpckhqdq)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) {
+ a[0] = b[1];
+ a[1] = c[1];
+}
diff --git a/libc/intrin/punpckhqdq.h b/libc/intrin/punpckhqdq.h
new file mode 100644
index 000000000..ced06c5f7
--- /dev/null
+++ b/libc/intrin/punpckhqdq.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKHQDQ_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKHQDQ_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void punpckhqdq(uint64_t[2], const uint64_t[2], const uint64_t[2]);
+
+#define punpckhqdq(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(punpckhqdq, SSE2, "punpckhqdq", INTRIN_NONCOMMUTATIVE, \
+ A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKHQDQ_H_ */
diff --git a/libc/intrin/punpckhwd.c b/libc/intrin/punpckhwd.c
new file mode 100644
index 000000000..8b7b51917
--- /dev/null
+++ b/libc/intrin/punpckhwd.c
@@ -0,0 +1,50 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/punpckhwd.h"
+#include "libc/str/str.h"
+
+/**
+ * Interleaves high words.
+ *
+ * 0 1 2 3 4 5 6 7
+ * B aa bb cc dd EE FF GG HH
+ * C ii jj kk ll MM NN OO PP
+ * └┤ └┤ └┤ └┤
+ * ┌────────┘ │ │ │
+ * │ ┌─────┘ │ │
+ * │ │ ┌──┘ │
+ * ┌───┤ ┌───┤ ┌───┤ ┌───┤
+ * → A EE MM FF NN GG OO HH PP
+ *
+ * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved
+ * @param 𝑏 [r/o] supplies eight words
+ * @param 𝑐 [r/o] supplies eight words
+ * @mayalias
+ */
+void(punpckhwd)(uint16_t a[8], const uint16_t b[8], const uint16_t c[8]) {
+ a[0] = b[4];
+ a[1] = c[4];
+ a[2] = b[5];
+ a[3] = c[5];
+ a[4] = b[6];
+ a[5] = c[6];
+ a[6] = b[7];
+ a[7] = c[7];
+}
diff --git a/libc/intrin/punpckhwd.h b/libc/intrin/punpckhwd.h
new file mode 100644
index 000000000..4d850e9a6
--- /dev/null
+++ b/libc/intrin/punpckhwd.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKHWD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKHWD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void punpckhwd(uint16_t[8], const uint16_t[8], const uint16_t[8]);
+
+#define punpckhwd(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(punpckhwd, SSE2, "punpckhwd", INTRIN_NONCOMMUTATIVE, A, \
+ B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKHWD_H_ */
diff --git a/libc/intrin/punpcklbw.c b/libc/intrin/punpcklbw.c
new file mode 100644
index 000000000..980d04423
--- /dev/null
+++ b/libc/intrin/punpcklbw.c
@@ -0,0 +1,57 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/punpcklbw.h"
+
+/**
+ * Interleaves low bytes.
+ *
+ * 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ * B A B C D E F G H i j k l m n o p
+ * C Q R S T U V W X y z α σ π μ τ ε
+ * │ │ │ │ │ │ │ │
+ * │ │ │ └─────┐
+ * │ │ └───┐ │ etc...
+ * │ └─┐ │ │
+ * ├─┐ ├─┐ ├─┐ ├─┐
+ * → A A Q B R C S D T E U F V G W H X
+ *
+ * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved
+ * @param 𝑏 [r/o] supplies eight words
+ * @param 𝑐 [r/o] supplies eight words
+ * @mayalias
+ */
+void(punpcklbw)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) {
+ a[0xf] = c[7];
+ a[0xe] = b[7];
+ a[0xd] = c[6];
+ a[0xc] = b[6];
+ a[0xb] = c[5];
+ a[0xa] = b[5];
+ a[0x9] = c[4];
+ a[0x8] = b[4];
+ a[0x7] = c[3];
+ a[0x6] = b[3];
+ a[0x5] = c[2];
+ a[0x4] = b[2];
+ a[0x3] = c[1];
+ a[0x2] = b[1];
+ a[0x1] = c[0];
+ a[0x0] = b[0];
+}
diff --git a/libc/intrin/punpcklbw.h b/libc/intrin/punpcklbw.h
new file mode 100644
index 000000000..259ec0447
--- /dev/null
+++ b/libc/intrin/punpcklbw.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKLBW_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKLBW_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void punpcklbw(uint8_t[16], const uint8_t[16], const uint8_t[16]);
+
+#define punpcklbw(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(punpcklbw, SSE2, "punpcklbw", INTRIN_NONCOMMUTATIVE, A, \
+ B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKLBW_H_ */
diff --git a/libc/intrin/punpckldq.c b/libc/intrin/punpckldq.c
new file mode 100644
index 000000000..220aedc9e
--- /dev/null
+++ b/libc/intrin/punpckldq.c
@@ -0,0 +1,43 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/punpckldq.h"
+
+/**
+ * Interleaves low doublewords.
+ *
+ * 0 1 2 3
+ * B AAAA BBBB cccc dddd
+ * C EEEE FFFF gggg hhhh
+ * └┬─┘ └─┬┘
+ * │ └───┐
+ * ┌┴──────┐ ┌┴──────┐
+ * → A AAAA EEEE BBBB FFFF
+ *
+ * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved
+ * @param 𝑏 [r/o] supplies four doublewords
+ * @param 𝑐 [r/o] supplies four doublewords
+ * @mayalias
+ */
+void(punpckldq)(uint32_t a[4], const uint32_t b[4], const uint32_t c[4]) {
+ a[3] = c[1];
+ a[2] = b[1];
+ a[1] = c[0];
+ a[0] = b[0];
+}
diff --git a/libc/intrin/punpckldq.h b/libc/intrin/punpckldq.h
new file mode 100644
index 000000000..b52d61322
--- /dev/null
+++ b/libc/intrin/punpckldq.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKLDQ_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKLDQ_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void punpckldq(uint32_t[4], const uint32_t[4], const uint32_t[4]);
+
+#define punpckldq(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(punpckldq, SSE2, "punpckldq", INTRIN_NONCOMMUTATIVE, A, \
+ B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKLDQ_H_ */
diff --git a/libc/intrin/punpcklqdq.c b/libc/intrin/punpcklqdq.c
new file mode 100644
index 000000000..b621affdc
--- /dev/null
+++ b/libc/intrin/punpcklqdq.c
@@ -0,0 +1,33 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/punpcklqdq.h"
+
+/**
+ * Interleaves low quadwords.
+ *
+ * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved
+ * @param 𝑏 [r/o] supplies two quadwords
+ * @param 𝑐 [r/o] supplies two quadwords
+ * @mayalias
+ */
+void(punpcklqdq)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) {
+ a[1] = c[0];
+ a[0] = b[0];
+}
diff --git a/libc/intrin/punpcklqdq.h b/libc/intrin/punpcklqdq.h
new file mode 100644
index 000000000..fc0f88f90
--- /dev/null
+++ b/libc/intrin/punpcklqdq.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKLQDQ_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKLQDQ_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void punpcklqdq(uint64_t[2], const uint64_t[2], const uint64_t[2]);
+
+#define punpcklqdq(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(punpcklqdq, SSE2, "punpcklqdq", INTRIN_NONCOMMUTATIVE, \
+ A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKLQDQ_H_ */
diff --git a/libc/intrin/punpcklwd.c b/libc/intrin/punpcklwd.c
new file mode 100644
index 000000000..7eca67197
--- /dev/null
+++ b/libc/intrin/punpcklwd.c
@@ -0,0 +1,49 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/punpcklwd.h"
+
+/**
+ * Interleaves low words.
+ *
+ * 0 1 2 3 4 5 6 7
+ * B AA BB CC DD ee ff gg hh
+ * C II JJ KK LL mm nn oo pp
+ * ├┘ ├┘ ├┘ ├┘
+ * │ │ │ └────────┐
+ * │ │ └─────┐ │
+ * │ └──┐ │ │
+ * ├───┐ ├───┐ ├───┐ ├───┐
+ * → A AA II BB JJ CC KK DD LL
+ *
+ * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved
+ * @param 𝑏 [r/o] supplies eight words
+ * @param 𝑐 [r/o] supplies eight words
+ * @mayalias
+ */
+void(punpcklwd)(uint16_t a[8], const uint16_t b[8], const uint16_t c[8]) {
+ a[7] = c[3];
+ a[6] = b[3];
+ a[5] = c[2];
+ a[4] = b[2];
+ a[3] = c[1];
+ a[2] = b[1];
+ a[1] = c[0];
+ a[0] = b[0];
+}
diff --git a/libc/intrin/punpcklwd.h b/libc/intrin/punpcklwd.h
new file mode 100644
index 000000000..f7e99e990
--- /dev/null
+++ b/libc/intrin/punpcklwd.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKLWD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKLWD_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void punpcklwd(uint16_t[8], const uint16_t[8], const uint16_t[8]);
+
+#define punpcklwd(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(punpcklwd, SSE2, "punpcklwd", INTRIN_NONCOMMUTATIVE, A, \
+ B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKLWD_H_ */
diff --git a/libc/intrin/pxor.c b/libc/intrin/pxor.c
new file mode 100644
index 000000000..5ea88f234
--- /dev/null
+++ b/libc/intrin/pxor.c
@@ -0,0 +1,35 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pxor.h"
+
+/**
+ * Xors 128-bit integers.
+ *
+ * @param 𝑎 [w/o] receives result
+ * @param 𝑏 [r/o] supplies first input vector
+ * @param 𝑐 [r/o] supplies second input vector
+ * @mayalias
+ */
+void(pxor)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) {
+ unsigned i;
+ for (i = 0; i < 2; ++i) {
+ a[i] = b[i] ^ c[i];
+ }
+}
diff --git a/libc/intrin/pxor.h b/libc/intrin/pxor.h
new file mode 100644
index 000000000..aee0120cd
--- /dev/null
+++ b/libc/intrin/pxor.h
@@ -0,0 +1,14 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_PXOR_H_
+#define COSMOPOLITAN_LIBC_INTRIN_PXOR_H_
+#include "libc/intrin/macros.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void pxor(uint64_t[2], const uint64_t[2], const uint64_t[2]);
+
+#define pxor(A, B, C) \
+ INTRIN_SSEVEX_X_X_X_(pxor, SSE2, "pxor", INTRIN_COMMUTATIVE, A, B, C)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_PXOR_H_ */
diff --git a/libc/intrin/repstosb.h b/libc/intrin/repstosb.h
new file mode 100644
index 000000000..4536f7f86
--- /dev/null
+++ b/libc/intrin/repstosb.h
@@ -0,0 +1,25 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_REPSTOSB_H_
+#define COSMOPOLITAN_LIBC_INTRIN_REPSTOSB_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+
+static void *repstosb(void *dest, unsigned char al, size_t cx) {
+ unsigned char *di = (unsigned char *)dest;
+ while (cx) *di++ = al, cx--;
+ return di;
+}
+
+#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+#define repstosb(DI, AL, CX) \
+ ({ \
+ void *Di = (DI); \
+ size_t Cx = (CX); \
+ unsigned char Al = (AL); \
+ asm("rep stosb" \
+ : "=D"(Di), "=c"(Cx), "=m"(*(char(*)[Cx])Di) \
+ : "0"(Di), "1"(Cx), "a"(Al)); \
+ Di; \
+ })
+#endif
+
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_REPSTOSB_H_ */
diff --git a/libc/intrin/shufpd.c b/libc/intrin/shufpd.c
new file mode 100644
index 000000000..fc0aab03f
--- /dev/null
+++ b/libc/intrin/shufpd.c
@@ -0,0 +1,33 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/shufpd.h"
+
+/**
+ * Shuffles double vector.
+ * @param 𝑚 needs to be a literal, constexpr, or embedding
+ * @mayalias
+ */
+void(shufpd)(double b[2], const double a[2], uint8_t m) {
+ double t[2];
+ t[0] = a[(m & 0b0000001) >> 0];
+ t[1] = a[(m & 0b0000010) >> 1];
+ b[0] = t[0];
+ b[1] = t[1];
+}
diff --git a/libc/intrin/shufpd.h b/libc/intrin/shufpd.h
new file mode 100644
index 000000000..62cf5a0bd
--- /dev/null
+++ b/libc/intrin/shufpd.h
@@ -0,0 +1,10 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_SHUFPD_H_
+#define COSMOPOLITAN_LIBC_INTRIN_SHUFPD_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void shufpd(double[2], const double[2], uint8_t);
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_SHUFPD_H_ */
diff --git a/libc/intrin/shufps.c b/libc/intrin/shufps.c
new file mode 100644
index 000000000..350584f2d
--- /dev/null
+++ b/libc/intrin/shufps.c
@@ -0,0 +1,37 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/shufps.h"
+
+/**
+ * Shuffles float vector.
+ * @param 𝑚 needs to be a literal, constexpr, or embedding
+ * @mayalias
+ */
+void(shufps)(float b[4], const float a[4], uint8_t m) {
+ float t[4];
+ t[0] = a[(m & 0b00000011) >> 0];
+ t[1] = a[(m & 0b00001100) >> 2];
+ t[2] = a[(m & 0b00110000) >> 4];
+ t[3] = a[(m & 0b11000000) >> 6];
+ b[0] = t[0];
+ b[1] = t[1];
+ b[2] = t[2];
+ b[3] = t[3];
+}
diff --git a/libc/intrin/shufps.h b/libc/intrin/shufps.h
new file mode 100644
index 000000000..bb5e824fb
--- /dev/null
+++ b/libc/intrin/shufps.h
@@ -0,0 +1,10 @@
+#ifndef COSMOPOLITAN_LIBC_INTRIN_SHUFPS_H_
+#define COSMOPOLITAN_LIBC_INTRIN_SHUFPS_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void shufps(float[4], const float[4], uint8_t);
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_INTRIN_SHUFPS_H_ */
diff --git a/libc/intrin/vpalignr.c b/libc/intrin/vpalignr.c
deleted file mode 100644
index 10fe6f188..000000000
--- a/libc/intrin/vpalignr.c
+++ /dev/null
@@ -1,131 +0,0 @@
-/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
-│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/intrin/palignr.h"
-
-/**
- * Shifts and concatenates xmm registers.
- *
- * @param i may be a non-literal
- * @see palignr()
- * @mayalias
- */
-void pvalignr(void *p, const void *prev, const void *next, size_t i) {
- switch (i) {
- case 0:
- palignr(p, prev, next, 0);
- break;
- case 1:
- palignr(p, prev, next, 1);
- break;
- case 2:
- palignr(p, prev, next, 2);
- break;
- case 3:
- palignr(p, prev, next, 3);
- break;
- case 4:
- palignr(p, prev, next, 4);
- break;
- case 5:
- palignr(p, prev, next, 5);
- break;
- case 6:
- palignr(p, prev, next, 6);
- break;
- case 7:
- palignr(p, prev, next, 7);
- break;
- case 8:
- palignr(p, prev, next, 8);
- break;
- case 9:
- palignr(p, prev, next, 9);
- break;
- case 10:
- palignr(p, prev, next, 10);
- break;
- case 11:
- palignr(p, prev, next, 11);
- break;
- case 12:
- palignr(p, prev, next, 12);
- break;
- case 13:
- palignr(p, prev, next, 13);
- break;
- case 14:
- palignr(p, prev, next, 14);
- break;
- case 15:
- palignr(p, prev, next, 15);
- break;
- case 16:
- palignr(p, prev, next, 16);
- break;
- case 17:
- palignr(p, prev, next, 17);
- break;
- case 18:
- palignr(p, prev, next, 18);
- break;
- case 19:
- palignr(p, prev, next, 19);
- break;
- case 20:
- palignr(p, prev, next, 20);
- break;
- case 21:
- palignr(p, prev, next, 21);
- break;
- case 22:
- palignr(p, prev, next, 22);
- break;
- case 23:
- palignr(p, prev, next, 23);
- break;
- case 24:
- palignr(p, prev, next, 24);
- break;
- case 25:
- palignr(p, prev, next, 25);
- break;
- case 26:
- palignr(p, prev, next, 26);
- break;
- case 27:
- palignr(p, prev, next, 27);
- break;
- case 28:
- palignr(p, prev, next, 28);
- break;
- case 29:
- palignr(p, prev, next, 29);
- break;
- case 30:
- palignr(p, prev, next, 30);
- break;
- case 31:
- palignr(p, prev, next, 31);
- break;
- default:
- palignr(p, prev, next, 32);
- break;
- }
-}
diff --git a/libc/leb128.h b/libc/leb128.h
deleted file mode 100644
index 6370e3b2a..000000000
--- a/libc/leb128.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef COSMOPOLITAN_LIBC_LEB128_H_
-#define COSMOPOLITAN_LIBC_LEB128_H_
-#if !(__ASSEMBLER__ + __LINKER__ + 0)
-
-/**
- * Decodes a GNU-style varint from a buffer.
- *
- * The GNU Assembler is able to encode numbers this way, since it's used
- * by the DWARF debug format.
- */
-forceinline int unsleb128(const void *buf, size_t size, int64_t *out) {
- const unsigned char *p = (const unsigned char *)buf;
- const unsigned char *pe = (const unsigned char *)buf + size;
- int64_t res = 0;
- int bits = 0;
- unsigned char c;
- do {
- if (size && p == pe) return -1;
- c = *p++;
- res |= (int64_t)(c & 0x7f) << bits;
- bits += 7;
- } while (c & 0x80);
- if ((c & 0x40) != 0) res |= -1ULL << bits;
- if (out) *out = res;
- return p - (const unsigned char *)buf;
-}
-
-#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#endif /* COSMOPOLITAN_LIBC_LEB128_H_ */
diff --git a/libc/libc.mk b/libc/libc.mk
index 1c1faeaed..f02d30dce 100644
--- a/libc/libc.mk
+++ b/libc/libc.mk
@@ -2,14 +2,10 @@
#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘
PKGS += LIBC
-LIBC_FILES := $(wildcard libc/*) $(wildcard libc/internal/*)
-LIBC_HDRS = $(filter %.h,$(LIBC_FILES))
-LIBC_CHECKS = $(LIBC_HDRS:%=o/$(MODE)/%.ok)
-.PHONY: o/libc
-o/libc: o/libc/stubs \
- o/libc/sysv \
- o/libc/nt
+LIBC_HDRS = $(filter %.h,$(LIBC_FILES))
+LIBC_FILES := $(wildcard libc/*) $(wildcard libc/internal/*)
+LIBC_CHECKS = $(LIBC_HDRS:%=o/$(MODE)/%.ok)
.PHONY: o/$(MODE)/libc
o/$(MODE)/libc: o/$(MODE)/libc/alg \
@@ -22,16 +18,20 @@ o/$(MODE)/libc: o/$(MODE)/libc/alg \
o/$(MODE)/libc/escape \
o/$(MODE)/libc/fmt \
o/$(MODE)/libc/intrin \
+ o/$(MODE)/libc/linux \
o/$(MODE)/libc/log \
o/$(MODE)/libc/math \
o/$(MODE)/libc/mem \
o/$(MODE)/libc/nexgen32e \
+ o/$(MODE)/libc/nt \
o/$(MODE)/libc/ohmyplus \
o/$(MODE)/libc/rand \
o/$(MODE)/libc/runtime \
o/$(MODE)/libc/sock \
o/$(MODE)/libc/stdio \
o/$(MODE)/libc/str \
+ o/$(MODE)/libc/stubs \
+ o/$(MODE)/libc/sysv \
o/$(MODE)/libc/testlib \
o/$(MODE)/libc/time \
o/$(MODE)/libc/tinymath \
diff --git a/libc/limits.h b/libc/limits.h
index 7e012e3fb..0f093bb20 100644
--- a/libc/limits.h
+++ b/libc/limits.h
@@ -86,6 +86,10 @@
(((intmax_t)0x7fffffffffffffff) << 64 | (intmax_t)0xffffffffffffffff)
#define UINTMAX_MAX \
(((uintmax_t)0xffffffffffffffff) << 64 | (uintmax_t)0xffffffffffffffff)
+#define INT128_MIN INTMAX_MIN
+#define INT128_MAX INTMAX_MAX
+#define UINT128_MIN ((uintmax_t)0)
+#define UINT128_MAX UINTMAX_MAX
#else
#define INTMAX_MAX __INT64_MAX__
#define UINTMAX_MAX __UINT64_MAX__
diff --git a/libc/linux/exit.h b/libc/linux/exit.h
new file mode 100644
index 000000000..655af633f
--- /dev/null
+++ b/libc/linux/exit.h
@@ -0,0 +1,16 @@
+#ifndef COSMOPOLITAN_LIBC_LINUX_EXIT_H_
+#define COSMOPOLITAN_LIBC_LINUX_EXIT_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+forceinline noreturn long LinuxExit(int rc) {
+ asm volatile("syscall"
+ : /* no outputs */
+ : "a"(0xE7), "D"(rc)
+ : "memory");
+ unreachable;
+}
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_LINUX_EXIT_H_ */
diff --git a/libc/linux/linux.mk b/libc/linux/linux.mk
new file mode 100644
index 000000000..c4872cee1
--- /dev/null
+++ b/libc/linux/linux.mk
@@ -0,0 +1,11 @@
+#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
+#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘
+
+PKGS += LIBC_LINUX
+
+LIBC_LINUX_HDRS = $(filter %.h,$(LIBC_FILES))
+LIBC_LINUX_FILES := $(wildcard libc/linux/*)
+LIBC_LINUX_CHECKS = $(LIBC_LINUX_HDRS:%=o/$(MODE)/%.ok)
+
+.PHONY: o/$(MODE)/libc/linux
+o/$(MODE)/linux: $(LIBC_LINUX_CHECKS)
diff --git a/libc/linux/read.h b/libc/linux/read.h
new file mode 100644
index 000000000..b3fae18ac
--- /dev/null
+++ b/libc/linux/read.h
@@ -0,0 +1,15 @@
+#ifndef COSMOPOLITAN_LIBC_LINUX_READ_H_
+#define COSMOPOLITAN_LIBC_LINUX_READ_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+
+forceinline long LinuxRead(int fd, void *data, unsigned long size) {
+ long rc;
+ asm volatile("syscall"
+ : "=a"(rc), "=m"(*(char(*)[size])data)
+ : "0"(0), "D"(fd), "S"(data), "d"(size)
+ : "rcx", "r11", "memory");
+ return rc;
+}
+
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_LINUX_READ_H_ */
diff --git a/libc/linux/write.h b/libc/linux/write.h
new file mode 100644
index 000000000..413f1a29c
--- /dev/null
+++ b/libc/linux/write.h
@@ -0,0 +1,18 @@
+#ifndef COSMOPOLITAN_LIBC_LINUX_WRITE_H_
+#define COSMOPOLITAN_LIBC_LINUX_WRITE_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+forceinline long LinuxWrite(int fd, const void *data, unsigned long size) {
+ long rc;
+ asm volatile("syscall"
+ : "=a"(rc)
+ : "0"(1), "D"(fd), "S"(data), "d"(size),
+ "m"(*(char(*)[size])data)
+ : "rcx", "r11", "memory");
+ return rc;
+}
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_LINUX_WRITE_H_ */
diff --git a/libc/log/addr2linepath.c b/libc/log/addr2linepath.c
new file mode 100644
index 000000000..b5cb0a89e
--- /dev/null
+++ b/libc/log/addr2linepath.c
@@ -0,0 +1,24 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/log/log.h"
+
+const char *GetAddr2linePath(void) {
+ return commandvenv("ADDR2LINE", "addr2line");
+}
diff --git a/libc/log/attachdebugger.c b/libc/log/attachdebugger.c
index 00e059c4d..16b9962c7 100644
--- a/libc/log/attachdebugger.c
+++ b/libc/log/attachdebugger.c
@@ -23,6 +23,7 @@
#include "libc/fmt/fmt.h"
#include "libc/log/gdb.h"
#include "libc/log/log.h"
+#include "libc/nexgen32e/vendor.h"
#include "libc/paths.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/symbols.h"
@@ -52,8 +53,8 @@ relegated int(attachdebugger)(intptr_t continuetoaddr) {
struct StackFrame *bp;
char pidstr[11], breakcmd[40];
const char *se, *elf, *gdb, *rewind, *layout;
- if (!((gdb = commandv(firstnonnull(getenv("GDB"), "gdb"))) &&
- (ttyin = ttyout = open(_PATH_TTY, O_RDWR, 0)) != -1)) {
+ if (IsGenuineCosmo() || !(gdb = GetGdbPath()) ||
+ (ttyin = ttyout = open(_PATH_TTY, O_RDWR, 0)) == -1) {
return -1;
}
snprintf(pidstr, sizeof(pidstr), "%u", getpid());
diff --git a/libc/log/backtrace.c b/libc/log/backtrace.c
index 30ff54e45..f5e3f702f 100644
--- a/libc/log/backtrace.c
+++ b/libc/log/backtrace.c
@@ -20,6 +20,6 @@
#include "libc/dce.h"
#include "libc/log/log.h"
-relegated void backtrace(FILE *f) {
+void backtrace(FILE *f) {
showbacktrace(f, __builtin_frame_address(0));
}
diff --git a/libc/log/backtrace2.c b/libc/log/backtrace2.c
index 888fd7ccb..a08a6df38 100644
--- a/libc/log/backtrace2.c
+++ b/libc/log/backtrace2.c
@@ -19,13 +19,14 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/alg/alg.h"
#include "libc/alg/bisectcarleft.h"
-#include "libc/bits/bits.h"
#include "libc/bits/safemacros.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/hefty/spawn.h"
#include "libc/conv/conv.h"
#include "libc/dce.h"
#include "libc/fmt/fmt.h"
+#include "libc/log/log.h"
#include "libc/nexgen32e/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/symbols.h"
@@ -36,7 +37,7 @@
#define kBacktraceMaxFrames 128
#define kBacktraceBufSize ((kBacktraceMaxFrames - 1) * (16 + 1))
-static char *formataddress(FILE *f, const struct SymbolTable *st, intptr_t addr,
+static char *FormatAddress(FILE *f, const struct SymbolTable *st, intptr_t addr,
char *out, unsigned size, bool symbolic) {
int64_t addend;
const char *name;
@@ -54,7 +55,7 @@ static char *formataddress(FILE *f, const struct SymbolTable *st, intptr_t addr,
return out;
}
-static int printbacktraceusingsymbols(FILE *f, const struct StackFrame *bp,
+static int PrintBacktraceUsingSymbols(FILE *f, const struct StackFrame *bp,
char buf[hasatleast kBacktraceBufSize]) {
size_t gi;
intptr_t addr;
@@ -62,17 +63,17 @@ static int printbacktraceusingsymbols(FILE *f, const struct StackFrame *bp,
struct SymbolTable *symbols;
const struct StackFrame *frame;
if ((symbols = getsymboltable())) {
- garbage = weaken(__garbage);
+ garbage = weaken(g_garbage);
gi = garbage ? garbage->i : 0;
for (frame = bp; frame; frame = frame->next) {
addr = frame->addr;
- if (addr == weakaddr("__gc")) {
+ if (addr == weakaddr("CollectGarbage")) {
do {
--gi;
- } while ((addr = garbage->p[gi].ret) == weakaddr("__gc"));
+ } while ((addr = garbage->p[gi].ret) == weakaddr("CollectGarbage"));
}
fprintf(f, "%p %p %s\n", frame, addr,
- formataddress(f, symbols, addr, buf, kBacktraceBufSize, true));
+ FormatAddress(f, symbols, addr, buf, kBacktraceBufSize, true));
}
return 0;
} else {
@@ -80,7 +81,7 @@ static int printbacktraceusingsymbols(FILE *f, const struct StackFrame *bp,
}
}
-static int printbacktraceusingaddr2line(
+static int PrintBacktraceUsingAddr2line(
FILE *f, const struct StackFrame *bp,
char buf[hasatleast kBacktraceBufSize],
char *argv[hasatleast kBacktraceMaxFrames]) {
@@ -90,10 +91,8 @@ static int printbacktraceusingaddr2line(
int rc, pid, tubes[3];
struct Garbages *garbage;
const struct StackFrame *frame;
- const char *addr2line, *debugbin, *p1, *p2, *p3;
- if (!(debugbin = finddebugbinary()) ||
- !(addr2line = emptytonull(firstnonnull(
- getenv("ADDR2LINE"), firstnonnull(commandv("addr2line"), ""))))) {
+ const char *debugbin, *p1, *p2, *p3, *addr2line;
+ if (!(debugbin = finddebugbinary()) || !(addr2line = GetAddr2linePath())) {
return -1;
}
i = 0;
@@ -102,14 +101,14 @@ static int printbacktraceusingaddr2line(
argv[i++] = "-a"; /* filter out w/ shell script wrapper for old versions */
argv[i++] = "-pCife";
argv[i++] = debugbin;
- garbage = weaken(__garbage);
+ garbage = weaken(g_garbage);
gi = garbage ? garbage->i : 0;
for (frame = bp; frame && i < kBacktraceMaxFrames - 1; frame = frame->next) {
addr = frame->addr;
- if (addr == weakaddr("__gc")) {
+ if (addr == weakaddr("CollectGarbage")) {
do {
--gi;
- } while ((addr = garbage->p[gi].ret) == weakaddr("__gc"));
+ } while ((addr = garbage->p[gi].ret) == weakaddr("CollectGarbage"));
}
argv[i++] = &buf[j];
j += snprintf(&buf[j], 17, "%#x", addr - 1) + 1;
@@ -118,7 +117,9 @@ static int printbacktraceusingaddr2line(
tubes[0] = STDIN_FILENO;
tubes[1] = -1;
tubes[2] = STDERR_FILENO;
- if ((pid = spawnve(0, tubes, addr2line, argv, environ)) == -1) return -1;
+ if ((pid = spawnve(0, tubes, addr2line, argv, environ)) == -1) {
+ return -1;
+ }
while ((got = read(tubes[1], buf, kBacktraceBufSize)) > 0) {
for (p1 = buf; got;) {
/*
@@ -147,15 +148,15 @@ static int printbacktraceusingaddr2line(
return 0;
}
-static noinline int printbacktrace(FILE *f, const struct StackFrame *bp,
+static noinline int PrintBacktrace(FILE *f, const struct StackFrame *bp,
char *argv[hasatleast kBacktraceMaxFrames],
char buf[hasatleast kBacktraceBufSize]) {
if (!IsTiny()) {
- if (printbacktraceusingaddr2line(f, bp, buf, argv) != -1) {
+ if (PrintBacktraceUsingAddr2line(f, bp, buf, argv) != -1) {
return 0;
}
}
- return printbacktraceusingsymbols(f, bp, buf);
+ return PrintBacktraceUsingSymbols(f, bp, buf);
}
void showbacktrace(FILE *f, const struct StackFrame *bp) {
@@ -164,7 +165,7 @@ void showbacktrace(FILE *f, const struct StackFrame *bp) {
char buf[kBacktraceBufSize];
if (!noreentry) {
noreentry = true;
- printbacktrace(f, bp, argv, buf);
+ PrintBacktrace(f, bp, argv, buf);
noreentry = 0;
}
}
diff --git a/libc/log/cancolor.c b/libc/log/cancolor.c
index a97ae02ce..f6bf61067 100644
--- a/libc/log/cancolor.c
+++ b/libc/log/cancolor.c
@@ -17,6 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/bits/weaken.h"
#include "libc/calls/internal.h"
#include "libc/dce.h"
#include "libc/log/log.h"
diff --git a/libc/log/check.h b/libc/log/check.h
index 910b25097..77129d50c 100644
--- a/libc/log/check.h
+++ b/libc/log/check.h
@@ -1,6 +1,7 @@
#ifndef COSMOPOLITAN_LIBC_LOG_CHECK_H_
#define COSMOPOLITAN_LIBC_LOG_CHECK_H_
#include "libc/dce.h"
+#include "libc/macros.h"
/**
* @fileoverview Modern assertions, courtesy of Elgoog.
@@ -19,12 +20,12 @@ COSMOPOLITAN_C_START_
#define CHECK_NOTNULL(X, ...) __CHK(ne, !=, NULL, "NULL", X, #X, "" __VA_ARGS__)
#define DCHECK(X, ...) __DCHK(eq, ==, true, "true", !!(X), #X, "" __VA_ARGS__)
-#define DCHECK_EQ(Y, X, ...) __DCHK(eq, ==, Y, #X, X, #Y, "" __VA_ARGS__)
-#define DCHECK_NE(Y, X, ...) __DCHK(ne, !=, Y, #X, X, #Y, "" __VA_ARGS__)
-#define DCHECK_LE(Y, X, ...) __DCHK(le, <=, Y, #X, X, #Y, "" __VA_ARGS__)
-#define DCHECK_LT(Y, X, ...) __DCHK(lt, <, Y, #X, X, #Y, "" __VA_ARGS__)
-#define DCHECK_GE(Y, X, ...) __DCHK(ge, >=, Y, #X, X, #Y, "" __VA_ARGS__)
-#define DCHECK_GT(Y, X, ...) __DCHK(gt, >, Y, #X, X, #Y, "" __VA_ARGS__)
+#define DCHECK_EQ(Y, X, ...) __DCHK(eq, ==, Y, #Y, X, #X, "" __VA_ARGS__)
+#define DCHECK_NE(Y, X, ...) __DCHK(ne, !=, Y, #Y, X, #X, "" __VA_ARGS__)
+#define DCHECK_LE(Y, X, ...) __DCHK(le, <=, Y, #Y, X, #X, "" __VA_ARGS__)
+#define DCHECK_LT(Y, X, ...) __DCHK(lt, <, Y, #Y, X, #X, "" __VA_ARGS__)
+#define DCHECK_GE(Y, X, ...) __DCHK(ge, >=, Y, #Y, X, #X, "" __VA_ARGS__)
+#define DCHECK_GT(Y, X, ...) __DCHK(gt, >, Y, #Y, X, #X, "" __VA_ARGS__)
#define DCHECK_NOTNULL(X, ...) \
__DCHK(ne, !=, NULL, "NULL", X, #X, "" __VA_ARGS__)
@@ -48,8 +49,8 @@ COSMOPOLITAN_C_START_
#define __CHK(SUFFIX, OP, WANT, WANTSTR, GOT, GOTSTR, ...) \
do { \
- autotype(WANT) Want = (WANT); \
autotype(GOT) Got = (GOT); \
+ autotype(WANT) Want = (WANT); \
if (!(Want OP Got)) { \
if (!NoDebug()) { \
__check_fail(#SUFFIX, #OP, (uint64_t)Want, (WANTSTR), (uint64_t)Got, \
@@ -64,9 +65,11 @@ COSMOPOLITAN_C_START_
#ifdef NDEBUG
#define __DCHK(SUFFIX, OP, WANT, WANTSTR, GOT, ...) \
do { \
- autotype(WANT) Want = (WANT); \
autotype(GOT) Got = (GOT); \
- if (!(Want OP Got)) unreachable; \
+ autotype(WANT) Want = (WANT); \
+ if (!(Want OP Got)) { \
+ unreachable; \
+ } \
} while (0)
#else
#define __DCHK(SUFFIX, OP, WANT, WANTSTR, GOT, GOTSTR, ...) \
diff --git a/libc/log/checkaligned.c b/libc/log/checkaligned.c
index 2222f734a..6f2fdb776 100644
--- a/libc/log/checkaligned.c
+++ b/libc/log/checkaligned.c
@@ -17,14 +17,19 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/stdio/stdio.h"
+STATIC_YOINK("ntoa");
+STATIC_YOINK("stoa");
+
void __check_fail_aligned(unsigned bytes, uint64_t ptr) {
- if (!IsTiny()) memsummary(stderr);
- fprintf(stderr, "%s%d%s%#p\n", "error: pointer not ", bytes,
- "-byte aligned: ", ptr);
+ fflush(stderr);
+ if (!IsTiny()) memsummary(fileno(stderr));
+ (dprintf)(fileno(stderr), "%s%d%s%#p\n", "error: pointer not ", bytes,
+ "-byte aligned: ", ptr);
die();
}
diff --git a/libc/log/checkfail.c b/libc/log/checkfail.c
index ccc880af9..c0fcbdb12 100644
--- a/libc/log/checkfail.c
+++ b/libc/log/checkfail.c
@@ -25,6 +25,7 @@
#include "libc/log/check.h"
#include "libc/log/internal.h"
#include "libc/log/log.h"
+#include "libc/runtime/memtrack.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
@@ -51,7 +52,7 @@ relegated void __check_fail(const char *suffix, const char *opstr,
"\tCHECK_%s(%s, %s);\n"
"\t\t → %#lx (%s)\n"
"\t\t%s %#lx (%s)\n",
- sufbuf, wantstr, gotstr, got, gotstr, opstr, want, wantstr);
+ sufbuf, wantstr, gotstr, want, wantstr, opstr, got, gotstr);
if (!isempty(fmt)) {
fputc('\t', stderr);
@@ -70,7 +71,8 @@ relegated void __check_fail(const char *suffix, const char *opstr,
if (!IsTiny() && lasterr == ENOMEM) {
fprintf(stderr, "\n");
- showmappings(stderr);
+ fflush(stderr);
+ PrintMemoryIntervals(fileno(stderr), &_mmi);
}
fflush(stderr);
diff --git a/libc/log/gdbexec.c b/libc/log/gdbexec.c
index deb9f3269..c238779c1 100644
--- a/libc/log/gdbexec.c
+++ b/libc/log/gdbexec.c
@@ -22,6 +22,7 @@
#include "libc/calls/hefty/spawn.h"
#include "libc/fmt/fmt.h"
#include "libc/log/gdb.h"
+#include "libc/log/log.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/symbols.h"
@@ -34,9 +35,7 @@ int(gdbexec)(const char *cmd) {
intptr_t continuetoaddr;
const char *se, *elf, *gdb;
char pidstr[11], breakcmd[40];
- if (!(gdb = commandv(firstnonnull(getenv("GDB"), "gdb")))) {
- return -1;
- }
+ if (!(gdb = GetGdbPath())) return -1;
snprintf(pidstr, sizeof(pidstr), "%u", getpid());
if ((elf = finddebugbinary())) {
se = "-se";
diff --git a/libc/runtime/mappings.c b/libc/log/gdbpath.c
similarity index 95%
rename from libc/runtime/mappings.c
rename to libc/log/gdbpath.c
index 92eeaf559..ab3b6afda 100644
--- a/libc/runtime/mappings.c
+++ b/libc/log/gdbpath.c
@@ -17,6 +17,8 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/runtime/mappings.h"
+#include "libc/log/log.h"
-struct Mappings _mm;
+const char *GetGdbPath(void) {
+ return commandvenv("GDB", "gdb");
+}
diff --git a/libc/log/internal.h b/libc/log/internal.h
index cc9ee6a7f..3b26f1807 100644
--- a/libc/log/internal.h
+++ b/libc/log/internal.h
@@ -1,17 +1,17 @@
#ifndef COSMOPOLITAN_LIBC_LOG_INTERNAL_H_
#define COSMOPOLITAN_LIBC_LOG_INTERNAL_H_
-#include "libc/log/log.h"
-#include "libc/calls/struct/sigaction.h"
#include "libc/calls/calls.h"
+#include "libc/calls/struct/sigaction.h"
+#include "libc/log/log.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
-#define RED (cancolor() ? "\x1b[30;101m" : "")
+#define RED (cancolor() ? "\x1b[30;101m" : "")
#define UNBOLD (cancolor() ? "\x1b[22m" : "")
-#define RED2 (cancolor() ? "\x1b[91;1m" : "")
-#define BLUE1 (cancolor() ? "\x1b[94;49m" : "")
-#define BLUE2 (cancolor() ? "\x1b[34m" : "")
-#define RESET (cancolor() ? "\x1b[0m" : "")
+#define RED2 (cancolor() ? "\x1b[91;1m" : "")
+#define BLUE1 (cancolor() ? "\x1b[94;49m" : "")
+#define BLUE2 (cancolor() ? "\x1b[34m" : "")
+#define RESET (cancolor() ? "\x1b[0m" : "")
#define SUBTLE (cancolor() ? "\x1b[35m" : "")
enum NtExceptionHandlerActions;
@@ -20,7 +20,6 @@ struct NtExceptionPointers;
extern int kCrashSigs[8];
extern struct sigaction g_oldcrashacts[8];
extern const char kCrashSigNames[8][5] aligned(1);
-extern const char kGodHatesFlags[12][2] aligned(1);
extern const char kGregNames[17][4] aligned(1);
extern const char kGregOrder[17] aligned(1);
diff --git a/libc/log/log.h b/libc/log/log.h
index b32aa6a2a..1ea9e1bce 100644
--- a/libc/log/log.h
+++ b/libc/log/log.h
@@ -17,8 +17,8 @@
#ifndef LOGGABLELEVEL
#ifndef NDEBUG
#define LOGGABLELEVEL kLogDebug
-#elif IsTiny()
-#define LOGGABLELEVEL kLogFatal
+/* #elif IsTiny() */
+/* #define LOGGABLELEVEL kLogInfo */
#else
#define LOGGABLELEVEL kLogInfo
#endif
@@ -37,15 +37,16 @@ extern FILE *g_logfile;
void backtrace(FILE *) relegated; /* shows fn backtrace and args */
void perror(const char *) relegated; /* print the last system error */
void die(void) relegated noreturn; /* print backtrace and abort() */
-void meminfo(FILE *); /* shows malloc statistics &c. */
-void memsummary(FILE *); /* light version of same thing */
+void meminfo(int); /* shows malloc statistics &c. */
+void memsummary(int); /* light version of same thing */
uint16_t getttycols(uint16_t);
int getttysize(int, struct winsize *) paramsnonnull();
bool cancolor(void) nothrow nocallback;
bool isterminalinarticulate(void) nosideeffect;
char *commandvenv(const char *, const char *) nodiscard;
+const char *GetAddr2linePath(void);
+const char *GetGdbPath(void);
-void showmappings(FILE *);
void showcrashreports(void);
void callexitontermination(struct sigset *);
bool32 IsDebuggerPresent(bool);
diff --git a/libc/log/logfile.c b/libc/log/logfile.initabi.c
similarity index 97%
rename from libc/log/logfile.c
rename to libc/log/logfile.initabi.c
index 09ee4b1ac..0107b73d1 100644
--- a/libc/log/logfile.c
+++ b/libc/log/logfile.initabi.c
@@ -17,8 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/bits/bits.h"
-#include "libc/log/log.h"
+#include "libc/bits/initializer.h"
#include "libc/stdio/stdio.h"
FILE *g_logfile;
diff --git a/libc/log/meminfo.c b/libc/log/meminfo.c
index 5edd3d0f4..d3e0a0c95 100644
--- a/libc/log/meminfo.c
+++ b/libc/log/meminfo.c
@@ -17,26 +17,25 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/calls.h"
#include "libc/fmt/fmt.h"
#include "libc/log/log.h"
#include "libc/mem/mem.h"
-#include "libc/stdio/stdio.h"
STATIC_YOINK("ntoa");
STATIC_YOINK("stoa");
static void onmemchunk(void *start, void *end, size_t used_bytes, void *arg) {
- FILE *f = arg;
- (fprintf)(f, "%p - %p : %08zx / %08lx\n", start, end, used_bytes,
+ (dprintf)(*(int *)arg, "%p - %p : %08zx / %08lx\n", start, end, used_bytes,
(intptr_t)end - (intptr_t)start);
}
/**
* Prints memory mappings.
*/
-void meminfo(FILE *f) {
- memsummary(f);
- (fprintf)(f, "%*s %*s %*s %*s\n", POINTER_XDIGITS, "start",
+void meminfo(int fd) {
+ memsummary(fd);
+ (dprintf)(fd, "%*s %*s %*s %*s\n", POINTER_XDIGITS, "start",
POINTER_XDIGITS, "end", 8, "used", 8, "size");
- malloc_inspect_all(onmemchunk, f);
+ malloc_inspect_all(onmemchunk, &fd);
}
diff --git a/libc/log/memsummary.c b/libc/log/memsummary.c
index e2dc61e84..410d219bf 100644
--- a/libc/log/memsummary.c
+++ b/libc/log/memsummary.c
@@ -17,15 +17,16 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/calls.h"
#include "libc/log/log.h"
#include "libc/mem/mem.h"
-#include "libc/stdio/stdio.h"
STATIC_YOINK("ntoa");
-void memsummary(FILE *f) {
- struct mallinfo mi = mallinfo();
- (fprintf)(f,
+void memsummary(int fd) {
+ struct mallinfo mi;
+ mi = mallinfo();
+ (dprintf)(fd,
"arena\t\t%,-12zu\t# space allocated from system\n"
"ordblks\t\t%,-12zu\t# number of free chunks\n"
"hblkhd\t\t%,-12zu\t# space in mmapped regions\n"
diff --git a/libc/log/oncrash.c b/libc/log/oncrash.c
index 1d09c8986..817295857 100644
--- a/libc/log/oncrash.c
+++ b/libc/log/oncrash.c
@@ -17,6 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/calls.h"
#include "libc/calls/sigbits.h"
#include "libc/calls/struct/utsname.h"
#include "libc/calls/ucontext.h"
@@ -27,10 +28,12 @@
#include "libc/log/log.h"
#include "libc/macros.h"
#include "libc/runtime/internal.h"
+#include "libc/runtime/memtrack.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/auxv.h"
+#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sig.h"
STATIC_YOINK("ftoa");
@@ -44,20 +47,23 @@ STATIC_YOINK("stoa");
struct siginfo;
-aligned(1) const char kGregOrder[17] = {13, 11, 8, 14, 12, 9, 10, 15, 16,
- 0, 1, 2, 3, 4, 5, 6, 7};
+aligned(1) const char kGregOrder[17] = {
+ 13, 11, 8, 14, 12, 9, 10, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7,
+};
+
aligned(1) const char kGregNames[17][4] = {
"R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15", "RDI",
- "RSI", "RBP", "RBX", "RDX", "RAX", "RCX", "RSP", "RIP"};
-aligned(1) const char kGodHatesFlags[12][2] = {
- "CF", "VF", "PF", "RF", "AF", "KF", "ZF", "SF", "TF", "IF", "DF", "OF"};
+ "RSI", "RBP", "RBX", "RDX", "RAX", "RCX", "RSP", "RIP",
+};
+
+aligned(1) const char kGodHatesFlags[12] = "CVPRAKZSTIDO";
aligned(1) const char kCrashSigNames[8][5] = {"QUIT", "FPE", "ILL", "SEGV",
"TRAP", "ABRT", "BUS"};
int kCrashSigs[8];
struct sigaction g_oldcrashacts[8];
-relegated static const char *tinystrsignal(int sig) {
+relegated static const char *TinyStrSignal(int sig) {
size_t i;
for (i = 0; i < ARRAYLEN(kCrashSigs); ++i) {
if (kCrashSigs[i] && sig == kCrashSigs[i]) {
@@ -67,10 +73,10 @@ relegated static const char *tinystrsignal(int sig) {
return "???";
}
-relegated static void showfunctioncalls(FILE *f, ucontext_t *ctx) {
- fputc('\n', f);
+relegated static void ShowFunctionCalls(FILE *f, ucontext_t *ctx) {
struct StackFrame *bp;
struct StackFrame goodframe;
+ fputc('\n', f);
if (ctx && ctx->uc_mcontext.rip && ctx->uc_mcontext.rbp) {
goodframe.next = (struct StackFrame *)ctx->uc_mcontext.rbp;
goodframe.addr = ctx->uc_mcontext.rip;
@@ -79,20 +85,20 @@ relegated static void showfunctioncalls(FILE *f, ucontext_t *ctx) {
}
}
-relegated static void describecpuflags(FILE *f, unsigned efl) {
+relegated static void DescribeCpuFlags(FILE *f, unsigned efl) {
size_t i;
for (i = 0; i < ARRAYLEN(kGodHatesFlags); ++i) {
if (efl & 1) {
fputc(' ', f);
- fputc(kGodHatesFlags[i][0], f);
- fputc(kGodHatesFlags[i][1], f);
+ fputc(kGodHatesFlags[i], f);
+ fputc('F', f);
}
efl >>= 1;
}
(fprintf)(f, " %s%d\n", "IOPL", efl & 3);
}
-relegated static void showgeneralregisters(FILE *f, ucontext_t *ctx) {
+relegated static void ShowGeneralRegisters(FILE *f, ucontext_t *ctx) {
size_t i, j, k;
long double st;
fputc('\n', f);
@@ -112,10 +118,10 @@ relegated static void showgeneralregisters(FILE *f, ucontext_t *ctx) {
}
}
fflush(stderr);
- describecpuflags(f, ctx->uc_mcontext.gregs[REG_EFL]);
+ DescribeCpuFlags(f, ctx->uc_mcontext.gregs[REG_EFL]);
}
-relegated static void showsseregisters(FILE *f, ucontext_t *ctx) {
+relegated static void ShowSseRegisters(FILE *f, ucontext_t *ctx) {
size_t i;
fputc('\n', f);
for (i = 0; i < 8; ++i) {
@@ -126,42 +132,42 @@ relegated static void showsseregisters(FILE *f, ucontext_t *ctx) {
}
}
-relegated static void showmemorymappings(FILE *f) {
- int c;
- FILE *f2;
+relegated static void ShowMemoryMappings(int outfd) {
+ ssize_t rc;
+ int c, infd;
+ char buf[64];
if (!IsTiny()) {
- showmappings(f);
- if (IsLinux()) {
- if ((f2 = fopen("/proc/self/maps", "r"))) {
- while ((c = fgetc(f2)) != -1) {
- if (fputc(c, f) == -1) break;
- }
+ PrintMemoryIntervals(outfd, &_mmi);
+ if ((infd = open("/proc/self/maps", O_RDONLY)) != -1) {
+ while ((rc = read(infd, buf, sizeof(buf))) > 0) {
+ write(outfd, buf, rc);
}
- fclose(f2);
}
+ close(infd);
}
}
-relegated static void showcrashreport(int err, FILE *f, int sig,
+relegated static void ShowCrashReport(int err, FILE *f, int sig,
ucontext_t *ctx) {
struct utsname names;
(fprintf)(f, VEIL("r", "\n%serror%s: Uncaught SIG%s\n %s\n %s\n"), RED2,
- RESET, tinystrsignal(sig), getauxval(AT_EXECFN), strerror(err));
+ RESET, TinyStrSignal(sig), getauxval(AT_EXECFN), strerror(err));
if (uname(&names) != -1) {
(fprintf)(f, VEIL("r", " %s %s %s %s\n"), names.sysname, names.nodename,
names.release, names.version);
}
- showfunctioncalls(f, ctx);
+ ShowFunctionCalls(f, ctx);
if (ctx) {
- showgeneralregisters(f, ctx);
- showsseregisters(f, ctx);
+ ShowGeneralRegisters(f, ctx);
+ ShowSseRegisters(f, ctx);
}
fputc('\n', f);
- memsummary(f);
- showmemorymappings(f);
+ fflush(f);
+ memsummary(fileno(f));
+ ShowMemoryMappings(fileno(f));
}
-relegated static void restoredefaultcrashsignalhandlers(void) {
+relegated static void RestoreDefaultCrashSignalHandlers(void) {
size_t i;
sigset_t ss;
sigemptyset(&ss);
@@ -200,7 +206,7 @@ relegated void oncrash(int sig, struct siginfo *si, ucontext_t *ctx) {
} else if (isterminalinarticulate() || isrunningundermake()) {
gdbpid = -1;
} else {
- restoredefaultcrashsignalhandlers();
+ RestoreDefaultCrashSignalHandlers();
gdbpid =
attachdebugger(((sig == SIGTRAP || sig == SIGQUIT) &&
(rip >= (intptr_t)&_base && rip < (intptr_t)&_etext))
@@ -208,7 +214,7 @@ relegated void oncrash(int sig, struct siginfo *si, ucontext_t *ctx) {
: 0);
}
if (gdbpid > 0 && (sig == SIGTRAP || sig == SIGQUIT)) return;
- showcrashreport(err, stderr, sig, ctx);
+ ShowCrashReport(err, stderr, sig, ctx);
quick_exit(128 + sig);
unreachable;
}
diff --git a/libc/log/showcrashreports.c b/libc/log/showcrashreports.c
index 0a4095484..11d7f12a1 100644
--- a/libc/log/showcrashreports.c
+++ b/libc/log/showcrashreports.c
@@ -24,12 +24,16 @@
#include "libc/dce.h"
#include "libc/log/internal.h"
#include "libc/log/log.h"
+#include "libc/log/ubsan.h"
#include "libc/macros.h"
#include "libc/nt/signals.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
+STATIC_YOINK("die");
+STATIC_YOINK("__ubsan_abort");
+
extern const unsigned char kOncrashThunks[7][11];
/**
@@ -49,11 +53,9 @@ extern const unsigned char kOncrashThunks[7][11];
*
* @see callexitontermination()
*/
-optimizesize void showcrashreports(void) {
+void showcrashreports(void) {
size_t i;
struct sigaction sa;
- if (IsModeDbg()) STATIC_YOINK("__ubsan_abort");
- if (!NoDebug()) STATIC_YOINK("die");
/* : oncrashthunks.S */
kCrashSigs[0] = SIGQUIT; /* ctrl+\ aka ctrl+break */
kCrashSigs[1] = SIGFPE; /* 1 / 0 */
diff --git a/libc/log/vflogf.c b/libc/log/vflogf.c
index 2cf49a740..2136f0641 100644
--- a/libc/log/vflogf.c
+++ b/libc/log/vflogf.c
@@ -25,6 +25,7 @@
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
+#include "libc/log/internal.h"
#include "libc/log/log.h"
#include "libc/math.h"
#include "libc/nexgen32e/nexgen32e.h"
@@ -75,6 +76,7 @@ void vflogf_onfail(FILE *f) {
void(vflogf)(unsigned level, const char *file, int line, FILE *f,
const char *fmt, va_list va) {
static struct timespec ts;
+ bool flush;
struct tm tm;
long double t2;
const char *prog;
@@ -85,7 +87,7 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f,
if (fileno(f) == -1) return;
t2 = nowl();
secs = t2;
- nsec = mod1000000000int64(t2 * 1e9L);
+ nsec = rem1000000000int64(t2 * 1e9L);
if (secs > ts.tv_sec) {
localtime_r(&secs, &tm);
strftime(timebuf, sizeof(timebuf), "%Y%m%dT%H%M%S", &tm);
@@ -93,23 +95,28 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f,
timebufp = timebuf;
zonebufp = zonebuf;
dots = nsec;
+ flush = true;
} else {
timebufp = "---------------";
zonebufp = "---";
dots = nsec - ts.tv_nsec;
+ flush = true;
}
ts.tv_sec = secs;
ts.tv_nsec = nsec;
prog = basename(program_invocation_name);
if (fprintf(f, "%c%s%s%06ld:%s:%d:%.*s:%d] ", loglevel2char(level), timebufp,
- zonebufp, mod1000000int64(div1000int64(dots)), file, line,
+ zonebufp, rem1000000int64(div1000int64(dots)), file, line,
strchrnul(prog, '.') - prog, prog, getpid()) <= 0) {
vflogf_onfail(f);
}
(vfprintf)(f, fmt, va);
va_end(va);
fputc('\n', f);
+ if (flush) fflush(f);
if (level == kLogFatal) {
+ startfatal(file, line);
+ fprintf(stderr, "fatal error see logfile\n");
die();
unreachable;
}
diff --git a/libc/macho.h b/libc/macho.h
index 3ce17af31..8c2cc8922 100644
--- a/libc/macho.h
+++ b/libc/macho.h
@@ -1,124 +1,125 @@
#ifndef COSMOPOLITAN_LIBC_MACHO_H_
#define COSMOPOLITAN_LIBC_MACHO_H_
+#ifndef __STRICT_ANSI__
-#define MAC_OBJECT 0x1
-#define MAC_EXECUTE 0x2
-#define MAC_FVMLIB 0x3
-#define MAC_CORE 0x4
-#define MAC_PRELOAD 0x5
-#define MAC_DYLIB 0x6
+#define MAC_OBJECT 0x1
+#define MAC_EXECUTE 0x2
+#define MAC_FVMLIB 0x3
+#define MAC_CORE 0x4
+#define MAC_PRELOAD 0x5
+#define MAC_DYLIB 0x6
#define MAC_DYLINKER 0x7
-#define MAC_BUNDLE 0x8
+#define MAC_BUNDLE 0x8
-#define MAC_CPU_NEXGEN32E 0x1000007
+#define MAC_CPU_NEXGEN32E 0x1000007
#define MAC_CPU_NEXGEN32E_ALL 3
-#define MAC_THREAD_NEXGEN32E 4
+#define MAC_THREAD_NEXGEN32E 4
#define MAC_THREAD_NEXGEN32E_256BIT 17
-#define MAC_NOUNDEFS 0x1
-#define MAC_INCRLINK 0x2
-#define MAC_DYLDLINK 0x4
-#define MAC_BINDATLOAD 0x8
-#define MAC_PREBOUND 0x10
-#define MAC_SPLIT_SEGS 0x20
-#define MAC_LAZY_INIT 0x40
-#define MAC_TWOLEVEL 0x80
-#define MAC_FORCE_FLAT 0x100
-#define MAC_NOMULTIDEFS 0x200
-#define MAC_NOFIXPREBINDING 0x400
-#define MAC_PREBINDABLE 0x800
-#define MAC_ALLMODSBOUND 0x1000
+#define MAC_NOUNDEFS 0x1
+#define MAC_INCRLINK 0x2
+#define MAC_DYLDLINK 0x4
+#define MAC_BINDATLOAD 0x8
+#define MAC_PREBOUND 0x10
+#define MAC_SPLIT_SEGS 0x20
+#define MAC_LAZY_INIT 0x40
+#define MAC_TWOLEVEL 0x80
+#define MAC_FORCE_FLAT 0x100
+#define MAC_NOMULTIDEFS 0x200
+#define MAC_NOFIXPREBINDING 0x400
+#define MAC_PREBINDABLE 0x800
+#define MAC_ALLMODSBOUND 0x1000
#define MAC_SUBSECTIONS_VIA_SYMBOLS 0x2000
-#define MAC_CANONICAL 0x4000
-#define MAC_ROOT_SAFE 0x40000
-#define MAC_SETUID_SAFE 0x80000
-#define MAC_PIE 0x200000
-#define MAC_HAS_TLV_DESCRIPTORS 0x800000
-#define MAC_NO_HEAP_EXECUTION 0x1000000
+#define MAC_CANONICAL 0x4000
+#define MAC_ROOT_SAFE 0x40000
+#define MAC_SETUID_SAFE 0x80000
+#define MAC_PIE 0x200000
+#define MAC_HAS_TLV_DESCRIPTORS 0x800000
+#define MAC_NO_HEAP_EXECUTION 0x1000000
-#define MAC_SG_HIGHVM 0x1
-#define MAC_SG_FVMLIB 0x2
+#define MAC_SG_HIGHVM 0x1
+#define MAC_SG_FVMLIB 0x2
#define MAC_SG_NORELOC 0x4
-#define MAC_S_REGULAR 0x0
-#define MAC_S_ZEROFILL 0x1
-#define MAC_S_CSTRING_LITERALS 0x2
-#define MAC_S_4BYTE_LITERALS 0x3
-#define MAC_S_8BYTE_LITERALS 0x4
-#define MAC_S_LITERAL_POINTERS 0x5
+#define MAC_S_REGULAR 0x0
+#define MAC_S_ZEROFILL 0x1
+#define MAC_S_CSTRING_LITERALS 0x2
+#define MAC_S_4BYTE_LITERALS 0x3
+#define MAC_S_8BYTE_LITERALS 0x4
+#define MAC_S_LITERAL_POINTERS 0x5
#define MAC_S_NON_LAZY_SYMBOL_POINTERS 0x6
-#define MAC_S_LAZY_SYMBOL_POINTERS 0x7
-#define MAC_S_SYMBOL_STUBS 0x8
-#define MAC_S_MOD_INIT_FUNC_POINTERS 0x9
-#define MAC_S_MOD_TERM_FUNC_POINTERS 0xa
-#define MAC_S_COALESCED 0xb
-#define MAC_S_GB_ZEROFILL 0xc
-#define MAC_S_INTERPOSING 0xd
-#define MAC_S_16BYTE_LITERALS 0xe
+#define MAC_S_LAZY_SYMBOL_POINTERS 0x7
+#define MAC_S_SYMBOL_STUBS 0x8
+#define MAC_S_MOD_INIT_FUNC_POINTERS 0x9
+#define MAC_S_MOD_TERM_FUNC_POINTERS 0xa
+#define MAC_S_COALESCED 0xb
+#define MAC_S_GB_ZEROFILL 0xc
+#define MAC_S_INTERPOSING 0xd
+#define MAC_S_16BYTE_LITERALS 0xe
-#define MAC_SECTION_ATTRIBUTES_USR 0xff000000
-#define MAC_S_ATTR_PURE_INSTRUCTIONS 0x80000000
-#define MAC_S_ATTR_NO_TOC 0x40000000
-#define MAC_S_ATTR_STRIP_STATIC_SYMS 0x20000000
-#define MAC_S_ATTR_NO_DEAD_STRIP 0x10000000
-#define MAC_S_ATTR_LIVE_SUPPORT 0x08000000
+#define MAC_SECTION_ATTRIBUTES_USR 0xff000000
+#define MAC_S_ATTR_PURE_INSTRUCTIONS 0x80000000
+#define MAC_S_ATTR_NO_TOC 0x40000000
+#define MAC_S_ATTR_STRIP_STATIC_SYMS 0x20000000
+#define MAC_S_ATTR_NO_DEAD_STRIP 0x10000000
+#define MAC_S_ATTR_LIVE_SUPPORT 0x08000000
#define MAC_S_ATTR_SELF_MODIFYING_CODE 0x04000000
-#define MAC_S_ATTR_DEBUG 0x02000000
-#define MAC_SECTION_ATTRIBUTES_SYS 0x00ffff00
-#define MAC_S_ATTR_SOME_INSTRUCTIONS 0x00000400
-#define MAC_S_ATTR_EXT_RELOC 0x00000200
-#define MAC_S_ATTR_LOC_RELOC 0x00000100
+#define MAC_S_ATTR_DEBUG 0x02000000
+#define MAC_SECTION_ATTRIBUTES_SYS 0x00ffff00
+#define MAC_S_ATTR_SOME_INSTRUCTIONS 0x00000400
+#define MAC_S_ATTR_EXT_RELOC 0x00000200
+#define MAC_S_ATTR_LOC_RELOC 0x00000100
-#define MAC_LC_REQ_DYLD 0x80000000
-#define MAC_LC_SEGMENT 0x1
-#define MAC_LC_SYMTAB 0x2
-#define MAC_LC_SYMSEG 0x3
-#define MAC_LC_THREAD 0x4
-#define MAC_LC_UNIXTHREAD 0x5
-#define MAC_LC_LOADFVMLIB 0x6
-#define MAC_LC_IDFVMLIB 0x7
-#define MAC_LC_IDENT 0x8
-#define MAC_LC_FVMFILE 0x9
-#define MAC_LC_PREPAGE 0xa
-#define MAC_LC_DYSYMTAB 0xb
-#define MAC_LC_LOAD_DYLIB 0xc
-#define MAC_LC_ID_DYLIB 0xd
-#define MAC_LC_LOAD_DYLINKER 0xe
-#define MAC_LC_ID_DYLINKER 0xf
-#define MAC_LC_PREBOUND_DYLIB 0x10
-#define MAC_LC_ROUTINES 0x11
-#define MAC_LC_SUB_FRAMEWORK 0x12
-#define MAC_LC_SUB_UMBRELLA 0x13
-#define MAC_LC_SUB_CLIENT 0x14
-#define MAC_LC_SUB_LIBRARY 0x15
-#define MAC_LC_TWOLEVEL_HINTS 0x16
-#define MAC_LC_PREBIND_CKSUM 0x17
-#define MAC_LC_LOAD_WEAK_DYLIB (0x18 | MAC_LC_REQ_DYLD)
-#define MAC_LC_SEGMENT_64 0x19
-#define MAC_LC_ROUTINES_64 0x1a
-#define MAC_LC_UUID 0x1b
-#define MAC_LC_CODE_SIGNATURE 0x1d
-#define MAC_LC_SEGMENT_SPLIT_INFO 0x1e
-#define MAC_LC_LAZY_LOAD_DYLIB 0x20
-#define MAC_LC_ENCRYPTION_INFO 0x21
-#define MAC_LC_DYLD_INFO 0x22
-#define MAC_LC_VERSION_MIN_MACOSX 0x24
+#define MAC_LC_REQ_DYLD 0x80000000
+#define MAC_LC_SEGMENT 0x1
+#define MAC_LC_SYMTAB 0x2
+#define MAC_LC_SYMSEG 0x3
+#define MAC_LC_THREAD 0x4
+#define MAC_LC_UNIXTHREAD 0x5
+#define MAC_LC_LOADFVMLIB 0x6
+#define MAC_LC_IDFVMLIB 0x7
+#define MAC_LC_IDENT 0x8
+#define MAC_LC_FVMFILE 0x9
+#define MAC_LC_PREPAGE 0xa
+#define MAC_LC_DYSYMTAB 0xb
+#define MAC_LC_LOAD_DYLIB 0xc
+#define MAC_LC_ID_DYLIB 0xd
+#define MAC_LC_LOAD_DYLINKER 0xe
+#define MAC_LC_ID_DYLINKER 0xf
+#define MAC_LC_PREBOUND_DYLIB 0x10
+#define MAC_LC_ROUTINES 0x11
+#define MAC_LC_SUB_FRAMEWORK 0x12
+#define MAC_LC_SUB_UMBRELLA 0x13
+#define MAC_LC_SUB_CLIENT 0x14
+#define MAC_LC_SUB_LIBRARY 0x15
+#define MAC_LC_TWOLEVEL_HINTS 0x16
+#define MAC_LC_PREBIND_CKSUM 0x17
+#define MAC_LC_LOAD_WEAK_DYLIB (0x18 | MAC_LC_REQ_DYLD)
+#define MAC_LC_SEGMENT_64 0x19
+#define MAC_LC_ROUTINES_64 0x1a
+#define MAC_LC_UUID 0x1b
+#define MAC_LC_CODE_SIGNATURE 0x1d
+#define MAC_LC_SEGMENT_SPLIT_INFO 0x1e
+#define MAC_LC_LAZY_LOAD_DYLIB 0x20
+#define MAC_LC_ENCRYPTION_INFO 0x21
+#define MAC_LC_DYLD_INFO 0x22
+#define MAC_LC_VERSION_MIN_MACOSX 0x24
#define MAC_LC_VERSION_MIN_IPHONEOS 0x25
-#define MAC_LC_FUNCTION_STARTS 0x26
-#define MAC_LC_DYLD_ENVIRONMENT 0x27
-#define MAC_LC_DATA_IN_CODE 0x29
-#define MAC_LC_SOURCE_VERSION 0x2a
-#define MAC_LC_RPATH (0x1c | MAC_LC_REQ_DYLD)
-#define MAC_LC_MAIN (0x28 | MAC_LC_REQ_DYLD)
+#define MAC_LC_FUNCTION_STARTS 0x26
+#define MAC_LC_DYLD_ENVIRONMENT 0x27
+#define MAC_LC_DATA_IN_CODE 0x29
+#define MAC_LC_SOURCE_VERSION 0x2a
+#define MAC_LC_RPATH (0x1c | MAC_LC_REQ_DYLD)
+#define MAC_LC_MAIN (0x28 | MAC_LC_REQ_DYLD)
-#define VM_PROT_NONE 0
-#define VM_PROT_READ 1
-#define VM_PROT_WRITE 2
-#define VM_PROT_EXECUTE 4
-#define VM_PROT_NO_CHANGE 8
-#define VM_PROT_COPY 16
-#define VM_PROT_TRUSTED 32
+#define VM_PROT_NONE 0
+#define VM_PROT_READ 1
+#define VM_PROT_WRITE 2
+#define VM_PROT_EXECUTE 4
+#define VM_PROT_NO_CHANGE 8
+#define VM_PROT_COPY 16
+#define VM_PROT_TRUSTED 32
#define VM_PROT_STRIP_READ 64
#if !(__ASSEMBLER__ + __LINKER__ + 0)
@@ -210,4 +211,5 @@ struct MachoLoadUuid {
};
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_MACHO_H_ */
diff --git a/libc/macros-cpp.inc b/libc/macros-cpp.inc
index 308414c2e..5ca2a1fb2 100644
--- a/libc/macros-cpp.inc
+++ b/libc/macros-cpp.inc
@@ -24,6 +24,39 @@
#define vzeroupper
#endif
+/ Begins definition of frameless function that calls no functions.
+.macro .leafprologue
+#if !(defined(TINY) && !defined(__PG__))
+ push %rbp
+ mov %rsp,%rbp
+#endif
+.endm
+
+/ Ends definition of frameless function that calls no functions.
+.macro .leafepilogue
+#if !(defined(TINY) && !defined(__PG__))
+ pop %rbp
+#endif
+ ret
+.endm
+
+/ Good alignment for functions where alignment actually helps.
+/ @note 16-byte
+.macro .alignfunc
+#ifndef __OPTIMIZE_SIZE__
+ .p2align 4
+#endif
+.endm
+
+/ Good alignment for loops where alignment actually helps.
+/ @note 16-byte if <10 padding otherwise 8-byte
+.macro .alignloop
+#ifndef __OPTIMIZE_SIZE__
+ .p2align 4,,10
+ .p2align 4
+#endif
+.endm
+
/ Loads Effective Address
/ Supporting security blankets
.macro plea symbol:req reg64:req reg32:req
@@ -48,7 +81,7 @@
/ Loads Effective Address
/ Supporting security blankets
.macro ezlea symbol:req reg:req
-#if __PIC__ + __PIE__ + __code_model_medium__ + __code_model_large__ + 0 > 1
+#if __pic__ + __pie__ + __code_model_medium__ + __code_model_large__ + 0 > 1
/ lea \symbol(%rip),%r\reg
mov $\symbol,%e\reg
#else
@@ -71,22 +104,6 @@
xor %ebp,%ebp
.endm
-/ Begins definition of frameless function that calls no functions.
-.macro .leafprologue
-#if !(defined(TINY) && !defined(__PG__))
- push %rbp
- mov %rsp,%rbp
-#endif
-.endm
-
-/ Ends definition of frameless function that calls no functions.
-.macro .leafepilogue
-#if !(defined(TINY) && !defined(__PG__))
- pop %rbp
-#endif
- ret
-.endm
-
/ Pulls source code file into ZIP portion of binary.
/
/ @param symbol is quoted path relative to root e.g. __FILE__
diff --git a/libc/macros.inc b/libc/macros.inc
index d6f860ea4..513e9baae 100644
--- a/libc/macros.inc
+++ b/libc/macros.inc
@@ -31,6 +31,9 @@
.macro .text.startup
.section .text.startup,"ax",@progbits
.endm
+.macro .text.exit
+ .section .text.exit,"ax",@progbits
+.endm
.macro .firstclass
.section .text.hot,"ax",@progbits
.endm
@@ -85,6 +88,14 @@
.section .rodata.cst64,"aM",@progbits,64
.align 64
.endm
+.macro .tdata
+ .section .tdata,"awT",@progbits
+ .align 4
+.endm
+.macro .tbss
+ .section .tdata,"awT",@nobits
+ .align 4
+.endm
/ Mergeable NUL-terminated UTF-8 string constant section.
/
@@ -175,11 +186,16 @@
.endif
.endm
+/ Custom emulator instruction for bottom stack frame.
+.macro bofram endfunc:req
+ .byte 0x0f,0x1f,0x45,\endfunc-. # nopl disp8(%rbp)
+.endm
+
/ Overrides LOOP instruction.
/ With its mop-Fusion Mexican equivalent.
/ Thus avoiding 3x legacy pipeline slowdown.
.macro loop label:req
- .byte 0x83,0xe9,0x01 # sub $1,%ecx
+ .byte 0x83,0xe9,0x01 # sub $1,%ecx
jnz \label
.endm
diff --git a/libc/math.h b/libc/math.h
index cbacf38c6..b70b21f2d 100644
--- a/libc/math.h
+++ b/libc/math.h
@@ -252,6 +252,10 @@ long lround(double);
long lroundf(float);
long lroundl(long double);
+int ilogbf(float);
+int ilogb(double);
+int ilogbl(long double);
+
long long llrint(double);
long long llrintf(float);
long long llrintl(long double);
@@ -295,13 +299,16 @@ int __fpclassifyl(long double);
#define fldlg2() __X87_CONST(fldlg2, M_LOG10_2)
#define fldln2() __X87_CONST(fldln2, M_LN2)
#define fldl2e() __X87_CONST(fldl2e, M_LOG2E)
-#define __X87_CONST(OP, VALUE) \
- ({ \
- long double St0##OP; \
- asm(#OP : "=t"(St0##OP)); \
- /* assume(st0##OP == VALUE); */ \
- St0##OP; \
+#ifdef __x86__
+#define __X87_CONST(OP, VALUE) \
+ ({ \
+ long double St0##OP; \
+ asm(#OP : "=t"(St0##OP)); \
+ St0##OP; \
})
+#else
+#define __X87_CONST(OP, VALUE) VALUE
+#endif
#define fnstsw() __X87_FPU_STATUS("fnstsw")
#define fstsw() __X87_FPU_STATUS("fstsw")
diff --git a/libc/runtime/balloc.c b/libc/mem/balloc.c
similarity index 68%
rename from libc/runtime/balloc.c
rename to libc/mem/balloc.c
index c5078c42a..aeb711eae 100644
--- a/libc/runtime/balloc.c
+++ b/libc/mem/balloc.c
@@ -17,16 +17,10 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/assert.h"
-#include "libc/bits/bits.h"
-#include "libc/bits/popcnt.h"
-#include "libc/bits/safemacros.h"
-#include "libc/calls/calls.h"
+#include "libc/mem/mem.h"
#include "libc/runtime/buffer.h"
-#include "libc/runtime/mappings.h"
-#include "libc/runtime/runtime.h"
-#include "libc/str/str.h"
-#include "libc/sysv/consts/prot.h"
+
+/* TODO(jart): Delete */
#define kGuard PAGESIZE
#define kGrain FRAMESIZE
@@ -46,46 +40,5 @@
* @see ralloc()
*/
void *balloc(struct GuardedBuffer *b, unsigned a, size_t n) {
- int rc;
- char *p;
- size_t mi, need;
- struct AddrSize az;
- struct MemoryCoord mc;
-
- assert(a >= 1);
- assert(a <= kGuard);
- assert(kGuard < kGrain);
- assert(popcnt(a) == 1);
- assert(popcnt(kGuard) == 1);
- assert(popcnt(kGrain) == 1);
- assert(n < 0x800000000000ul - kGrain - kGuard);
-
- if (n) {
- az.addr = 0;
- az.size = 0;
- n = roundup(n, a);
- need = roundup(n + kGuard, kGrain);
- if (b->p) {
- mc = ADDRSIZE_TO_COORD(b->p, 1);
- mi = findmapping(mc.y);
- if (mi && ISOVERLAPPING(mc, _mm.p[mi - 1])) {
- az = COORD_TO_ADDRSIZE(_mm.p[mi - 1]);
- if (az.size < need) {
- rc = munmap(az.addr, az.size);
- assert(rc != -1);
- az.addr = NULL;
- az.size = 0;
- }
- }
- }
- if (!az.size) {
- if ((p = mapanon(need)) == MAP_FAILED) abort();
- if (mprotect(p + need - kGuard, kGuard, PROT_NONE) == -1) abort();
- } else {
- p = az.addr;
- }
- b->p = p + need - kGuard - n;
- }
-
- return b->p;
+ return (b->p = memalign(a, n));
}
diff --git a/libc/runtime/bfree.c b/libc/mem/bfree.c
similarity index 84%
rename from libc/runtime/bfree.c
rename to libc/mem/bfree.c
index 9913494d4..1e8192537 100644
--- a/libc/runtime/bfree.c
+++ b/libc/mem/bfree.c
@@ -19,22 +19,11 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
+#include "libc/mem/mem.h"
#include "libc/runtime/buffer.h"
-#include "libc/runtime/mappings.h"
+
+/* TODO(jart): Delete */
void bfree(struct GuardedBuffer *b) {
- int mr;
- size_t mi;
- struct AddrSize az;
- struct MemoryCoord mc;
- if (b->p) {
- mc = ADDRSIZE_TO_COORD(b->p, 1);
- mi = findmapping(mc.y);
- assert(mi > 0);
- assert(ISOVERLAPPING(mc, _mm.p[mi - 1]));
- az = COORD_TO_ADDRSIZE(_mm.p[mi - 1]);
- mr = munmap(az.addr, az.size);
- assert(mr != -1);
- b->p = NULL;
- }
+ free(b->p);
}
diff --git a/libc/mem/mem.mk b/libc/mem/mem.mk
index 00285dd1f..edcfc1b0a 100644
--- a/libc/mem/mem.mk
+++ b/libc/mem/mem.mk
@@ -25,6 +25,7 @@ LIBC_MEM_A_CHECKS = \
$(LIBC_MEM_A_HDRS:%=o/$(MODE)/%.ok)
LIBC_MEM_A_DIRECTDEPS = \
+ LIBC_CONV \
LIBC_STR \
LIBC_FMT \
LIBC_RAND \
diff --git a/libc/mem/reallocarray.c b/libc/mem/reallocarray.c
index f72569da1..97f235a83 100644
--- a/libc/mem/reallocarray.c
+++ b/libc/mem/reallocarray.c
@@ -18,7 +18,6 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/conv/conv.h"
-#include "libc/conv/sizemultiply.h"
#include "libc/mem/mem.h"
/**
@@ -29,7 +28,5 @@
* @return new address or NULL w/ errno and ptr is NOT free()'d
*/
void *reallocarray(void *ptr, size_t nmemb, size_t itemsize) {
- size_t newsize;
- sizemultiply(&newsize, nmemb, itemsize); /* punts error */
- return realloc(ptr, newsize);
+ return realloc(ptr, nmemb * itemsize);
}
diff --git a/libc/mem/unhexstr.c b/libc/mem/unhexstr.c
index 73de65f4d..f699ca5fc 100644
--- a/libc/mem/unhexstr.c
+++ b/libc/mem/unhexstr.c
@@ -25,5 +25,5 @@
nodiscard void *unhexstr(const char *hexdigs) {
assert(strlen(hexdigs) % 2 == 0);
return unhexbuf(memalign(__BIGGEST_ALIGNMENT__, strlen(hexdigs) / 2),
- strlen(hexdigs), hexdigs);
+ strlen(hexdigs) / 2, hexdigs);
}
diff --git a/libc/ncabi.h b/libc/ncabi.h
deleted file mode 100644
index c2b729a41..000000000
--- a/libc/ncabi.h
+++ /dev/null
@@ -1,87 +0,0 @@
-#ifndef COSMOPOLITAN_LIBC_NCABI_H_
-#define COSMOPOLITAN_LIBC_NCABI_H_
-#if !(__ASSEMBLER__ + __LINKER__ + 0)
-COSMOPOLITAN_C_START_
-
-/**
- * @fileoverview No-Clobber ABI Function Veneers.
- *
- * These macros map be specified alongside alongside the prototype of
- * any function that promises to not clobber anything except %rax and
- * flags. These macros let the compiler know about that promise. That
- * allows the compiler to generate faster, smaller code.
- */
-
-#define NCABI_PRUNABLE asm
-#define NCABI_NOPRUNE asm volatile
-
-#define NCABI_CALL_0(KIND, T, NAME) \
- ({ \
- T ax; \
- KIND("call\t" NAME \
- : "=a"(ax) \
- : /* no inputs */ \
- : "memory", "cc"); \
- ax; \
- })
-
-#define NCABI_CALL_1(KIND, T, NAME, T1, P1) \
- ({ \
- T ax; \
- KIND("call\t" NAME : "=a"(ax) : "D"(P1) : "memory", "cc"); \
- ax; \
- })
-
-#define NCABI_CALL_2(KIND, T, NAME, T1, P1, T2, P2) \
- ({ \
- T ax; \
- KIND("call\t" NAME : "=a"(ax) : "D"(P1), "S"(P2) : "memory", "cc"); \
- ax; \
- })
-
-#define NCABI_CALL_3(KIND, T, NAME, T1, P1, T2, P2, T3, P3) \
- ({ \
- T ax; \
- KIND("call\t" NAME \
- : "=a"(ax) \
- : "D"(P1), "S"(P2), "d"(P3) \
- : "memory", "cc"); \
- ax; \
- })
-
-#define NCABI_CALL_4(KIND, T, NAME, T1, P1, T2, P2, T3, P3, T4, P4) \
- ({ \
- T ax; \
- KIND("call\t" NAME \
- : "=a"(ax) \
- : "D"(P1), "S"(P2), "d"(P3), "c"(P4) \
- : "memory", "cc"); \
- ax; \
- })
-
-#define NCABI_DECLARE_0(KIND, T, ALIAS, NAME) \
- forceinline nodebuginfo T ALIAS(void) { return NCABI_CALL_0(KIND, T, NAME); }
-
-#define NCABI_DECLARE_1(KIND, T, ALIAS, NAME, T1) \
- forceinline nodebuginfo T ALIAS(T1 p1) { \
- return NCABI_CALL_1(KIND, T, NAME, T1, p1); \
- }
-
-#define NCABI_DECLARE_2(KIND, T, ALIAS, NAME, T1, T2) \
- forceinline nodebuginfo T ALIAS(T1 p1, T2 p2) { \
- return NCABI_CALL_2(KIND, T, NAME, T1, p1, T2, p2); \
- }
-
-#define NCABI_DECLARE_3(KIND, T, ALIAS, NAME, T1, T2, T3) \
- forceinline nodebuginfo T ALIAS(T1 p1, T2 p2, T3 p3) { \
- return NCABI_CALL_3(KIND, T, NAME, T1, p1, T2, p2, T3, p3); \
- }
-
-#define NCABI_DECLARE_4(KIND, T, ALIAS, NAME, T1, T2, T3, T4) \
- forceinline nodebuginfo T ALIAS(T1 p1, T2 p2, T3 p3, T4 p4) { \
- return NCABI_CALL_4(KIND, T, NAME, T1, p1, T2, p2, T3, p3, T4, p4); \
- }
-
-COSMOPOLITAN_C_END_
-#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#endif /* COSMOPOLITAN_LIBC_NCABI_H_ */
diff --git a/libc/nexgen32e/bcmp.S b/libc/nexgen32e/bcmp.S
index d7d999d58..ab8131991 100644
--- a/libc/nexgen32e/bcmp.S
+++ b/libc/nexgen32e/bcmp.S
@@ -28,6 +28,6 @@
/ @param esi second string
/ @param edx byte size
/ @return 0 if equal or nonzero
-bcmp: jmp *hook$memcmp(%rip)
+bcmp: jmp *__memcmp(%rip)
.endfn bcmp,globl
.source __FILE__
diff --git a/libc/nexgen32e/bsf.S b/libc/nexgen32e/bsf.S
deleted file mode 100644
index a0686a1d4..000000000
--- a/libc/nexgen32e/bsf.S
+++ /dev/null
@@ -1,43 +0,0 @@
-/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
-│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/macros.h"
-
-/ Finds lowest set bit in 𝑥.
-/
-/ @param edi is 32-bit unsigned 𝑥 value
-/ @return eax in range [0,32) or undefined if 𝑥 is 0
-/ @see also treasure trove of nearly identical functions
-/
-/ uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥)
-/ 0x00000000 wut 32 0 wut 32
-/ 0x00000001 0 0 1 0 31
-/ 0x80000001 0 0 1 31 0
-/ 0x80000000 31 31 32 31 0
-/ 0x00000010 4 4 5 4 27
-/ 0x08000010 4 4 5 27 4
-/ 0x08000000 27 27 28 27 4
-/ 0xffffffff 0 0 1 31 0
-/
-bsf: .leafprologue
- .profilable
- bsf %edi,%eax
- .leafepilogue
- .endfn bsf,globl
- .source __FILE__
diff --git a/libc/nexgen32e/bsf.h b/libc/nexgen32e/bsf.h
index 728949983..ea4d9e73d 100644
--- a/libc/nexgen32e/bsf.h
+++ b/libc/nexgen32e/bsf.h
@@ -3,18 +3,25 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
-unsigned bsf(unsigned) libcesque pureconst;
-unsigned bsfl(unsigned long) libcesque pureconst;
-unsigned bsfmax(uintmax_t) libcesque pureconst;
+/*
+ * BIT SCANNING 101
+ * ctz(𝑥) 31^clz(𝑥) clz(𝑥)
+ * uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥)
+ * 0x00000000 wut 32 0 wut 32
+ * 0x00000001 0 0 1 0 31
+ * 0x80000001 0 0 1 31 0
+ * 0x80000000 31 31 32 31 0
+ * 0x00000010 4 4 5 4 27
+ * 0x08000010 4 4 5 27 4
+ * 0x08000000 27 27 28 27 4
+ * 0xffffffff 0 0 1 31 0
+ */
-#define bsf(X) \
- ({ \
- unsigned Res; \
- typeof(X) Word; \
- asm("bsf\t%1,%0" : "=r,r"(Word) : "r,m"(X)); \
- Res = Word; \
- Res; \
- })
+#define bsf(u) __builtin_ctz(u)
+#define bsfl(u) __builtin_ctzl(u)
+#define bsfll(u) __builtin_ctzll(u)
+
+unsigned bsfmax(uintmax_t);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/nexgen32e/bsfl.S b/libc/nexgen32e/bsfl.S
deleted file mode 100644
index 15e495733..000000000
--- a/libc/nexgen32e/bsfl.S
+++ /dev/null
@@ -1,44 +0,0 @@
-/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
-│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/macros.h"
-
-/ Finds lowest set bit in 𝑥.
-/
-/ @param rdi is 64-bit unsigned 𝑥 value
-/ @return eax number in range [0,64) or undefined if 𝑥 is 0
-/ @see also treasure trove of nearly identical functions
-/
-/ uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥)
-/ 0x00000000 wut 32 0 wut 32
-/ 0x00000001 0 0 1 0 31
-/ 0x80000001 0 0 1 31 0
-/ 0x80000000 31 31 32 31 0
-/ 0x00000010 4 4 5 4 27
-/ 0x08000010 4 4 5 27 4
-/ 0x08000000 27 27 28 27 4
-/ 0xffffffff 0 0 1 31 0
-/
-bsfl: .leafprologue
- .profilable
- bsf %rdi,%rax
- .leafepilogue
- .endfn bsfl,globl
- .alias bsfl,bsfll
- .source __FILE__
diff --git a/libc/nexgen32e/bsr.S b/libc/nexgen32e/bsr.S
deleted file mode 100644
index 963fd37f2..000000000
--- a/libc/nexgen32e/bsr.S
+++ /dev/null
@@ -1,43 +0,0 @@
-/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
-│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/macros.h"
-
-/ Returns binary logarithm of integer 𝑥.
-/
-/ @param edi is 32-bit unsigned 𝑥 value
-/ @return eax number in range [0,32) or undefined if 𝑥 is 0
-/ @see also treasure trove of nearly identical functions
-/
-/ uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥)
-/ 0x00000000 wut 32 0 wut 32
-/ 0x00000001 0 0 1 0 31
-/ 0x80000001 0 0 1 31 0
-/ 0x80000000 31 31 32 31 0
-/ 0x00000010 4 4 5 4 27
-/ 0x08000010 4 4 5 27 4
-/ 0x08000000 27 27 28 27 4
-/ 0xffffffff 0 0 1 31 0
-/
-bsr: .leafprologue
- .profilable
- bsr %edi,%eax
- .leafepilogue
- .endfn bsr,globl
- .source __FILE__
diff --git a/libc/nexgen32e/bsr.h b/libc/nexgen32e/bsr.h
index cefe02cf3..4d77806f9 100644
--- a/libc/nexgen32e/bsr.h
+++ b/libc/nexgen32e/bsr.h
@@ -3,26 +3,25 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
-unsigned bsr(unsigned) libcesque pureconst;
-unsigned bsrl(unsigned long) libcesque pureconst;
-unsigned bsrmax(uintmax_t) libcesque pureconst;
+/*
+ * BIT SCANNING 101
+ * ctz(𝑥) 31^clz(𝑥) clz(𝑥)
+ * uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥)
+ * 0x00000000 wut 32 0 wut 32
+ * 0x00000001 0 0 1 0 31
+ * 0x80000001 0 0 1 31 0
+ * 0x80000000 31 31 32 31 0
+ * 0x00000010 4 4 5 4 27
+ * 0x08000010 4 4 5 27 4
+ * 0x08000000 27 27 28 27 4
+ * 0xffffffff 0 0 1 31 0
+ */
-#ifdef __GNUC__
-#define bsr(X) \
- ({ \
- unsigned Word; \
- asm("bsrl\t%1,%0" : "=r,r"(Word) : "r,m"(X)); \
- Word; \
- })
-#define bsrl(X) \
- ({ \
- unsigned Res; \
- unsigned long Word; \
- asm("bsrq\t%1,%0" : "=r,r"(Word) : "r,m"(X)); \
- Res = Word; \
- Res; \
- })
-#endif
+#define bsr(u) ((sizeof(unsigned) * 8 - 1) ^ __builtin_clz(u))
+#define bsrl(u) ((sizeof(unsigned long) * 8 - 1) ^ __builtin_clzl(u))
+#define bsrll(u) ((sizeof(unsigned long long) * 8 - 1) ^ __builtin_clzll(u))
+
+unsigned bsrmax(uintmax_t);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/nexgen32e/bsrl.S b/libc/nexgen32e/bsrl.S
deleted file mode 100644
index 9640d022f..000000000
--- a/libc/nexgen32e/bsrl.S
+++ /dev/null
@@ -1,44 +0,0 @@
-/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
-│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/macros.h"
-
-/ Returns binary logarithm of integer 𝑥.
-/
-/ @param rdi is 32-bit unsigned 𝑥 value
-/ @return eax number in range [0,64) or undefined if 𝑥 is 0
-/ @see also treasure trove of nearly identical functions
-/
-/ uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥)
-/ 0x00000000 wut 32 0 wut 32
-/ 0x00000001 0 0 1 0 31
-/ 0x80000001 0 0 1 31 0
-/ 0x80000000 31 31 32 31 0
-/ 0x00000010 4 4 5 4 27
-/ 0x08000010 4 4 5 27 4
-/ 0x08000000 27 27 28 27 4
-/ 0xffffffff 0 0 1 31 0
-/
-bsrl: .leafprologue
- .profilable
- bsr %rdi,%rax
- .leafepilogue
- .endfn bsrl,globl
- .alias bsrl,bsrll
- .source __FILE__
diff --git a/libc/nexgen32e/bzero.S b/libc/nexgen32e/bzero.S
index 0267db6a7..0ada5c6ee 100644
--- a/libc/nexgen32e/bzero.S
+++ b/libc/nexgen32e/bzero.S
@@ -30,6 +30,6 @@
/ @see memset(), explicit_bzero()
bzero: mov %rsi,%rdx
xor %esi,%esi
- jmp _memset
+ jmp MemSet
.endfn bzero,globl
.source __FILE__
diff --git a/libc/nexgen32e/cmpsl.S b/libc/nexgen32e/cmpsl.S
index 59c7906e9..4cfb70ac9 100644
--- a/libc/nexgen32e/cmpsl.S
+++ b/libc/nexgen32e/cmpsl.S
@@ -29,7 +29,10 @@ cmpsl: .leafprologue
.profilable
xor %eax,%eax
cmpsl
- cmovl .Lone(%rip),%eax
+/ mov (%rdi),%edi
+/ mov (%rsi),%esi
+/ cmp %edi,%esi
+ setl %al
cmovg .Lneg1(%rip),%eax
.leafepilogue
.endfn cmpsl,globl
diff --git a/libc/nexgen32e/crc32.h b/libc/nexgen32e/crc32.h
new file mode 100644
index 000000000..f01610d45
--- /dev/null
+++ b/libc/nexgen32e/crc32.h
@@ -0,0 +1,17 @@
+#ifndef COSMOPOLITAN_LIBC_NEXGEN32E_CRC32_H_
+#define COSMOPOLITAN_LIBC_NEXGEN32E_CRC32_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+extern const uint32_t kCrc32Tab[256];
+
+void crc32init(uint32_t[hasatleast 256], uint32_t);
+uint32_t crc32_z(uint32_t, const void *, size_t);
+extern uint32_t (*const crc32c)(uint32_t, const void *, size_t) paramsnonnull();
+uint32_t crc32c$pure(uint32_t, const void *, size_t) strlenesque hidden;
+uint32_t crc32c$sse42(uint32_t, const void *, size_t) strlenesque hidden;
+uint32_t crc32$pclmul(uint32_t, const void *, size_t) hidden;
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_NEXGEN32E_CRC32_H_ */
diff --git a/libc/nexgen32e/crc32c-pure.c b/libc/nexgen32e/crc32c-pure.c
index 69f041f6b..5bbd9addb 100644
--- a/libc/nexgen32e/crc32c-pure.c
+++ b/libc/nexgen32e/crc32c-pure.c
@@ -17,7 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/str/internal.h"
+#include "libc/nexgen32e/crc32.h"
/**
* Computes Castagnoli CRC-32 on old computers.
diff --git a/libc/nexgen32e/crc32c-sse42.c b/libc/nexgen32e/crc32c-sse42.c
index de2f0cd40..f724f5412 100644
--- a/libc/nexgen32e/crc32c-sse42.c
+++ b/libc/nexgen32e/crc32c-sse42.c
@@ -23,11 +23,11 @@
* Hashes data with hardware acceleration at 10GBps.
* @note needs Nehalem+ c. 2008 or Bulldozer+ c. 2011
*/
-uint32_t crc32c$sse42(uint32_t init, const void *data, size_t size) {
+uint32_t crc32c$sse42(uint32_t init, const void *data, size_t n) {
const unsigned char *p = (const unsigned char *)data;
- const unsigned char *pe = (const unsigned char *)data + size;
+ const unsigned char *pe = (const unsigned char *)data + n;
uint32_t h = init ^ 0xffffffff;
- if (size >= 16 + 8) {
+ if (n >= 16 + 8) {
while ((uintptr_t)p & 7) asm("crc32b\t%1,%0" : "+r"(h) : "rm"(*p++));
uint64_t hl = h;
while (p < pe - 16ul) {
diff --git a/libc/nexgen32e/crc32c.S b/libc/nexgen32e/crc32c.S
index 57bebc553..147ca2460 100644
--- a/libc/nexgen32e/crc32c.S
+++ b/libc/nexgen32e/crc32c.S
@@ -36,11 +36,9 @@ crc32c: .quad 0
.init.start 300,_init_crc32c
ezlea crc32c$pure,ax
-#if !IsTiny()
ezlea crc32c$sse42,cx
testb X86_HAVE(SSE4_2)+kCpuids(%rip)
cmovnz %rcx,%rax
-#endif
stosq
.init.end 300,_init_crc32c
.source __FILE__
diff --git a/libc/nexgen32e/crc32init.S b/libc/nexgen32e/crc32init.S
index af2d79d2a..9af6fb9d0 100644
--- a/libc/nexgen32e/crc32init.S
+++ b/libc/nexgen32e/crc32init.S
@@ -50,7 +50,7 @@ crc32init:
dec %eax
mov %eax,(%rsp)
jnz 0b
- movups (%rsp),%xmm1
+ movdqu (%rsp),%xmm1
1: mov $8,%ecx
movdqa %xmm1,%xmm3
2: movdqa %xmm3,%xmm4
diff --git a/libc/nexgen32e/crc32z.c b/libc/nexgen32e/crc32z.c
index 58a2f9a08..94280ab20 100644
--- a/libc/nexgen32e/crc32z.c
+++ b/libc/nexgen32e/crc32z.c
@@ -18,9 +18,8 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/safemacros.h"
+#include "libc/nexgen32e/crc32.h"
#include "libc/nexgen32e/x86feature.h"
-#include "libc/str/internal.h"
-#include "libc/str/str.h"
/**
* Computes Phil Katz CRC-32 used by zip/zlib/gzip/etc.
diff --git a/libc/nexgen32e/div10.greg.S b/libc/nexgen32e/div10.greg.S
deleted file mode 100644
index 5690a2a32..000000000
--- a/libc/nexgen32e/div10.greg.S
+++ /dev/null
@@ -1,119 +0,0 @@
-/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
-│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/macros.h"
-
-/ Performs 128-bit div+mod by 10 without using div or mod.
-/
-/ If we didn't have this one-off function, our palandprintf()
-/ implementation would cause nearly everything to need a soft
-/ math library. It also somehow goes faster than 64-bit IDIV.
-/
-/ @param rdi:rsi is the number
-/ @param rdx points to where remainder goes
-/ @return rax:rdx is result of division
-/ @see “Division by Invariant Integers using Multiplication”
-/ @see llog10() and div10int64() is a tiny bit faster
-div10: .leafprologue
- .profilable
- push %rbx
- mov %rdx,%r8
- test %rsi,%rsi
- je 1f
- bsr %rsi,%r10
- xor $63,%r10d
- mov $125,%r9d
- sub %r10d,%r9d
- cmp $64,%r9d
- jne 6f
- xor %eax,%eax
- xor %r11d,%r11d
- jmp 9f
-1: test %r8,%r8
- je 3f
- movabs $0xcccccccccccccccd,%rcx
- mov %rdi,%rax
- mul %rcx
- shr $3,%rdx
- add %edx,%edx
- lea (%rdx,%rdx,4),%eax
- mov %edi,%ecx
- sub %eax,%ecx
- mov %ecx,(%r8)
-3: movabs $0xcccccccccccccccd,%rcx
- mov %rdi,%rax
- mul %rcx
- mov %rdx,%rax
- shr $3,%rax
- xor %edi,%edi
- jmp 14f
-6: mov %r9d,%ecx
- neg %cl
- cmp $62,%r10d
- jb 8f
- mov %rdi,%rdx
- shl %cl,%rdx
- mov %rsi,%rax
- mov %r9d,%ecx
- shr %cl,%rax
- shrd %cl,%rsi,%rdi
- xor %r11d,%r11d
- mov %rdi,%rsi
- mov %rdx,%rdi
- jmp 9f
-8: mov %rdi,%r11
- shl %cl,%r11
- mov %rsi,%rax
- shl %cl,%rax
- mov %r9d,%ecx
- shr %cl,%rdi
- or %rax,%rdi
- shr %cl,%rsi
- xor %eax,%eax
-9: add $-125,%r10d
- xor %ecx,%ecx
- mov $9,%r9d
-10: shld $1,%rsi,%rax
- shld $1,%rdi,%rsi
- shld $1,%r11,%rdi
- mov %r11,%rdx
- add %r11,%rdx
- mov %rcx,%r11
- or %rdx,%r11
- cmp %rsi,%r9
- mov $0,%ebx
- sbb %rax,%rbx
- sar $63,%rbx
- mov %ebx,%ecx
- and $1,%ecx
- and $10,%ebx
- sub %rbx,%rsi
- sbb $0,%rax
- inc %r10d
- jne 10b
- test %r8,%r8
- je 13f
- mov %esi,(%r8)
-13: lea (%rcx,%r11,2),%rax
- shld $1,%rdx,%rdi
-14: mov %rdi,%rdx
- pop %rbx
- .leafepilogue
- .endfn div10,globl,hidden
- .source __FILE__
diff --git a/libc/nexgen32e/div1000000000int64.greg.S b/libc/nexgen32e/div1000000000int64.S
similarity index 96%
rename from libc/nexgen32e/div1000000000int64.greg.S
rename to libc/nexgen32e/div1000000000int64.S
index caaa4c497..690ab9b9c 100644
--- a/libc/nexgen32e/div1000000000int64.greg.S
+++ b/libc/nexgen32e/div1000000000int64.S
@@ -19,10 +19,10 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-/ Divides 64-bit integer by 1,000,000,000.
+/ Divides 64-bit signed integer by 1,000,000,000.
/
/ @param rdi is number to divide
-/ @return truncated numerator
+/ @return quotient
div1000000000int64:
mov $0x1a,%cl
movabs $0x112e0be826d694b3,%rdx
diff --git a/libc/nexgen32e/div1000000int64.greg.S b/libc/nexgen32e/div1000000int64.S
similarity index 96%
rename from libc/nexgen32e/div1000000int64.greg.S
rename to libc/nexgen32e/div1000000int64.S
index 2a5a4f5ff..035c8920d 100644
--- a/libc/nexgen32e/div1000000int64.greg.S
+++ b/libc/nexgen32e/div1000000int64.S
@@ -19,10 +19,10 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-/ Divides 64-bit integer by 1,000,000.
+/ Divides 64-bit signed integer by 1,000,000.
/
/ @param rdi is number to divide
-/ @return truncated numerator
+/ @return quotient
div1000000int64:
mov $0x12,%cl
movabs $0x431bde82d7b634db,%rdx
diff --git a/libc/nexgen32e/div10000int64.S b/libc/nexgen32e/div10000int64.S
new file mode 100644
index 000000000..b163fb0af
--- /dev/null
+++ b/libc/nexgen32e/div10000int64.S
@@ -0,0 +1,31 @@
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/macros.h"
+
+/ Divides 64-bit signed integer by 10,000.
+/
+/ @param rdi is number to divide
+/ @return truncated quotient
+div10000int64:
+ mov $11,%cl
+ movabs $0x346dc5d63886594b,%rdx
+ jmp tinydivsi
+ .endfn div10000int64,globl
+ .source __FILE__
diff --git a/libc/nexgen32e/div1000int64.greg.S b/libc/nexgen32e/div1000int64.S
similarity index 97%
rename from libc/nexgen32e/div1000int64.greg.S
rename to libc/nexgen32e/div1000int64.S
index bab8f9bc5..9c310b929 100644
--- a/libc/nexgen32e/div1000int64.greg.S
+++ b/libc/nexgen32e/div1000int64.S
@@ -19,10 +19,10 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-/ Divides 64-bit integer by 1,000.
+/ Divides 64-bit signed integer by 1,000.
/
/ @param rdi is number to divide
-/ @return truncated numerator
+/ @return quotient
div1000int64:
mov $0x7,%cl
movabs $0x20c49ba5e353f7cf,%rdx
diff --git a/libc/nexgen32e/div100int64.S b/libc/nexgen32e/div100int64.S
new file mode 100644
index 000000000..a625ef0a6
--- /dev/null
+++ b/libc/nexgen32e/div100int64.S
@@ -0,0 +1,36 @@
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/macros.h"
+
+/ Divides 64-bit signed integer by 100.
+/
+/ @param rdi is number to divide
+/ @return rax has quotient
+div100int64:
+ mov %rdi,%rax
+ movabs $-6640827866535438581,%rdx
+ imul %rdx
+ lea (%rdx,%rdi),%rax
+ sar $63,%rdi
+ sar $6,%rax
+ sub %rdi,%rax
+ ret
+ .endfn div100int64,globl
+ .source __FILE__
diff --git a/libc/nexgen32e/div10int64.greg.S b/libc/nexgen32e/div10int64.S
similarity index 96%
rename from libc/nexgen32e/div10int64.greg.S
rename to libc/nexgen32e/div10int64.S
index 86b6e5fb6..5aff32770 100644
--- a/libc/nexgen32e/div10int64.greg.S
+++ b/libc/nexgen32e/div10int64.S
@@ -19,12 +19,12 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-/ Divides 64-bit integer by 10.
+/ Divides 64-bit signed integer by 10.
/
/ @param rdi is number to divide
-/ @return truncated numerator
+/ @return quotient
div10int64:
- mov $0x2,%cl
+ mov $2,%cl
movabs $0x6666666666666667,%rdx
jmp tinydivsi
.endfn div10int64,globl
diff --git a/libc/nexgen32e/doc/memrchr.c b/libc/nexgen32e/doc/memrchr.c
index b74cb2a9f..12ab77569 100644
--- a/libc/nexgen32e/doc/memrchr.c
+++ b/libc/nexgen32e/doc/memrchr.c
@@ -17,8 +17,6 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/dce.h"
-#include "libc/str/str.h"
#define N 32
typedef uint8_t uint8_v _Vector_size(N);
diff --git a/libc/nexgen32e/explicit_bzero.S b/libc/nexgen32e/explicit_bzero.S
index 744c57d8c..5ee8e5bf9 100644
--- a/libc/nexgen32e/explicit_bzero.S
+++ b/libc/nexgen32e/explicit_bzero.S
@@ -39,12 +39,12 @@ explicit_bzero:
xor %r9,%r9
xor %r10,%r10
xor %r11,%r11
- xorps %xmm0,%xmm0
- xorps %xmm1,%xmm1
- xorps %xmm2,%xmm2
- xorps %xmm3,%xmm3
- xorps %xmm4,%xmm4
- xorps %xmm5,%xmm5
+ pxor %xmm0,%xmm0
+ pxor %xmm1,%xmm1
+ pxor %xmm2,%xmm2
+ pxor %xmm3,%xmm3
+ pxor %xmm4,%xmm4
+ pxor %xmm5,%xmm5
.leafepilogue
.endfn explicit_bzero,globl
.source __FILE__
diff --git a/libc/nexgen32e/ffs.S b/libc/nexgen32e/ffs.S
index 39be2372c..375aea08e 100644
--- a/libc/nexgen32e/ffs.S
+++ b/libc/nexgen32e/ffs.S
@@ -41,7 +41,7 @@ ffs: .leafprologue
bsf %edi,%eax
or $-1,%edx
cmovz %edx,%eax
- inc %eax
+ add $1,%eax
.leafepilogue
.endfn ffs,globl
.source __FILE__
diff --git a/libc/nexgen32e/ffsl.S b/libc/nexgen32e/ffsl.S
index 44327c062..dac385dd9 100644
--- a/libc/nexgen32e/ffsl.S
+++ b/libc/nexgen32e/ffsl.S
@@ -41,7 +41,7 @@ ffsl: .leafprologue
bsf %rdi,%rax
or $-1,%edx
cmovz %edx,%eax
- inc %eax
+ add $1,%eax
.leafepilogue
.endfn ffsl,globl
.alias ffsl,ffsll
diff --git a/libc/nexgen32e/gc.S b/libc/nexgen32e/gc.S
index 0460da7d4..215a81218 100644
--- a/libc/nexgen32e/gc.S
+++ b/libc/nexgen32e/gc.S
@@ -33,9 +33,10 @@
/ @param rax,rdx,xmm0,xmm1,st0,st1 is return value
/ @see test/libc/runtime/gc_test.c
/
-__gc: decq __garbage(%rip)
- mov __garbage(%rip),%r8
- mov __garbage+16(%rip),%r9
+CollectGarbage:
+ decq g_garbage(%rip)
+ mov g_garbage(%rip),%r8
+ mov g_garbage+16(%rip),%r9
js 9f
shl $5,%r8
lea (%r9,%r8),%r8
@@ -45,28 +46,25 @@ __gc: decq __garbage(%rip)
/
push %rbp
mov %rsp,%rbp
- sub $0x40,%rsp
+ sub $0x20,%rsp
push %rax
push %rdx
- fstpl -0x40(%rbp)
- fstpl -0x30(%rbp)
movaps %xmm0,-0x20(%rbp)
movaps %xmm1,-0x10(%rbp)
call *%r9
movaps -0x10(%rbp),%xmm1
movaps -0x20(%rbp),%xmm0
- fldl -0x30(%rbp)
- fldl -0x40(%rbp)
pop %rdx
pop %rax
leave
ret
9: call abort
- .endfn __gc,globl,hidden
+ .endfn CollectGarbage,globl,hidden
+ .source __FILE__
.bss
.align 8
-__garbage:
+g_garbage:
.quad 0 # garbage.i
.quad 0 # garbage.n
.quad 0 # garbage.p
@@ -76,16 +74,10 @@ __garbage:
.quad 0 # garbage.p[𝑖].arg
.quad 0 # garbage.p[𝑖].ret
.endr
- .endobj __garbage,globl,hidden
+ .endobj g_garbage,globl,hidden
.previous
.init.start 100,_init_garbage
- push %rdi
- ezlea __garbage+8,di
- pushpop INITIAL_CAPACITY,%rax
- stosq
- lea 8(%rdi),%rax
- stosq
- pop %rdi
+ movb $INITIAL_CAPACITY,g_garbage+8(%rip)
+ movl $g_garbage+24,g_garbage+16(%rip)
.init.end 100,_init_garbage
- .source __FILE__
diff --git a/libc/nexgen32e/gc.h b/libc/nexgen32e/gc.h
index 2183bc6a4..b3b3b4435 100644
--- a/libc/nexgen32e/gc.h
+++ b/libc/nexgen32e/gc.h
@@ -15,9 +15,9 @@ struct Garbages {
} * p;
};
-hidden extern struct Garbages __garbage;
+hidden extern struct Garbages g_garbage;
-int64_t __gc(void) hidden;
+int64_t CollectGarbage(void) hidden;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/nexgen32e/gclongjmp.S b/libc/nexgen32e/gclongjmp.S
index 65977768a..eb96fe143 100644
--- a/libc/nexgen32e/gclongjmp.S
+++ b/libc/nexgen32e/gclongjmp.S
@@ -33,8 +33,8 @@
gclongjmp:
.leafprologue
.profilable
- .weak __garbage
- lea __garbage(%rip),%r12
+ .weak g_garbage
+ lea g_garbage(%rip),%r12
test %r12,%r12
jnz .L.unwind.destructors
0: jmp longjmp
diff --git a/libc/nexgen32e/kcp437.S b/libc/nexgen32e/kcp437.S
index ef845cbbe..f76a26626 100644
--- a/libc/nexgen32e/kcp437.S
+++ b/libc/nexgen32e/kcp437.S
@@ -20,6 +20,7 @@
#include "libc/macros.h"
.rodata
.align 16
+.source __FILE__
/ ibm cp437 unicode table w/ string literal safety
/
@@ -70,7 +71,7 @@ kCp437:
.short 0x0040,0x0041,0x0042,0x0043,0x0044,0x0045,0x0046,0x0047 #40:@ABCDEFG
.short 0x0048,0x0049,0x004a,0x004b,0x004c,0x004d,0x004e,0x004f #48:HIJKLMNO
.short 0x0050,0x0051,0x0052,0x0053,0x0054,0x0055,0x0056,0x0057 #50:PQRSTUVW
-.short 0x0058,0x0059,0x005a,0x005b,0x2572,0x005d,0x005e,0x005f #58:XYZ[╲]^_
+.short 0x0058,0x0059,0x005a,0x005b,0x005c,0x005d,0x005e,0x005f #58:XYZ[\]^_
.short 0x0060,0x0061,0x0062,0x0063,0x0064,0x0065,0x0066,0x0067 #60:`abcdefg
.short 0x0068,0x0069,0x006a,0x006b,0x006c,0x006d,0x006e,0x006f #68:hijklmno
.short 0x0070,0x0071,0x0072,0x0073,0x0074,0x0075,0x0076,0x0077 #70:pqrstuvw
@@ -93,4 +94,3 @@ kCp437:
.short 0x00b0,0x2219,0x00b7,0x221a,0x207f,0x00b2,0x25a0,0x03bb #f8:°∙·√ⁿ²■λ
.endobj kCp437,globl
.previous
- .source __FILE__
diff --git a/libc/nexgen32e/kcpuids.S b/libc/nexgen32e/kcpuids.S
index fd01c757c..f3a2cebfe 100644
--- a/libc/nexgen32e/kcpuids.S
+++ b/libc/nexgen32e/kcpuids.S
@@ -65,8 +65,8 @@ kCpuids:.long 0,0,0,0 # EAX=0 (Basic Processor Info)
2: pop %rax
test %eax,%eax # EAX = stacklist->pop()
jz 3f # EAX ≠ 0 (EOL sentinel)
- cmp KCPUIDS(0H,EAX)(%r8),%eax # EAX ≤ CPUID.0 max leaf
- jle 1b # CPUID too new to probe
+ cmp KCPUIDS(0H,EAX)(%r8),%al # EAX ≤ CPUID.0 max leaf
+ jbe 1b # CPUID too new to probe
add $4*4,%rdi
jmp 2b
3: nop
diff --git a/libc/nexgen32e/kcpuids.h b/libc/nexgen32e/kcpuids.h
index 7328d3d04..f104f06fc 100644
--- a/libc/nexgen32e/kcpuids.h
+++ b/libc/nexgen32e/kcpuids.h
@@ -8,7 +8,7 @@
#define KCPUIDS_80000001H 4
#define KCPUIDS_80000007H 5
#define KCPUIDS_16H 6
-#define _KCPUIDS_LEN 7
+#define KCPUIDS_LEN 7
#define KCPUIDS_6H -1 /* TBD: Thermal and Power Management */
#define KCPUIDS_DH -1 /* TBD: Extended state features */
#define KCPUIDS_80000008H -1 /* TBD: AMD Miscellaneous */
@@ -36,7 +36,7 @@ COSMOPOLITAN_C_START_
* @note Protected with PIRO
* @see X86_HAVE()
*/
-extern const unsigned kCpuids[_KCPUIDS_LEN][4];
+extern const unsigned kCpuids[KCPUIDS_LEN][4];
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/nexgen32e/kstarttsc.S b/libc/nexgen32e/kstarttsc.S
index b3de12460..9f57da661 100644
--- a/libc/nexgen32e/kstarttsc.S
+++ b/libc/nexgen32e/kstarttsc.S
@@ -34,4 +34,5 @@ kStartTsc:
xchg %edx,%eax
stosl
.init.end 200,_init_kStartTsc
+
.source __FILE__
diff --git a/libc/nexgen32e/memcmp-avx2.S b/libc/nexgen32e/memcmp-avx2.S
index 1387d2f67..91e1940d3 100644
--- a/libc/nexgen32e/memcmp-avx2.S
+++ b/libc/nexgen32e/memcmp-avx2.S
@@ -28,6 +28,7 @@
/ @note AVX2 requires Haswell (2014+) or Excavator (2015+)
/ @see libc/nexgen32e/memcmp.S (for benchmarks)
/ @asyncsignalsafe
+ .align 16
memcmp$avx2:
.leafprologue
.profilable
@@ -58,7 +59,7 @@ memcmp$avx2:
jz 5b
jmp 8f
7: xor %eax,%eax
-8: vxorps %ymm0,%ymm0,%ymm0
+8: vpxor %ymm0,%ymm0,%ymm0
.leafepilogue
.endfn memcmp$avx2,globl,hidden
.source __FILE__
diff --git a/libc/nexgen32e/memcmp-hook.S b/libc/nexgen32e/memcmp-hook.S
index d80635733..b3a2fc580 100644
--- a/libc/nexgen32e/memcmp-hook.S
+++ b/libc/nexgen32e/memcmp-hook.S
@@ -29,9 +29,9 @@
/ @return unsigned char subtraction at stop index
/ @asyncsignalsafe
.initbss 300,_init_memcmp
-hook$memcmp:
+__memcmp:
.quad 0
- .endobj hook$memcmp,globl,hidden
+ .endobj __memcmp,globl,hidden
.previous
.init.start 300,_init_memcmp
diff --git a/libc/nexgen32e/memcmp-sse2.S b/libc/nexgen32e/memcmp-sse2.S
index 0494e64ff..452aec851 100644
--- a/libc/nexgen32e/memcmp-sse2.S
+++ b/libc/nexgen32e/memcmp-sse2.S
@@ -44,7 +44,7 @@ memcmp$sse2:
movdqu (%rsi,%rcx),%xmm1
pcmpeqb %xmm1,%xmm0
pmovmskb %xmm0,%eax
- subl $0xffff,%eax
+ sub $0xffff,%eax
jz 1b
bsf %eax,%eax
add %rax,%rcx
diff --git a/libc/nexgen32e/memcmp.S b/libc/nexgen32e/memcmp.S
index 62de69384..1a9b18e93 100644
--- a/libc/nexgen32e/memcmp.S
+++ b/libc/nexgen32e/memcmp.S
@@ -26,7 +26,7 @@
/ @param edx byte size
/ @return unsigned char subtraction at stop index
/ @asyncsignalsafe
-memcmp: jmp *hook$memcmp(%rip)
+memcmp: jmp *__memcmp(%rip)
.endfn memcmp,globl
.source __FILE__
diff --git a/libc/nexgen32e/memcpy.S b/libc/nexgen32e/memcpy.S
index dd9ae4c2a..ec435c192 100644
--- a/libc/nexgen32e/memcpy.S
+++ b/libc/nexgen32e/memcpy.S
@@ -46,17 +46,17 @@
.source __FILE__
memcpy: mov %rdi,%rax
/ 𝑠𝑙𝑖𝑑𝑒
+ .endfn memcpy,globl
/ Copies memory w/ minimal impact ABI.
/
/ @param rdi is dest
/ @param rsi is src
/ @param rdx is number of bytes
-/ @clob flags,xmm3
+/ @clob flags,xmm3,xmm4
/ @mode long
.align 16
-_memcpy:.leafprologue
- .profilable
+MemCpy: .leafprologue
push %rcx
mov $.Lmemcpytab.ro.size,%ecx
cmp %rcx,%rdx
@@ -65,27 +65,29 @@ _memcpy:.leafprologue
.Lanchorpoint:
.L32r: cmp $1024,%rdx
jae .Lerms
-.L32: mov $32,%rcx
+.L32: vmovdqu -32(%rsi,%rdx),%ymm4
+ mov $32,%rcx
0: add $32,%rcx
vmovdqu -64(%rsi,%rcx),%ymm3
vmovdqu %ymm3,-64(%rdi,%rcx)
cmp %rcx,%rdx
ja 0b
- vmovdqu -32(%rsi,%rdx),%ymm3
- vmovdqu %ymm3,-32(%rdi,%rdx)
- vxorps %ymm3,%ymm3,%ymm3
+ vmovdqu %ymm4,-32(%rdi,%rdx)
+ vpxor %ymm4,%ymm4,%ymm4
+ vpxor %ymm3,%ymm3,%ymm3
jmp .L0
.L16r: cmp $1024,%rdx
jae .Lerms
-.L16: mov $16,%rcx
+.L16: movdqu -16(%rsi,%rdx),%xmm4
+ mov $16,%rcx
0: add $16,%rcx
movdqu -32(%rsi,%rcx),%xmm3
movdqu %xmm3,-32(%rdi,%rcx)
cmp %rcx,%rdx
ja 0b
- movdqu -16(%rsi,%rdx),%xmm3
- movdqu %xmm3,-16(%rdi,%rdx)
- xorps %xmm3,%xmm3
+ movdqu %xmm4,-16(%rdi,%rdx)
+ pxor %xmm4,%xmm4
+ pxor %xmm3,%xmm3
jmp .L0
.L8: push %rbx
mov (%rsi),%rcx
@@ -134,10 +136,9 @@ _memcpy:.leafprologue
sfence
movdqu -16(%rsi,%rdx),%xmm3
movdqu %xmm3,-16(%rdi,%rdx)
- xorps %xmm3,%xmm3
+ pxor %xmm3,%xmm3
jmp .L0
- .endfn _memcpy,globl,hidden
- .endfn memcpy,globl
+ .endfn MemCpy,globl,hidden
.initro 300,_init_memcpy
memcpytab.ro:
diff --git a/libc/nexgen32e/memjmpinit.S b/libc/nexgen32e/memjmpinit.S
index 0e3d57ddf..352d86daf 100644
--- a/libc/nexgen32e/memjmpinit.S
+++ b/libc/nexgen32e/memjmpinit.S
@@ -29,6 +29,7 @@
/ @param rdx is address of indirect branch
/ @param ecx is size of jump table
memjmpinit:
+ .leafprologue
setnz %r8b
shl %r8b
0: xor %eax,%eax
@@ -44,6 +45,6 @@ memjmpinit:
add %rdx,%rax
stosq
lodsq
- ret
+ .leafepilogue
.endfn memjmpinit,globl,hidden
.source __FILE__
diff --git a/libc/nexgen32e/memmove.S b/libc/nexgen32e/memmove.S
index f34a037ef..3efe08d36 100644
--- a/libc/nexgen32e/memmove.S
+++ b/libc/nexgen32e/memmove.S
@@ -28,10 +28,12 @@
/ @param rdx is number of bytes
/ @return original rdi copied to rax
/ @asyncsignalsafe
-memmove:mov %rdi,%rax
+memmove:
+ mov %rdi,%rax
/ 𝑠𝑙𝑖𝑑𝑒
+ .endfn MemMove,globl,hidden
-_memmove:
+MemMove:
.leafprologue
.profilable
push %rcx
@@ -49,6 +51,5 @@ _memmove:
pop %rdi
pop %rcx
.leafepilogue
- .endfn _memmove,globl,hidden
.endfn memmove,globl
.source __FILE__
diff --git a/libc/nexgen32e/mempcpy.S b/libc/nexgen32e/mempcpy.S
index 9f286e547..20e4d5b80 100644
--- a/libc/nexgen32e/mempcpy.S
+++ b/libc/nexgen32e/mempcpy.S
@@ -18,6 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
+.source __FILE__
/ Copies memory.
/
@@ -27,7 +28,7 @@
/ @param rsi is src
/ @param rdx is number of bytes
/ @return original rdi + rdx copied to rax
-mempcpy:lea (%rdi,%rdx),%rax
- jmp _memcpy
+mempcpy:
+ lea (%rdi,%rdx),%rax
+ jmp MemCpy
.endfn mempcpy,globl
- .source __FILE__
diff --git a/libc/nexgen32e/memset.S b/libc/nexgen32e/memset.S
index 22e31f10d..a04b53925 100644
--- a/libc/nexgen32e/memset.S
+++ b/libc/nexgen32e/memset.S
@@ -36,6 +36,7 @@
/ @asyncsignalsafe
memset: mov %rdi,%rax
/ fallthrough
+ .endfn memset,globl
/ Sets memory w/ minimal-impact ABI.
/
@@ -44,7 +45,7 @@ memset: mov %rdi,%rax
/ @param edx is the number of bytes to set
/ @clob flags,xmm3
/ @mode long
-_memset:.leafprologue
+MemSet: .leafprologue
.profilable
push %rbx
push %rcx
@@ -64,7 +65,7 @@ _memset:.leafprologue
cmp %rcx,%rdx
ja 1b
vmovdqu %ymm3,-32(%rdi,%rdx)
- vxorps %ymm3,%ymm3,%ymm3
+ vpxor %ymm3,%ymm3,%ymm3
jmp .L0
.L16r: cmp $1024,%rdx
jae .Lerms
@@ -75,7 +76,7 @@ _memset:.leafprologue
cmp %rcx,%rdx
ja 1b
movdqu %xmm3,-16(%rdi,%rdx)
- xorps %xmm3,%xmm3
+ pxor %xmm3,%xmm3
.L0: pop %rcx
pop %rbx
.leafepilogue
@@ -103,8 +104,7 @@ _memset:.leafprologue
pop %rdi
pop %rax
jmp .L0
- .endfn _memset,globl,hidden
- .endfn memset,globl
+ .endfn MemSet,globl,hidden
.initro 300,_init_memset
memsettab.ro:
diff --git a/libc/nexgen32e/nexgen32e.h b/libc/nexgen32e/nexgen32e.h
index 187ae06e7..10619500a 100644
--- a/libc/nexgen32e/nexgen32e.h
+++ b/libc/nexgen32e/nexgen32e.h
@@ -8,14 +8,18 @@ void insertionsort(size_t n, int32_t[n]);
void *doublebytes(size_t, void *);
int64_t div10int64(int64_t) libcesque pureconst;
+int64_t div100int64(int64_t) libcesque pureconst;
int64_t div1000int64(int64_t) libcesque pureconst;
+int64_t div10000int64(int64_t) libcesque pureconst;
int64_t div1000000int64(int64_t) libcesque pureconst;
int64_t div1000000000int64(int64_t) libcesque pureconst;
-int64_t mod10int64(int64_t) libcesque pureconst;
-int64_t mod1000int64(int64_t) libcesque pureconst;
-int64_t mod1000000int64(int64_t) libcesque pureconst;
-int64_t mod1000000000int64(int64_t) libcesque pureconst;
+int64_t rem10int64(int64_t) libcesque pureconst;
+int64_t rem100int64(int64_t) libcesque pureconst;
+int64_t rem1000int64(int64_t) libcesque pureconst;
+int64_t rem10000int64(int64_t) libcesque pureconst;
+int64_t rem1000000int64(int64_t) libcesque pureconst;
+int64_t rem1000000000int64(int64_t) libcesque pureconst;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/nexgen32e/nontemporal.h b/libc/nexgen32e/nontemporal.h
deleted file mode 100644
index 33d863e67..000000000
--- a/libc/nexgen32e/nontemporal.h
+++ /dev/null
@@ -1,66 +0,0 @@
-#ifndef COSMOPOLITAN_LIBC_NEXGEN32E_NONTEMPORAL_H_
-#define COSMOPOLITAN_LIBC_NEXGEN32E_NONTEMPORAL_H_
-#include "libc/bits/emmintrin.h"
-#include "libc/nexgen32e/x86feature.h"
-#if !(__ASSEMBLER__ + __LINKER__ + 0)
-COSMOPOLITAN_C_START_
-
-#define sfence() asm volatile("sfence" ::: "memory")
-#define lfence() asm volatile("lfence" ::: "memory")
-
-/**
- * Stores memory asynchronously, e.g.
- *
- * for (i = 0; i < n; ++i)
- * nontemporal_store(m[i], x);
- * }
- * sfence();
- *
- * @param MEM is an aligned xmm vector pointer
- * @param REG is an xmm vector
- * @return REG
- */
-#define nontemporal_store(MEM, REG) \
- _Generic((REG), __m128i \
- : __movntdq, __m128 \
- : __movntps, __m128d \
- : __movntpd)(MEM, REG)
-
-/**
- * Loads memory asynchronously, e.g.
- *
- * x1 = nontemporal_load(m16[0]);
- * x2 = nontemporal_load(m16[1]);
- * x3 = nontemporal_load(m16[2]);
- * x4 = nontemporal_load(m16[3]);
- * lfence();
- *
- * @param REG is an xmm vector
- * @param MEM is an aligned xmm vector pointer
- * @return REG
- */
-#define nontemporal_load(REG, MEM) __movntdqa(MEM)
-
-#define __DECLARE_MOVNT(OS, TS) \
- forceinline __m128##TS __movnt##OS(__m128##TS *mem, __m128##TS reg) { \
- asm("movnt" #OS "\t%1,%0" : "=m"(*mem) : "x"(reg)); \
- return reg; \
- }
-
-__DECLARE_MOVNT(ps, )
-__DECLARE_MOVNT(dq, i)
-__DECLARE_MOVNT(pd, d)
-
-forceinline __m128i __movntdqa(const __m128i *mem) {
- __m128i reg;
- if (X86_HAVE(SSE4_1)) {
- asm("movntdqa\t%1,%0" : "=x"(reg) : "m"(*mem));
- } else {
- asm("movdqa\t%1,%0" : "=x"(reg) : "m"(*mem));
- }
- return reg;
-}
-
-COSMOPOLITAN_C_END_
-#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#endif /* COSMOPOLITAN_LIBC_NEXGEN32E_NONTEMPORAL_H_ */
diff --git a/libc/nexgen32e/mod1000000000int64.greg.S b/libc/nexgen32e/rem1000000000int64.S
similarity index 96%
rename from libc/nexgen32e/mod1000000000int64.greg.S
rename to libc/nexgen32e/rem1000000000int64.S
index 41f0db8e3..114959c86 100644
--- a/libc/nexgen32e/mod1000000000int64.greg.S
+++ b/libc/nexgen32e/rem1000000000int64.S
@@ -22,8 +22,8 @@
/ Returns 𝑥 % 1,000,000,000.
/
/ @param rdi int64 𝑥
-/ @return rax
-mod1000000000int64:
+/ @return rax has remainder
+rem1000000000int64:
movabs $0x112e0be826d694b3,%rdx
mov %rdi,%rax
imul %rdx
@@ -36,5 +36,5 @@ mod1000000000int64:
sub %rax,%rdi
mov %rdi,%rax
ret
- .endfn mod1000000000int64,globl
+ .endfn rem1000000000int64,globl
.source __FILE__
diff --git a/libc/nexgen32e/mod1000000int64.greg.S b/libc/nexgen32e/rem1000000int64.S
similarity index 94%
rename from libc/nexgen32e/mod1000000int64.greg.S
rename to libc/nexgen32e/rem1000000int64.S
index c8d59d1bd..c3530bbaa 100644
--- a/libc/nexgen32e/mod1000000int64.greg.S
+++ b/libc/nexgen32e/rem1000000int64.S
@@ -19,7 +19,11 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-mod1000000int64:
+/ Returns 𝑥 % 1,000,000.
+/
+/ @param rdi int64 𝑥
+/ @return rax has remainder
+rem1000000int64:
movabs $0x431bde82d7b634db,%rdx
mov %rdi,%rax
imul %rdx
@@ -32,5 +36,5 @@ mod1000000int64:
sub %rax,%rdi
mov %rdi,%rax
ret
- .endfn mod1000000int64,globl
+ .endfn rem1000000int64,globl
.source __FILE__
diff --git a/libc/nexgen32e/rem10000int64.S b/libc/nexgen32e/rem10000int64.S
new file mode 100644
index 000000000..ad5415bf8
--- /dev/null
+++ b/libc/nexgen32e/rem10000int64.S
@@ -0,0 +1,40 @@
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/macros.h"
+
+/ Returns 𝑥 % 10,000.
+/
+/ @param rdi int64 𝑥
+/ @return rax has remainder
+rem10000int64:
+ mov %rdi,%rax
+ movabsq $0x346dc5d63886594b,%rdx
+ imulq %rdx
+ mov %rdx,%rax
+ mov %rdi,%rdx
+ sar $11,%rax
+ sar $63,%rdx
+ sub %rdx,%rax
+ imulq $10000,%rax,%rax
+ sub %rax,%rdi
+ mov %rdi,%rax
+ ret
+ .endfn rem10000int64,globl
+ .source __FILE__
diff --git a/libc/nexgen32e/mod1000int64.greg.S b/libc/nexgen32e/rem1000int64.S
similarity index 94%
rename from libc/nexgen32e/mod1000int64.greg.S
rename to libc/nexgen32e/rem1000int64.S
index 8e16945e4..a989c0408 100644
--- a/libc/nexgen32e/mod1000int64.greg.S
+++ b/libc/nexgen32e/rem1000int64.S
@@ -19,7 +19,11 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-mod1000int64:
+/ Returns 𝑥 % 1,000.
+/
+/ @param rdi int64 𝑥
+/ @return rax has remainder
+rem1000int64:
movabs $0x20c49ba5e353f7cf,%rdx
mov %rdi,%rax
imul %rdx
@@ -32,5 +36,5 @@ mod1000int64:
sub %rax,%rdi
mov %rdi,%rax
ret
- .endfn mod1000int64,globl
+ .endfn rem1000int64,globl
.source __FILE__
diff --git a/libc/nexgen32e/rem100int64.S b/libc/nexgen32e/rem100int64.S
new file mode 100644
index 000000000..63fa53a2a
--- /dev/null
+++ b/libc/nexgen32e/rem100int64.S
@@ -0,0 +1,40 @@
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/macros.h"
+
+/ Returns 𝑥 % 100.
+/
+/ @param rdi int64 𝑥
+/ @return rax has remainder
+rem100int64:
+ mov %rdi,%rax
+ movabsq $-6640827866535438581,%rdx
+ imul %rdx
+ lea (%rdx,%rdi),%rax
+ mov %rdi,%rdx
+ sar $6,%rax
+ sar $63,%rdx
+ sub %rdx,%rax
+ imul $100,%rax,%rax
+ sub %rax,%rdi
+ mov %rdi,%rax
+ ret
+ .endfn rem100int64,globl
+ .source __FILE__
diff --git a/libc/nexgen32e/mod10int64.greg.S b/libc/nexgen32e/rem10int64.S
similarity index 95%
rename from libc/nexgen32e/mod10int64.greg.S
rename to libc/nexgen32e/rem10int64.S
index efae4668e..cd0469313 100644
--- a/libc/nexgen32e/mod10int64.greg.S
+++ b/libc/nexgen32e/rem10int64.S
@@ -19,7 +19,11 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-mod10int64:
+/ Returns 𝑥 % 10.
+/
+/ @param rdi int64 𝑥
+/ @return rax has remainder
+rem10int64:
movabs $0x6666666666666667,%rdx
mov %rdi,%rax
imul %rdx
@@ -33,5 +37,5 @@ mod10int64:
sub %rax,%rdi
mov %rdi,%rax
ret
- .endfn mod10int64,globl
+ .endfn rem10int64,globl
.source __FILE__
diff --git a/libc/nexgen32e/slowcall.h b/libc/nexgen32e/slowcall.h
new file mode 100644
index 000000000..5666f1a11
--- /dev/null
+++ b/libc/nexgen32e/slowcall.h
@@ -0,0 +1,24 @@
+#ifndef COSMOPOLITAN_LIBC_NEXGEN32E_SLOWCALL_H_
+#define COSMOPOLITAN_LIBC_NEXGEN32E_SLOWCALL_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+
+#define slowcall(fn, arg1, arg2, arg3, arg4, arg5, arg6) \
+ ({ \
+ void *ax; \
+ asm volatile("push\t%7\n\t" \
+ "push\t%6\n\t" \
+ "push\t%5\n\t" \
+ "push\t%4\n\t" \
+ "push\t%3\n\t" \
+ "push\t%2\n\t" \
+ "push\t%1\n\t" \
+ "call\tslowcall" \
+ : "=a"(ax) \
+ : "g"(fn), "g"(arg1), "g"(arg2), "g"(arg3), "g"(arg4), \
+ "g"(arg5), "g"(arg6) \
+ : "memory"); \
+ ax; \
+ })
+
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_NEXGEN32E_SLOWCALL_H_ */
diff --git a/libc/nexgen32e/strcaseconv.S b/libc/nexgen32e/strcaseconv.S
index 0fbc5803c..0b8861b88 100644
--- a/libc/nexgen32e/strcaseconv.S
+++ b/libc/nexgen32e/strcaseconv.S
@@ -30,6 +30,7 @@ strtoupper:
mov $'A-'a,%edx # adding this uppers
mov $'a|'z<<8,%ecx # uint8_t range a..z
jmp strcaseconv
+ .endfn strtoupper,globl
/ Mutates string to lowercase roman characters.
/
@@ -40,6 +41,7 @@ strtolower:
mov $'a-'A,%edx # adding this lowers
mov $'A|'Z<<8,%ecx # uint8_t range A..Z
/ 𝑠𝑙𝑖𝑑𝑒
+ .endfn strtolower,globl
/ Support code for strtolower() and strtoupper().
/
@@ -64,16 +66,29 @@ strcaseconv:
test %al,%al # is it NUL?
jz 3f
cmp %cl,%al # is it in range?
- jb 1b
+ jb 0b
cmp %ch,%al
- ja 1b
+ ja 0b
add %dl,-1(%rsi)
- jmp 1b
+ jmp 0b
.Lsse4: movd %ecx,%xmm1 # XMM1 = ['A,'Z,0,0,...]
movd %edx,%xmm2 # XMM2 = ['a-'A,'a-'A,...]
pbroadcastb %xmm2
xor %ecx,%ecx
2: movdqa (%rsi,%rcx),%xmm3
+/ ┌─0:index of the LEAST significant, set, bit is used
+/ │ regardless of corresponding input element validity
+/ │ intres2 is returned in least significant bits of xmm0
+/ ├─1:index of the MOST significant, set, bit is used
+/ │ regardless of corresponding input element validity
+/ │ each bit of intres2 is expanded to byte/word
+/ │┌─0:negation of intres1 is for all 16 (8) bits
+/ │├─1:negation of intres1 is masked by reg/mem validity
+/ ││┌─intres1 is negated (1’s complement)
+/ │││┌─mode{equalany,ranges,equaleach,equalordered}
+/ ││││ ┌─issigned
+/ ││││ │┌─is16bit
+/ u│││├┐││
pcmpistrm $0b01000100,%xmm3,%xmm1 # →XMM0 8-bit byte mask
pand %xmm2,%xmm0 # won't mask after NUL
paddb %xmm0,%xmm3
@@ -83,6 +98,4 @@ strcaseconv:
3: mov %rdi,%rax
.leafepilogue
.endfn strcaseconv
- .endfn strtolower,globl
- .endfn strtoupper,globl
.source __FILE__
diff --git a/libc/nexgen32e/strcmp-avx.S b/libc/nexgen32e/strcmp-avx.S
index a9d52a290..cce891653 100644
--- a/libc/nexgen32e/strcmp-avx.S
+++ b/libc/nexgen32e/strcmp-avx.S
@@ -43,7 +43,7 @@ strncmp$avx:
cmp %rsi,%rdi
je 1f
mov $-16,%rcx
- vxorps %xmm0,%xmm0,%xmm0
+ vpxor %xmm0,%xmm0,%xmm0
vpcmpeqd %xmm1,%xmm1,%xmm1
3: add $16,%rcx
4: lea 16(%rcx),%rax
diff --git a/libc/nexgen32e/strsak.S b/libc/nexgen32e/strsak.S
index a37391b4e..6f52553b8 100644
--- a/libc/nexgen32e/strsak.S
+++ b/libc/nexgen32e/strsak.S
@@ -18,6 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/nexgen32e/x86feature.h"
+#include "libc/nexgen32e/macros.h"
#include "libc/macros.h"
.source __FILE__
@@ -32,6 +33,7 @@ strchrnul:
.profilable
or $-1,%r9
jmp 0f
+ .endfn strchrnul,globl
/ Returns pointer to first instance of character, the BSD way.
/
@@ -41,6 +43,7 @@ strchrnul:
/ @note this won't return NULL if search character is NUL
index: nop
/ 𝑠𝑙𝑖𝑑𝑒
+ .endfn index,globl
/ Returns pointer to first instance of character.
/
@@ -56,6 +59,7 @@ strchr: .leafprologue
or $-1,%rsi
xor %r8,%r8
jmp strsak
+ .endfn strchr,globl
/ Returns pointer to first instance of character in range.
/
@@ -65,6 +69,7 @@ strchr: .leafprologue
rawmemchr:
or $-1,%rdx
/ 𝑠𝑙𝑖𝑑𝑒
+ .endfn rawmemchr,globl
/ Returns pointer to first instance of character in range.
/
@@ -80,6 +85,7 @@ memchr: .leafprologue
xor %r8,%r8
xor %r10,%r10
jmp strsak
+ .endfn memchr,globl
/ Returns length of NUL-terminated string w/ security blankets.
/
@@ -97,6 +103,7 @@ strnlen_s:
test %rdi,%rdi
jnz 0f
.leafepilogue
+ .endfn strnlen_s,globl
/ Returns length of NUL-terminated string.
/
@@ -105,6 +112,7 @@ strnlen_s:
/ @asyncsignalsafe
strlen: or $-1,%rsi
/ 𝑠𝑙𝑖𝑑𝑒
+ .endfn strlen,globl
/ Returns length of NUL-terminated memory, with limit.
/
@@ -118,6 +126,7 @@ strnlen:.leafprologue
0: xor %edx,%edx
mov %rdi,%r8
/ 𝑠𝑙𝑖𝑑𝑒
+ .endfn strnlen,globl
/ Swiss army knife of string character scanning.
/ Sixteen fast functions in one.
@@ -175,12 +184,8 @@ strsak: lea -1(%rdi),%rax
2: add %rcx,%rax
jmp .Lbyte
#if !X86_NEED(AVX2)
-.Lsse2: punpcklbw %xmm0,%xmm0
- pshuflw $0xe0,%xmm0,%xmm0
- pshufd $0x00,%xmm0,%xmm0
- punpcklbw %xmm1,%xmm1
- pshuflw $0xe0,%xmm1,%xmm1
- pshufd $0x00,%xmm1,%xmm1
+.Lsse2: pbroadcastb %xmm0
+ pbroadcastb %xmm1
1: add $32,%rax
sub $32,%rsi
jb 9b
@@ -203,14 +208,6 @@ strsak: lea -1(%rdi),%rax
jmp 2b
#endif
.endfn strsak,globl,hidden
- .endfn strnlen,globl
- .endfn strlen,globl
- .endfn strnlen_s,globl
- .endfn memchr,globl
- .endfn rawmemchr,globl
- .endfn strchr,globl
- .endfn index,globl
- .endfn strchrnul,globl
/* benchmarked on intel core i7-6700 @ 3.40GHz (skylake)
includes function call overhead (unless marked otherwise)
diff --git a/libc/nexgen32e/tinydivsi.greg.S b/libc/nexgen32e/tinydivsi.greg.S
index 4dae771a2..81dda78f4 100644
--- a/libc/nexgen32e/tinydivsi.greg.S
+++ b/libc/nexgen32e/tinydivsi.greg.S
@@ -21,17 +21,22 @@
/ Support code for fast integer division by Si units.
/
+/ Division by magnums is described in Hacker's Delight and is
+/ usually generated automatically by compilers, but sadly not
+/ when we optimize for size and idiv goes at least 10x slower
+/ so we do this which saves space while avoiding build tuning
+/
/ @param rdi is number to divide
/ @param cl is magnum #1
/ @param rdx is magnum #2
-/ @return truncated numerator
+/ @return quotient
tinydivsi:
.leafprologue
mov %rdi,%rax
imul %rdx
mov %rdx,%rax
sar %cl,%rax
- sar $0x3f,%rdi
+ sar $63,%rdi
sub %rdi,%rax
.leafepilogue
.endfn tinydivsi,globl
diff --git a/libc/nexgen32e/tinystrcmp.S b/libc/nexgen32e/tinystrcmp.S
index 1323bd970..63a96093e 100644
--- a/libc/nexgen32e/tinystrcmp.S
+++ b/libc/nexgen32e/tinystrcmp.S
@@ -19,7 +19,7 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-/ Compares NUL-terminated strings w/o heavy-lifting.
+/ Compares strings w/ no-clobber greg abi.
/
/ @param rdi is first non-null NUL-terminated string pointer
/ @param rsi is second non-null NUL-terminated string pointer
@@ -30,7 +30,11 @@ tinystrcmp:
.leafprologue
push %rcx
push %rdx
+ xor %eax,%eax
+ xor %edx,%edx
xor %ecx,%ecx
+ cmp %rdi,%rsi
+ je 1f
0: movzbl (%rdi,%rcx,1),%eax
movzbl (%rsi,%rcx,1),%edx
test %al,%al
diff --git a/libc/nexgen32e/tinystrlen.h b/libc/nexgen32e/tinystrlen.h
index b2034ea72..591ddafdf 100644
--- a/libc/nexgen32e/tinystrlen.h
+++ b/libc/nexgen32e/tinystrlen.h
@@ -1,6 +1,16 @@
#ifndef COSMOPOLITAN_LIBC_NEXGEN32E_TINYSTRLEN_H_
#define COSMOPOLITAN_LIBC_NEXGEN32E_TINYSTRLEN_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+#if !defined(__GNUC__) || defined(__STRICT_ANSI__)
+
+int tinystrlen(const char *);
+int tinystrnlen(const char *, size_t);
+int tinystrlen16(const char16_t *);
+int tinystrnlen16(const char16_t *, size_t);
+int tinywcslen(const wchar_t *);
+int tinywcsnlen(const wchar_t *, size_t);
+
+#else
forceinline int tinystrlen(const char *s) {
unsigned ax;
@@ -40,5 +50,6 @@ forceinline int tinywcsnlen(const wchar_t *s, size_t n) {
return ax;
}
+#endif
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_NEXGEN32E_TINYSTRLEN_H_ */
diff --git a/libc/nexgen32e/tinystrncmp.ncabi.S b/libc/nexgen32e/tinystrncmp.ncabi.S
new file mode 100644
index 000000000..546ad74c7
--- /dev/null
+++ b/libc/nexgen32e/tinystrncmp.ncabi.S
@@ -0,0 +1,56 @@
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/macros.h"
+
+/ Compares strings w/ limit & no-clobber greg abi.
+/
+/ @param %rdi is first string
+/ @param %rsi is second string
+/ @param %rdx is max length
+/ @return <0, 0, or >0 depending on comparison
+/ @clob flags only
+/ @asyncsignalsafe
+tinystrncmp:
+ .leafprologue
+ push %rbx
+ push %rcx
+ xor %eax,%eax
+ xor %ebx,%ebx
+ xor %ecx,%ecx
+ test %edx,%edx
+ jz 2f
+ cmp %rdi,%rsi
+ je 2f
+0: cmp %edx,%ecx
+ jae 1f
+ movzbl (%rdi,%rcx,1),%eax
+ movzbl (%rsi,%rcx,1),%ebx
+ test %al,%al
+ jz 1f
+ cmp %bl,%al
+ jne 1f
+ inc %ecx
+ jmp 0b
+1: sub %ebx,%eax
+2: pop %rcx
+ pop %rbx
+ .leafepilogue
+ .endfn tinystrncmp,globl
+ .source __FILE__
diff --git a/libc/nexgen32e/vendor.h b/libc/nexgen32e/vendor.h
index fe475cbeb..6b389ab2f 100644
--- a/libc/nexgen32e/vendor.h
+++ b/libc/nexgen32e/vendor.h
@@ -9,6 +9,7 @@
* ╤ ╤
* GenuineIntel
* AuthenticAMD
+ * GenuineCosmo
* NexGenDriven
* AMDisbetter!
* CentaurHauls
@@ -27,6 +28,7 @@
* └────┐ │
* G ⊕ t = 0x33 Intel
* A ⊕ A = 0x00 AMD
+ * G ⊕ s = 0x34 Cosmopolitan
* N ⊕ v = 0x38 NexGen (Modern x86)
* A ⊕ e = 0x24 AMD (Rank & File)
* C ⊕ u = 0x36 Via (DBA Centaur)
@@ -53,10 +55,11 @@
*/
#define IsAuthenticAMD() (_KCPUIDS_VENDOR() == 0x00)
#define IsGenuineIntel() (_KCPUIDS_VENDOR() == 0x33)
+#define IsGenuineCosmo() (_KCPUIDS_VENDOR() == 0x34)
-#define _KCPUIDS_VENDOR() \
- (((kCpuids[KCPUIDS_0][KCPUIDS_EBX] >> 000) & 0xff) ^ \
- ((kCpuids[KCPUIDS_0][KCPUIDS_ECX] >> 010) & 0xff))
+#define _KCPUIDS_VENDOR() \
+ (((kCpuids[KCPUIDS_0H][KCPUIDS_EBX] >> 000) & 0xff) ^ \
+ ((kCpuids[KCPUIDS_0H][KCPUIDS_EDX] >> 010) & 0xff))
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_NEXGEN32E_VENDOR_H_ */
diff --git a/libc/nexgen32e/x86feature.h b/libc/nexgen32e/x86feature.h
index 8a964cea1..f3ba0d5a8 100644
--- a/libc/nexgen32e/x86feature.h
+++ b/libc/nexgen32e/x86feature.h
@@ -72,6 +72,7 @@
#define X86_INVPCID 1H, EBX, 10, 0, _
#define X86_INVTSC 80000007H, EDX, 8, _X86_CC_POPCNT, _ /* i.e. not a K8 */
#define X86_LA57 7H, ECX, 16, 0, _
+#define X86_LAHF_LM 80000001H, ECX, 0, 0, _
#define X86_LM 80000001H, EDX, 29, 0, _
#define X86_MCA 1H, EDX, 14, 0, _
#define X86_MCE 1H, EDX, 7, 0, _
@@ -153,7 +154,6 @@
#define X86_FMA4 80000001H, ECX, 16, 0, _
#define X86_FXSR_OPT 80000001H, EDX, 25, 0, _
#define X86_IBS 80000001H, ECX, 10, 0, _
-#define X86_LAHF_LM 80000001H, ECX, 0, 0, _
#define X86_LWP 80000001H, ECX, 15, 0, _
#define X86_MISALIGNSSE 80000001H, ECX, 7, 0, _
#define X86_MMXEXT 80000001H, EDX, 22, 0, _
diff --git a/libc/nt/accounting.h b/libc/nt/accounting.h
index 8a4de2809..fba016218 100644
--- a/libc/nt/accounting.h
+++ b/libc/nt/accounting.h
@@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_NT_ACCOUNTING_H_
#define COSMOPOLITAN_LIBC_NT_ACCOUNTING_H_
+#include "libc/nt/thunk/msabi.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
#if 0
@@ -64,6 +65,9 @@ int32_t SetProcessWorkingSetSizeEx(int64_t hProcess,
uint64_t dwMaximumWorkingSetSize,
uint32_t Flags);
+#if ShouldUseMsabiAttribute()
+#include "libc/nt/thunk/accounting.inc"
+#endif /* ShouldUseMsabiAttribute() */
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_NT_ACCOUNTING_H_ */
diff --git a/libc/nt/files.h b/libc/nt/files.h
index 410133815..7b4739632 100644
--- a/libc/nt/files.h
+++ b/libc/nt/files.h
@@ -148,9 +148,9 @@ bool32 GetFileAttributesEx(
uint32_t GetCompressedFileSize(const char16_t *lpFileName,
uint32_t *lpFileSizeHigh);
bool32 SetFileAttributes(const char16_t *lpFileName, uint32_t dwFileAttributes);
-bool32 GetFileTime(int64_t hFile, struct NtFileTime *lpCreationFileTime,
- struct NtFileTime *lpLastAccessFileTime,
- struct NtFileTime *lpLastWriteFileTime);
+bool32 GetFileTime(int64_t hFile, struct NtFileTime *opt_lpCreationFileTime,
+ struct NtFileTime *opt_lpLastAccessFileTime,
+ struct NtFileTime *opt_lpLastWriteFileTime);
bool32 SetFileTime(int64_t hFile,
const struct NtFileTime *opt_lpCreationFileTime,
const struct NtFileTime *opt_lpLastAccessFileTime,
diff --git a/libc/nt/master.sh b/libc/nt/master.sh
index 509f54f15..6443db5fe 100755
--- a/libc/nt/master.sh
+++ b/libc/nt/master.sh
@@ -2162,8 +2162,8 @@ imp 'GetFileMUIPath' GetFileMUIPath KernelBase 550
imp 'GetFileNameFromBrowse' GetFileNameFromBrowse shell32 63
imp 'GetFileSecurity' GetFileSecurityW KernelBase 551 5
imp 'GetFileSecurityA' GetFileSecurityA advapi32 1326 5
-imp 'GetFileSize$nopenopenope' GetFileSize KernelBase 552
-imp 'GetFileSizeEx$nopenopenope' GetFileSizeEx KernelBase 553
+imp 'GetFileSize' GetFileSize KernelBase 552 m
+imp 'GetFileSizeEx' GetFileSizeEx KernelBase 553 m
imp 'GetFileTime' GetFileTime KernelBase 554 4
imp 'GetFileTitleA' GetFileTitleA comdlg32 111
imp 'GetFileTitle' GetFileTitleW comdlg32 112
diff --git a/libc/nt/memory.h b/libc/nt/memory.h
index af80b3729..8a91fab72 100644
--- a/libc/nt/memory.h
+++ b/libc/nt/memory.h
@@ -4,6 +4,7 @@
#include "libc/nt/enum/memflags.h"
#include "libc/nt/enum/offerpriority.h"
#include "libc/nt/enum/pageflags.h"
+#include "libc/nt/thunk/msabi.h"
#if 0
/* ░░░░
▒▒▒░░░▒▒▒▒▒▒▒▓▓▓░
@@ -72,6 +73,9 @@ bool32 PrefetchVirtualMemory(int64_t hProcess, const uint32_t *NumberOfEntries,
bool32 OfferVirtualMemory(void *inout_VirtualAddress, size_t Size,
enum NtOfferPriority Priority);
+#if ShouldUseMsabiAttribute()
+#include "libc/nt/thunk/memory.inc"
+#endif /* ShouldUseMsabiAttribute() */
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_NT_MEMORY_H_ */
diff --git a/libc/nt/nt.mk b/libc/nt/nt.mk
index c44789344..2035ee2e8 100644
--- a/libc/nt/nt.mk
+++ b/libc/nt/nt.mk
@@ -35,7 +35,7 @@ LIBC_NT_KERNEL32_A = o/$(MODE)/libc/nt/kernel32.a
LIBC_NT_KERNEL32_A_SRCS := $(wildcard libc/nt/kernel32/*.s)
LIBC_NT_KERNEL32_A_OBJS = $(LIBC_NT_KERNEL32_A_SRCS:%.s=o/$(MODE)/%.o)
LIBC_NT_KERNEL32_A_CHECKS = $(LIBC_NT_KERNEL32_A).pkg
-LIBC_NT_KERNEL32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E
+LIBC_NT_KERNEL32_A_DIRECTDEPS = LIBC_STUBS
LIBC_NT_KERNEL32_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_NT_KERNEL32_A_DIRECTDEPS),$($(x))))
@@ -56,7 +56,7 @@ LIBC_NT_ADVAPI32_A = o/$(MODE)/libc/nt/advapi32.a
LIBC_NT_ADVAPI32_A_SRCS := $(wildcard libc/nt/advapi32/*.s)
LIBC_NT_ADVAPI32_A_OBJS = $(LIBC_NT_ADVAPI32_A_SRCS:%.s=o/$(MODE)/%.o)
LIBC_NT_ADVAPI32_A_CHECKS = $(LIBC_NT_ADVAPI32_A).pkg
-LIBC_NT_ADVAPI32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E
+LIBC_NT_ADVAPI32_A_DIRECTDEPS = LIBC_STUBS
LIBC_NT_ADVAPI32_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_NT_ADVAPI32_A_DIRECTDEPS),$($(x))))
@@ -77,7 +77,7 @@ LIBC_NT_COMDLG32_A = o/$(MODE)/libc/nt/comdlg32.a
LIBC_NT_COMDLG32_A_SRCS := $(wildcard libc/nt/comdlg32/*.s)
LIBC_NT_COMDLG32_A_OBJS = $(LIBC_NT_COMDLG32_A_SRCS:%.s=o/$(MODE)/%.o)
LIBC_NT_COMDLG32_A_CHECKS = $(LIBC_NT_COMDLG32_A).pkg
-LIBC_NT_COMDLG32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E
+LIBC_NT_COMDLG32_A_DIRECTDEPS = LIBC_STUBS
LIBC_NT_COMDLG32_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_NT_COMDLG32_A_DIRECTDEPS),$($(x))))
@@ -98,7 +98,7 @@ LIBC_NT_GDI32_A = o/$(MODE)/libc/nt/gdi32.a
LIBC_NT_GDI32_A_SRCS := $(wildcard libc/nt/gdi32/*.s)
LIBC_NT_GDI32_A_OBJS = $(LIBC_NT_GDI32_A_SRCS:%.s=o/$(MODE)/%.o)
LIBC_NT_GDI32_A_CHECKS = $(LIBC_NT_GDI32_A).pkg
-LIBC_NT_GDI32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E
+LIBC_NT_GDI32_A_DIRECTDEPS = LIBC_STUBS
LIBC_NT_GDI32_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_NT_GDI32_A_DIRECTDEPS),$($(x))))
@@ -119,7 +119,7 @@ LIBC_NT_KERNELBASE_A = o/$(MODE)/libc/nt/KernelBase.a
LIBC_NT_KERNELBASE_A_SRCS := $(wildcard libc/nt/KernelBase/*.s)
LIBC_NT_KERNELBASE_A_OBJS = $(LIBC_NT_KERNELBASE_A_SRCS:%.s=o/$(MODE)/%.o)
LIBC_NT_KERNELBASE_A_CHECKS = $(LIBC_NT_KERNELBASE_A).pkg
-LIBC_NT_KERNELBASE_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E
+LIBC_NT_KERNELBASE_A_DIRECTDEPS = LIBC_STUBS
LIBC_NT_KERNELBASE_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_NT_KERNELBASE_A_DIRECTDEPS),$($(x))))
@@ -142,10 +142,9 @@ LIBC_NT_NTDLL_A_SRCS_S = libc/nt/ntdllimport.S
LIBC_NT_NTDLL_A_SRCS = $(LIBC_NT_NTDLL_A_SRCS_A) $(LIBC_NT_NTDLL_A_SRCS_S)
LIBC_NT_NTDLL_A_OBJS = \
$(LIBC_NT_NTDLL_A_SRCS_A:%.s=o/$(MODE)/%.o) \
- $(LIBC_NT_NTDLL_A_SRCS_S:%.S=o/$(MODE)/%.o) \
- $(LIBC_NT_NTDLL_A_SRCS:%=o/$(MODE)/%.zip.o)
+ $(LIBC_NT_NTDLL_A_SRCS_S:%.S=o/$(MODE)/%.o)
LIBC_NT_NTDLL_A_CHECKS = $(LIBC_NT_NTDLL_A).pkg
-LIBC_NT_NTDLL_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E LIBC_NT_KERNELBASE
+LIBC_NT_NTDLL_A_DIRECTDEPS = LIBC_STUBS LIBC_NT_KERNELBASE
LIBC_NT_NTDLL_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_NT_NTDLL_A_DIRECTDEPS),$($(x))))
@@ -176,7 +175,7 @@ LIBC_NT_NETAPI32_A = o/$(MODE)/libc/nt/netapi32.a
LIBC_NT_NETAPI32_A_SRCS := $(wildcard libc/nt/netapi32/*.s)
LIBC_NT_NETAPI32_A_OBJS = $(LIBC_NT_NETAPI32_A_SRCS:%.s=o/$(MODE)/%.o)
LIBC_NT_NETAPI32_A_CHECKS = $(LIBC_NT_NETAPI32_A).pkg
-LIBC_NT_NETAPI32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E
+LIBC_NT_NETAPI32_A_DIRECTDEPS = LIBC_STUBS
LIBC_NT_NETAPI32_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_NT_NETAPI32_A_DIRECTDEPS),$($(x))))
@@ -197,7 +196,7 @@ LIBC_NT_URL_A = o/$(MODE)/libc/nt/url.a
LIBC_NT_URL_A_SRCS := $(wildcard libc/nt/url/*.s)
LIBC_NT_URL_A_OBJS = $(LIBC_NT_URL_A_SRCS:%.s=o/$(MODE)/%.o)
LIBC_NT_URL_A_CHECKS = $(LIBC_NT_URL_A).pkg
-LIBC_NT_URL_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E
+LIBC_NT_URL_A_DIRECTDEPS = LIBC_STUBS
LIBC_NT_URL_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_NT_URL_A_DIRECTDEPS),$($(x))))
@@ -218,7 +217,7 @@ LIBC_NT_USER32_A = o/$(MODE)/libc/nt/user32.a
LIBC_NT_USER32_A_SRCS := $(wildcard libc/nt/user32/*.s)
LIBC_NT_USER32_A_OBJS = $(LIBC_NT_USER32_A_SRCS:%.s=o/$(MODE)/%.o)
LIBC_NT_USER32_A_CHECKS = $(LIBC_NT_USER32_A).pkg
-LIBC_NT_USER32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E
+LIBC_NT_USER32_A_DIRECTDEPS = LIBC_STUBS
LIBC_NT_USER32_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_NT_USER32_A_DIRECTDEPS),$($(x))))
@@ -239,7 +238,7 @@ LIBC_NT_WS2_32_A = o/$(MODE)/libc/nt/ws2_32.a
LIBC_NT_WS2_32_A_SRCS := $(wildcard libc/nt/ws2_32/*.s)
LIBC_NT_WS2_32_A_OBJS = $(LIBC_NT_WS2_32_A_SRCS:%.s=o/$(MODE)/%.o)
LIBC_NT_WS2_32_A_CHECKS = $(LIBC_NT_WS2_32_A).pkg
-LIBC_NT_WS2_32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E
+LIBC_NT_WS2_32_A_DIRECTDEPS = LIBC_STUBS
LIBC_NT_WS2_32_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_NT_WS2_32_A_DIRECTDEPS),$($(x))))
@@ -260,7 +259,7 @@ LIBC_NT_MSWSOCK_A = o/$(MODE)/libc/nt/MsWSock.a
LIBC_NT_MSWSOCK_A_SRCS := $(wildcard libc/nt/MsWSock/*.s)
LIBC_NT_MSWSOCK_A_OBJS = $(LIBC_NT_MSWSOCK_A_SRCS:%.s=o/$(MODE)/%.o)
LIBC_NT_MSWSOCK_A_CHECKS = $(LIBC_NT_MSWSOCK_A).pkg
-LIBC_NT_MSWSOCK_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E
+LIBC_NT_MSWSOCK_A_DIRECTDEPS = LIBC_STUBS
LIBC_NT_MSWSOCK_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_NT_MSWSOCK_A_DIRECTDEPS),$($(x))))
@@ -281,7 +280,7 @@ LIBC_NT_SHELL32_A = o/$(MODE)/libc/nt/shell32.a
LIBC_NT_SHELL32_A_SRCS := $(wildcard libc/nt/shell32/*.s)
LIBC_NT_SHELL32_A_OBJS = $(LIBC_NT_SHELL32_A_SRCS:%.s=o/$(MODE)/%.o)
LIBC_NT_SHELL32_A_CHECKS = $(LIBC_NT_SHELL32_A).pkg
-LIBC_NT_SHELL32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E
+LIBC_NT_SHELL32_A_DIRECTDEPS = LIBC_STUBS
LIBC_NT_SHELL32_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_NT_SHELL32_A_DIRECTDEPS),$($(x))))
$(LIBC_NT_SHELL32_A): \
@@ -294,11 +293,7 @@ $(LIBC_NT_SHELL32_A).pkg: \
#───────────────────────────────────────────────────────────────────────────────
-$(LIBC_NT_OBJS): libc/nt/nt.mk o/libc/nt/codegen.inc
-
-.PHONY: o/libc/nt
-o/libc/nt: $(LIBC_NT_LIBS) \
- $(LIBC_NT_CHECKS)
+$(LIBC_NT_OBJS): o/libc/nt/codegen.inc
.PHONY: o/$(MODE)/libc/nt
o/$(MODE)/libc/nt: \
diff --git a/libc/nt/nt/systemthreads.h b/libc/nt/nt/systemthreads.h
deleted file mode 100644
index 1a3124b7c..000000000
--- a/libc/nt/nt/systemthreads.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#ifndef COSMOPOLITAN_LIBC_NT_NT_SYSTEMTHREADS_H_
-#define COSMOPOLITAN_LIBC_NT_NT_SYSTEMTHREADS_H_
-#include "libc/nt/enum/kwaitreason.h"
-#include "libc/nt/enum/threadstate.h"
-#include "libc/nt/struct/clientid.h"
-#if !(__ASSEMBLER__ + __LINKER__ + 0)
-
-struct NtSystemThreads {
- int64_t KernelTime;
- int64_t UserTime;
- int64_t CreateTime;
- uint32_t WaitTime;
- void *StartAddress;
- struct NtClientId ClientId;
- int32_t Priority;
- int32_t BasePriority;
- uint32_t ContextSwitchCount;
- enum NtThreadState State;
- uint32_t WaitReason;
-};
-
-#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#endif /* COSMOPOLITAN_LIBC_NT_NT_SYSTEMTHREADS_H_ */
diff --git a/libc/nt/ntdllimport.S b/libc/nt/ntdllimport.S
index d64e27415..bb47fed14 100644
--- a/libc/nt/ntdllimport.S
+++ b/libc/nt/ntdllimport.S
@@ -19,7 +19,6 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/nt/enum/status.h"
#include "libc/macros.h"
-.source __FILE__
/ @fileoverview NTDLL.DLL Non-Mandatory Importer
/
diff --git a/libc/nt/process.h b/libc/nt/process.h
index f70718caf..ad2759895 100644
--- a/libc/nt/process.h
+++ b/libc/nt/process.h
@@ -76,7 +76,7 @@ bool32 SetProcessPriorityBoost(int64_t hProcess, bool32 bDisablePriorityBoost);
bool32 GetProcessPriorityBoost(int64_t hProcess, bool32 *pDisablePriorityBoost);
#if ShouldUseMsabiAttribute()
-#include "libc/nt/thunk/msabi.h"
+#include "libc/nt/thunk/process.inc"
#endif /* ShouldUseMsabiAttribute() */
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/nt/signals.h b/libc/nt/signals.h
index d391f1903..2d047072f 100644
--- a/libc/nt/signals.h
+++ b/libc/nt/signals.h
@@ -30,41 +30,38 @@
╚────────────────────────────────────────────────────────────────────────────│*/
#endif
-#define kNtSignalBreakpoint 0x80000003u
-#define kNtSignalIllegalInstruction 0xC000001Du
-#define kNtSignalPrivInstruction 0xC0000096u
-#define kNtSignalGuardPage 0x80000001u
-#define kNtSignalAccessViolation 0xC0000005u
-#define kNtSignalInPageError 0xC0000006u
-#define kNtSignalInvalidHandle 0xC0000008u
-#define kNtSignalInvalidParameter 0xC000000du
-#define kNtSignalFltDenormalOperand 0xC000008Du
-#define kNtSignalFltDivideByZero 0xC000008Eu
-#define kNtSignalFltInexactResult 0xC000008Fu
+#define kNtSignalBreakpoint 0x80000003u
+#define kNtSignalIllegalInstruction 0xC000001Du
+#define kNtSignalPrivInstruction 0xC0000096u
+#define kNtSignalGuardPage 0x80000001u
+#define kNtSignalAccessViolation 0xC0000005u
+#define kNtSignalInPageError 0xC0000006u
+#define kNtSignalInvalidHandle 0xC0000008u
+#define kNtSignalInvalidParameter 0xC000000du
+#define kNtSignalFltDenormalOperand 0xC000008Du
+#define kNtSignalFltDivideByZero 0xC000008Eu
+#define kNtSignalFltInexactResult 0xC000008Fu
#define kNtSignalFltInvalidOperation 0xC0000090u
-#define kNtSignalFltOverflow 0xC0000091u
-#define kNtSignalFltStackCheck 0xC0000092u
-#define kNtSignalFltUnderflow 0xC0000093u
+#define kNtSignalFltOverflow 0xC0000091u
+#define kNtSignalFltStackCheck 0xC0000092u
+#define kNtSignalFltUnderflow 0xC0000093u
#define kNtSignalIntegerDivideByZero 0xC0000094u
-#define kNtSignalDllNotFound 0xC0000135u
-#define kNtSignalOrdinalNotFound 0xC0000138u
-#define kNtSignalEntrypointNotFound 0xC0000139u
-#define kNtSignalControlCExit 0xC000013Au
-#define kNtSignalDllInitFailed 0xC0000142u
+#define kNtSignalDllNotFound 0xC0000135u
+#define kNtSignalOrdinalNotFound 0xC0000138u
+#define kNtSignalEntrypointNotFound 0xC0000139u
+#define kNtSignalControlCExit 0xC000013Au
+#define kNtSignalDllInitFailed 0xC0000142u
#define kNtSignalFloatMultipleFaults 0xC00002B4u
-#define kNtSignalFloatMultipleTraps 0xC00002B5u
-#define kNtSignalAssertionFailure 0xC0000420u
+#define kNtSignalFloatMultipleTraps 0xC00002B5u
+#define kNtSignalAssertionFailure 0xC0000420u
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
struct NtExceptionPointers;
-typedef enum NtExceptionHandlerActions (*NtTopLevelExceptionFilter)(
- const struct NtExceptionPointers *ExceptionInfo);
-
-typedef int32_t (*NtVectoredExceptionHandler)(
- struct NtExceptionPointers *ExceptionInfo);
+typedef int (*NtTopLevelExceptionFilter)(const struct NtExceptionPointers *);
+typedef int32_t (*NtVectoredExceptionHandler)(struct NtExceptionPointers *);
enum NtErrorModeFlags SetErrorMode(enum NtErrorModeFlags uMode);
diff --git a/libc/nt/struct/teb.h b/libc/nt/struct/teb.h
index be7f437bf..558a2fa2b 100644
--- a/libc/nt/struct/teb.h
+++ b/libc/nt/struct/teb.h
@@ -1,6 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_NT_TEB_H_
#define COSMOPOLITAN_LIBC_NT_TEB_H_
-#include "libc/bits/bits.h"
+#include "libc/bits/segmentation.h"
#include "libc/nt/struct/peb.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
@@ -14,14 +14,14 @@
#define NtGetErr() gs((int *)(0x68))
#define NtGetVersion() \
((NtGetPeb()->OSMajorVersion & 0xff) << 8 | NtGetPeb()->OSMinorVersion)
-#define _NtGetSeh() gs((void **)(0x00))
-#define _NtGetStackHigh() gs((void **)(0x08))
-#define _NtGetStackLow() gs((void **)(0x10))
+#define _NtGetSeh() gs((void **)(0x00))
+#define _NtGetStackHigh() gs((void **)(0x08))
+#define _NtGetStackLow() gs((void **)(0x10))
#define _NtGetSubsystemTib() gs((void **)(0x18))
-#define _NtGetFib() gs((void **)(0x20))
-#define _NtGetEnv() gs((char16_t **)(0x38))
-#define _NtGetRpc() gs((void **)(0x50))
-#define _NtGetTls() gs((void **)(0x58))
+#define _NtGetFib() gs((void **)(0x20))
+#define _NtGetEnv() gs((char16_t **)(0x38))
+#define _NtGetRpc() gs((void **)(0x50))
+#define _NtGetTls() gs((void **)(0x58))
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_NT_TEB_H_ */
diff --git a/libc/nt/thunk/accounting.inc b/libc/nt/thunk/accounting.inc
new file mode 100644
index 000000000..860f1f14b
--- /dev/null
+++ b/libc/nt/thunk/accounting.inc
@@ -0,0 +1,11 @@
+#define GetProcessTimes(...) __imp_GetProcessTimes(__VA_ARGS__)
+extern typeof(GetProcessTimes) *const __imp_GetProcessTimes __msabi;
+
+#define GetThreadTimes(...) __imp_GetThreadTimes(__VA_ARGS__)
+extern typeof(GetThreadTimes) *const __imp_GetThreadTimes __msabi;
+
+#define GetUserName(...) __imp_GetUserNameW(__VA_ARGS__)
+extern typeof(GetUserName) *const __imp_GetUserNameW __msabi;
+
+#define GetExitCodeProcess(...) __imp_GetExitCodeProcess(__VA_ARGS__)
+extern typeof(GetExitCodeProcess) *const __imp_GetExitCodeProcess __msabi;
diff --git a/libc/nt/thunk/files.inc b/libc/nt/thunk/files.inc
index 3afabee54..b2ac7e491 100644
--- a/libc/nt/thunk/files.inc
+++ b/libc/nt/thunk/files.inc
@@ -1,3 +1,5 @@
+#define CopyFile(...) __imp_CopyFileW(__VA_ARGS__)
#define FlushFileBuffers(...) __imp_FlushFileBuffers(__VA_ARGS__)
+extern typeof(CopyFile) *const __imp_CopyFileW __msabi;
extern typeof(FlushFileBuffers) *const __imp_FlushFileBuffers __msabi;
diff --git a/libc/nt/thunk/memory.inc b/libc/nt/thunk/memory.inc
new file mode 100644
index 000000000..fe36ff37b
--- /dev/null
+++ b/libc/nt/thunk/memory.inc
@@ -0,0 +1,10 @@
+#define CreateFileMappingNuma(...) __imp_CreateFileMappingNumaW(__VA_ARGS__)
+#define MapViewOfFileExNuma(...) __imp_MapViewOfFileExNuma(__VA_ARGS__)
+#define FlushViewOfFile(...) __imp_FlushViewOfFile(__VA_ARGS__)
+#define UnmapViewOfFile(...) __imp_UnmapViewOfFile(__VA_ARGS__)
+
+extern typeof(UnmapViewOfFile) *const __imp_UnmapViewOfFile __msabi;
+extern typeof(FlushViewOfFile) *const __imp_FlushViewOfFile __msabi;
+extern typeof(MapViewOfFileExNuma) *const __imp_MapViewOfFileExNuma __msabi;
+extern typeof(CreateFileMappingNuma) *const
+ __imp_CreateFileMappingNumaW __msabi;
diff --git a/libc/nt/thunk/process.inc b/libc/nt/thunk/process.inc
index 5fb341898..e483b043e 100644
--- a/libc/nt/thunk/process.inc
+++ b/libc/nt/thunk/process.inc
@@ -1,8 +1,19 @@
#define GetEnvironmentVariable(...) __imp_GetEnvironmentVariableW(__VA_ARGS__)
-#define GetPriorityClass(...) __imp_GetPriorityClass(__VA_ARGS__)
-#define SetPriorityClass(...) __imp_SetPriorityClass(__VA_ARGS__)
-
extern typeof(GetEnvironmentVariable) *const
__imp_GetEnvironmentVariableW __msabi;
+
+#define SetEnvironmentVariable(...) __imp_SetEnvironmentVariableW(__VA_ARGS__)
+extern typeof(SetEnvironmentVariable) *const
+ __imp_SetEnvironmentVariableW __msabi;
+
+#define GetPriorityClass(...) __imp_GetPriorityClass(__VA_ARGS__)
extern typeof(GetPriorityClass) *const __imp_GetPriorityClass __msabi;
+
+#define SetPriorityClass(...) __imp_SetPriorityClass(__VA_ARGS__)
extern typeof(SetPriorityClass) *const __imp_SetPriorityClass __msabi;
+
+#define GetCurrentProcessId(...) __imp_GetCurrentProcessId(__VA_ARGS__)
+extern typeof(GetCurrentProcessId) *const __imp_GetCurrentProcessId __msabi;
+
+#define CreateProcess(...) __imp_CreateProcessW(__VA_ARGS__)
+extern typeof(CreateProcess) *const __imp_CreateProcessW __msabi;
diff --git a/libc/nt/thunk/runtime.inc b/libc/nt/thunk/runtime.inc
index ea5a3faa8..4d2204897 100644
--- a/libc/nt/thunk/runtime.inc
+++ b/libc/nt/thunk/runtime.inc
@@ -1,23 +1,33 @@
-#define ExitProcess(...) __imp_ExitProcess(__VA_ARGS__)
-#define FreeEnvironmentStrings(...) __imp_FreeEnvironmentStringsW(__VA_ARGS__)
-#define GetCommandLine(...) __imp_GetCommandLineW(__VA_ARGS__)
-#define GetEnvironmentStrings(...) __imp_GetEnvironmentStringsW(__VA_ARGS__)
-#define GetStdHandle(...) __imp_GetStdHandle(__VA_ARGS__)
-#define SetStdHandle(...) __imp_SetStdHandle(__VA_ARGS__)
-#define ReadFile(...) __imp_ReadFile(__VA_ARGS__)
-#define WriteFile(...) __imp_WriteFile(__VA_ARGS__)
-#define SetDefaultDllDirectories(...) \
- __imp_SetDefaultDllDirectories(__VA_ARGS__)
-
+#define ExitProcess(...) __imp_ExitProcess(__VA_ARGS__)
extern typeof(ExitProcess) *const __imp_ExitProcess __msabi;
+
+#define FreeEnvironmentStrings(...) __imp_FreeEnvironmentStringsW(__VA_ARGS__)
extern typeof(FreeEnvironmentStrings) *const
__imp_FreeEnvironmentStringsW __msabi;
+
+#define GetCommandLine(...) __imp_GetCommandLineW(__VA_ARGS__)
extern typeof(GetCommandLine) *const __imp_GetCommandLineW __msabi;
+
+#define GetEnvironmentStrings(...) __imp_GetEnvironmentStringsW(__VA_ARGS__)
extern typeof(GetEnvironmentStrings) *const
__imp_GetEnvironmentStringsW __msabi;
+
+#define GetStdHandle(...) __imp_GetStdHandle(__VA_ARGS__)
extern typeof(GetStdHandle) *const __imp_GetStdHandle __msabi;
+
+#define SetStdHandle(...) __imp_SetStdHandle(__VA_ARGS__)
extern typeof(SetStdHandle) *const __imp_SetStdHandle __msabi;
+
+#define ReadFile(...) __imp_ReadFile(__VA_ARGS__)
extern typeof(ReadFile) *const __imp_ReadFile __msabi;
+
+#define WriteFile(...) __imp_WriteFile(__VA_ARGS__)
extern typeof(WriteFile) *const __imp_WriteFile __msabi;
+
+#define SetDefaultDllDirectories(...) \
+ __imp_SetDefaultDllDirectories(__VA_ARGS__)
extern typeof(SetDefaultDllDirectories) *const
__imp_SetDefaultDllDirectories __msabi;
+
+#define GetCurrentProcess(...) __imp_GetCurrentProcess(__VA_ARGS__)
+extern typeof(GetCurrentProcess) *const __imp_GetCurrentProcess __msabi;
diff --git a/libc/pe.h b/libc/pe.h
index b9b0239d3..32c1f0f61 100644
--- a/libc/pe.h
+++ b/libc/pe.h
@@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_PE_H_
#define COSMOPOLITAN_LIBC_PE_H_
+#ifndef __STRICT_ANSI__
#include "libc/dce.h"
#include "libc/nt/pedef.h"
#include "libc/nt/struct/imagedosheader.h"
@@ -25,4 +26,5 @@ forceinline void *pecomputerva(struct NtImageDosHeader *mz, size_t mzsize,
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_PE_H_ */
diff --git a/libc/rand/g_rando.S b/libc/rand/g_rando.S
index 4fe478c15..e2bca92de 100644
--- a/libc/rand/g_rando.S
+++ b/libc/rand/g_rando.S
@@ -18,15 +18,16 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-#include "libc/notice.inc"
-.source __FILE__
.bss
.align 8
-g_rando:.quad 0
- .endobj g_rando,globl,hidden
+g_rando:
+ .quad 0
+ .endobj g_rando,globl
.previous
.init.start 100,_init_g_rando
- movq $1,g_rando(%rip)
+ movb $1,g_rando(%rip)
.init.end 100,_init_g_rando
+
+ .source __FILE__
diff --git a/libc/rand/lcg.h b/libc/rand/lcg.h
index e6ac7f7b7..bdb57d3d7 100644
--- a/libc/rand/lcg.h
+++ b/libc/rand/lcg.h
@@ -1,7 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_LCG_H_
#define COSMOPOLITAN_LIBC_LCG_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
-COSMOPOLITAN_C_START_
forceinline uint64_t KnuthLinearCongruentialGenerator(uint64_t prev[1]) {
/* Knuth, D.E., "The Art of Computer Programming," Vol 2,
@@ -11,6 +10,5 @@ forceinline uint64_t KnuthLinearCongruentialGenerator(uint64_t prev[1]) {
return prev[0];
}
-COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_LCG_H_ */
diff --git a/libc/rand/rand.ncabi.c b/libc/rand/rand.c
similarity index 96%
rename from libc/rand/rand.ncabi.c
rename to libc/rand/rand.c
index 6255a04ff..e483056b4 100644
--- a/libc/rand/rand.ncabi.c
+++ b/libc/rand/rand.c
@@ -28,4 +28,6 @@
* seed at startup by default, unless srand() is called. This makes it
* useful in cases where deterministic behavior is needed.
*/
-int(rand)(void) { return KnuthLinearCongruentialGenerator(&g_rando) >> 33; }
+int rand(void) {
+ return KnuthLinearCongruentialGenerator(&g_rando) >> 33;
+}
diff --git a/libc/rand/rand.h b/libc/rand/rand.h
index 92b7e0d85..ae1a8c3ca 100644
--- a/libc/rand/rand.h
+++ b/libc/rand/rand.h
@@ -1,6 +1,5 @@
#ifndef COSMOPOLITAN_LIBC_RAND_RAND_H_
#define COSMOPOLITAN_LIBC_RAND_RAND_H_
-#include "libc/ncabi.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
/*───────────────────────────────────────────────────────────────────────────│─╗
@@ -26,11 +25,6 @@ int64_t winrandish(void);
uint64_t rdrand(void);
float randf(void);
-#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
-NCABI_DECLARE_0(NCABI_NOPRUNE, int, __rand, "rand")
-#define rand() __rand()
-#endif
-
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_RAND_RAND_H_ */
diff --git a/libc/rand/rngset.c b/libc/rand/rngset.c
index 57b95bef3..f5563c120 100644
--- a/libc/rand/rngset.c
+++ b/libc/rand/rngset.c
@@ -22,16 +22,18 @@
#include "libc/str/str.h"
/**
- * Fills memory with random bytes.
+ * Fills memory with random bytes, e.g.
*
- * @param seed can be rand64
- * @param reseed is number of bytes between seed() calls
- * @return buf
+ * char buf[1024];
+ * rngset(buf, sizeof(buf), rand64, -1);
+ *
+ * @param seed can be rand64() and is always called at least once
+ * @param reseed is bytes between seed() calls and -1 disables it
+ * @return original buf
*/
-void *rngset(void *buf, size_t size, uint64_t (*seed)(void), size_t reseed) {
+void *rngset(void *buf, size_t size, uint64_t seed(void), size_t reseed) {
unsigned char *p;
uint64_t i, x, state;
- i = 0;
p = buf;
state = seed();
for (i = 0; size - i >= sizeof(x); i += sizeof(x)) {
diff --git a/libc/rand/xorshift.h b/libc/rand/xorshift.h
index b98d8bbb1..8ff8ad571 100644
--- a/libc/rand/xorshift.h
+++ b/libc/rand/xorshift.h
@@ -5,24 +5,11 @@
#define kMarsagliaXorshift32Seed 2463534242
#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
-forceinline uint64_t MarsagliaXorshift64(uint64_t state[hasatleast 1]) {
- uint64_t x = state[0];
- x ^= x << 13;
- x ^= x >> 7;
- x ^= x << 17;
- state[0] = x;
- return x;
-}
-
-forceinline uint32_t MarsagliaXorshift32(uint32_t state[hasatleast 1]) {
- uint32_t x = state[0];
- x ^= x << 13;
- x ^= x >> 17;
- x ^= x << 5;
- state[0] = x;
- return x;
-}
+uint32_t MarsagliaXorshift32(uint32_t[hasatleast 1]);
+uint64_t MarsagliaXorshift64(uint64_t[hasatleast 1]);
+COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_RAND_XORSHIFT_H_ */
diff --git a/libc/rand/xorshift32.c b/libc/rand/xorshift32.c
new file mode 100644
index 000000000..a51522ecc
--- /dev/null
+++ b/libc/rand/xorshift32.c
@@ -0,0 +1,29 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/rand/xorshift.h"
+
+uint32_t MarsagliaXorshift32(uint32_t state[hasatleast 1]) {
+ uint32_t x = state[0];
+ x ^= x << 13;
+ x ^= x >> 17;
+ x ^= x << 5;
+ state[0] = x;
+ return x;
+}
diff --git a/libc/rand/xorshift64.c b/libc/rand/xorshift64.c
new file mode 100644
index 000000000..e8f7485f3
--- /dev/null
+++ b/libc/rand/xorshift64.c
@@ -0,0 +1,29 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/rand/xorshift.h"
+
+uint64_t MarsagliaXorshift64(uint64_t state[hasatleast 1]) {
+ uint64_t x = state[0];
+ x ^= x << 13;
+ x ^= x >> 7;
+ x ^= x << 17;
+ state[0] = x;
+ return x;
+}
diff --git a/libc/runtime/arememoryintervalsok.c b/libc/runtime/arememoryintervalsok.c
new file mode 100644
index 000000000..e05fa60ed
--- /dev/null
+++ b/libc/runtime/arememoryintervalsok.c
@@ -0,0 +1,36 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/runtime/memtrack.h"
+#include "libc/runtime/runtime.h"
+
+bool AreMemoryIntervalsOk(const struct MemoryIntervals *mm) {
+ int i;
+ for (i = 0; i < mm->i; ++i) {
+ if (mm->p[i].y < mm->p[i].x) return false;
+ if (i) {
+ if (mm->h[i] || mm->h[i - 1]) {
+ if (mm->p[i].x <= mm->p[i - 1].y) return false;
+ } else {
+ if (mm->p[i].x <= mm->p[i - 1].y + 1) return false;
+ }
+ }
+ }
+ return true;
+}
diff --git a/libc/runtime/asan.greg.c b/libc/runtime/asan.greg.c
index 968cd9ca2..7f0707442 100644
--- a/libc/runtime/asan.greg.c
+++ b/libc/runtime/asan.greg.c
@@ -19,31 +19,57 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/calls.h"
#include "libc/stdio/stdio.h"
+#include "libc/str/str.h"
#include "libc/sysv/consts/fileno.h"
+struct SourceLocation {
+ const char *filename;
+ int line;
+ int column;
+};
+
+struct AccessInfo {
+ const uint8_t *addr;
+ const uint8_t *first_bad_addr;
+ size_t size;
+ bool iswrite;
+ unsigned long ip;
+};
+
+struct Global {
+ const uint8_t *addr;
+ size_t size;
+ size_t size_with_redzone;
+ const void *name;
+ const void *module_name;
+ unsigned long has_cxx_init;
+ struct kasan_source_location *location;
+ char *odr_indicator;
+};
+
privileged void __asan_init(void) {
}
-privileged void __asan_register_globals(uintptr_t a, uintptr_t b) {
+privileged void __asan_version_mismatch_check_v8(void) {
}
-privileged void __asan_unregister_globals(uintptr_t a, uintptr_t b) {
+privileged void __asan_register_globals(struct Global globals[], int n) {
+}
+
+privileged void __asan_unregister_globals(struct Global globals[], int n) {
+}
+
+privileged void __asan_report_load_n(uint8_t *p, int n) {
+}
+
+privileged void __asan_report_store_n(uint8_t *p, int n) {
+ __asan_report_load_n(p, n);
}
privileged void __asan_loadN(uintptr_t ptr, size_t size) {
- dprintf(STDERR_FILENO, "load %p %zu");
}
privileged void __asan_storeN(uintptr_t ptr, size_t size) {
- dprintf(STDERR_FILENO, "store %p %zu");
-}
-
-privileged void __asan_report_load_n(uintptr_t ptr, size_t size) {
- dprintf(STDERR_FILENO, "%s%zu%s%#p", "asan: load ", size, " bytes at ", ptr);
-}
-
-privileged void __asan_report_store_n(uintptr_t ptr, size_t size) {
- dprintf(STDERR_FILENO, "%s%zu%s%#p", "asan: store ", size, " bytes at ", ptr);
}
privileged uintptr_t __asan_stack_malloc(size_t size, int classid) {
@@ -54,9 +80,7 @@ privileged void __asan_stack_free(uintptr_t ptr, size_t size, int classid) {
}
privileged void __asan_handle_no_return(void) {
-}
-
-privileged void __asan_version_mismatch_check_v8(void) {
+ DebugBreak();
}
privileged void __asan_alloca_poison(uintptr_t addr, uintptr_t size) {
diff --git a/libc/runtime/assertfail.c b/libc/runtime/assertfail.c
index f46431617..f31463f38 100644
--- a/libc/runtime/assertfail.c
+++ b/libc/runtime/assertfail.c
@@ -18,7 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
-#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/log/log.h"
#include "libc/macros.h"
diff --git a/libc/runtime/atexit.c b/libc/runtime/atexit.c
new file mode 100644
index 000000000..d373e580e
--- /dev/null
+++ b/libc/runtime/atexit.c
@@ -0,0 +1,34 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/runtime/runtime.h"
+
+/**
+ * Adds global destructor.
+ *
+ * Destructors are called in reverse order. They won't be called
+ * if the program aborts or _exit() is called. Invocations of this
+ * function are usually generated by the C++ compiler.
+ *
+ * @param rdi callback typed void(*)(T) (nullable)
+ * @return 0 on success or nonzero if out of space
+ */
+int atexit(void f(void)) {
+ return __cxa_atexit(f, NULL, NULL);
+}
diff --git a/libc/runtime/carsort.h b/libc/runtime/carsort.h
new file mode 100644
index 000000000..212d60eb4
--- /dev/null
+++ b/libc/runtime/carsort.h
@@ -0,0 +1,11 @@
+#ifndef COSMOPOLITAN_LIBC_RUNTIME_CARSORT_H_
+#define COSMOPOLITAN_LIBC_RUNTIME_CARSORT_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void carsort100(size_t, int32_t (*)[2]) paramsnonnull() nocallback nothrow;
+void carsort1000(size_t, int32_t (*)[2]) paramsnonnull() nocallback nothrow;
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_RUNTIME_CARSORT_H_ */
diff --git a/libc/runtime/carsort100.c b/libc/runtime/carsort100.c
new file mode 100644
index 000000000..a7c5c94ca
--- /dev/null
+++ b/libc/runtime/carsort100.c
@@ -0,0 +1,41 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/runtime/carsort.h"
+#include "libc/str/str.h"
+
+/**
+ * Sorts int32 key-value arrays of trivial size.
+ *
+ * @see test/libc/alg/carsort_test.c
+ * @see carsort1000() if larger
+ */
+textstartup void carsort100(size_t n, int32_t a[n][2]) {
+ int32_t t[2];
+ unsigned i, j;
+ for (i = 1; i < n; ++i) {
+ j = i;
+ memcpy(t, a[i], sizeof(t));
+ while (j > 0 && t[0] < a[j - 1][0]) {
+ memcpy(a[j], a[j - 1], sizeof(t));
+ --j;
+ }
+ memcpy(a[j], t, sizeof(t));
+ }
+}
diff --git a/libc/runtime/heapsortcar.c b/libc/runtime/carsort1000.c
similarity index 73%
rename from libc/runtime/heapsortcar.c
rename to libc/runtime/carsort1000.c
index 1bdaa16c0..2e971b0c1 100644
--- a/libc/runtime/heapsortcar.c
+++ b/libc/runtime/carsort1000.c
@@ -17,22 +17,21 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/alg/alg.h"
+#include "libc/runtime/carsort.h"
#include "libc/str/str.h"
-static unsigned heapsortmax(int32_t (*A)[2], unsigned n, unsigned i, unsigned j,
- unsigned k) {
- unsigned m = i;
- if (j < n && A[j][0] > A[m][0]) m = j;
- if (k < n && A[k][0] > A[m][0]) m = k;
- return m;
+static textstartup size_t HeapSortMax(size_t n, int32_t A[n][2], long i, long j,
+ long k) {
+ if (j < n && A[j][0] > A[i][0]) i = j;
+ if (k < n && A[k][0] > A[i][0]) i = k;
+ return i;
}
-static void heapsortdown(int32_t (*A)[2], unsigned n, unsigned i) {
- unsigned j;
+static textstartup void HeapSortDown(size_t n, int32_t A[n][2], long i) {
+ size_t j;
int32_t t[2];
for (;;) {
- unsigned j = heapsortmax(A, n, i, 2 * i + 1, 2 * i + 2);
+ j = HeapSortMax(n, A, i, 2 * i + 1, 2 * i + 2);
if (j == i) break;
memcpy(t, A[i], sizeof(t));
memcpy(A[i], A[j], sizeof(t));
@@ -42,25 +41,21 @@ static void heapsortdown(int32_t (*A)[2], unsigned n, unsigned i) {
}
/**
- * Sorts key-values.
+ * Sorts int32 key-value arrays of nontrivial size.
*
- * heapsortcar is a linearithmic / constant-memory / non-stable sorting
- * function that's a tenth the size of the more generalized qsort() API.
- * It can only sort arrays of 64-bit values, and comparisons happen by
- * treating the lower 32-bits as a signed integer.
- *
- * @see test/libc/alg/heapsortcar_test.c
+ * @see test/libc/alg/carsort_test.c
+ * @see carsort100() if smaller
*/
-void heapsortcar(int32_t (*A)[2], unsigned n) {
- unsigned i;
+textstartup void carsort1000(size_t n, int32_t A[n][2]) {
+ size_t i;
int32_t t[2];
for (i = ((n - 2) / 2) + 1; i > 0; i--) {
- heapsortdown(A, n, i - 1);
+ HeapSortDown(n, A, i - 1);
}
for (i = 0; i < n; i++) {
memcpy(t, A[n - i - 1], sizeof(t));
memcpy(A[n - i - 1], A[0], sizeof(t));
memcpy(A[0], t, sizeof(t));
- heapsortdown(A, n - i - 1, 0);
+ HeapSortDown(n - i - 1, A, 0);
}
}
diff --git a/libc/runtime/close_s.c b/libc/runtime/close_s.c
index 021e5a660..0e2209ecc 100644
--- a/libc/runtime/close_s.c
+++ b/libc/runtime/close_s.c
@@ -19,18 +19,18 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/bits.h"
#include "libc/calls/calls.h"
-#include "libc/runtime/mappings.h"
#include "libc/runtime/runtime.h"
/**
* Closes file descriptor.
*
- * The caller's variable is made -1. Note that close(-1) is a no-op.
+ * The caller's variable is made -1 so subsequent calls are no-ops.
*
* @return 0 on success, or -1 w/ errno
* @asyncsignalsafe
*/
int close_s(int *fdp) {
int fd = -1;
- return close(lockxchg(fdp, &fd));
+ if (lockxchg(fdp, &fd) == -1) return 0;
+ return close(fd);
}
diff --git a/libc/runtime/closesymboltable.c b/libc/runtime/closesymboltable.c
index 74e551791..7981892a9 100644
--- a/libc/runtime/closesymboltable.c
+++ b/libc/runtime/closesymboltable.c
@@ -17,21 +17,23 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/calls.h"
#include "libc/runtime/ezmap.h"
#include "libc/runtime/symbols.h"
-#include "libc/calls/calls.h"
/**
* Frees symbol table.
* @return 0 on success or -1 on system error
*/
int closesymboltable(struct SymbolTable **table) {
- int rc = 0;
- if (*table && (intptr_t)*table != (intptr_t)-1) {
- struct SymbolTable *t = *table;
+ int rc;
+ struct SymbolTable *t;
+ rc = 0;
+ if (*table && *table != MAP_FAILED) {
+ t = *table;
*table = NULL;
- if (unmapfile(&t->mf) == -1) rc = -1;
- if (munmap(t, t->scratch) == -1) rc = -1;
+ rc |= unmapfile(&t->mf);
+ rc |= munmap(t, t->scratch);
}
return rc;
}
diff --git a/libc/runtime/construct.S b/libc/runtime/construct.S
new file mode 100644
index 000000000..24caf77b6
--- /dev/null
+++ b/libc/runtime/construct.S
@@ -0,0 +1,45 @@
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/runtime/internal.h"
+#include "libc/macros.h"
+.text.startup
+.source __FILE__
+
+/ Calls global initialization functions.
+_construct:
+ push %rbp
+ mov %rsp,%rbp
+ orb $RUNSTATE_INITIALIZED,g_runstate(%rip)
+ ezlea __init_array_start,ax # static ctors in forward order
+ .weak __init_array_start # could be called multiple times
+ ezlea __init_array_end,cx # idempotency recommended
+ .weak __init_array_end # @see ape/ape.lds
+1: cmp %rax,%rcx
+ je 2f
+ push %rax
+ push %rcx
+ call *(%rax)
+ pop %rcx
+ pop %rax
+ add $8,%rax
+ jmp 1b
+2: pop %rbp
+ ret
+ .endfn _construct,globl
diff --git a/libc/runtime/cxaatexit.S b/libc/runtime/cxaatexit.S
deleted file mode 100644
index 24fbd3b25..000000000
--- a/libc/runtime/cxaatexit.S
+++ /dev/null
@@ -1,181 +0,0 @@
-/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
-│vi: set et ft=asm ts=8 sw=8 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "ape/relocations.h"
-#include "libc/macros.h"
-.section .text.exit,"ax",@progbits
-.source __FILE__
-
-/ Delegates to __cxa_atexit().
-/
-/ @param rdi callback typed void(*)(void) (nullable)
-/ @return 0 on success or nonzero if out of space
-atexit: xor %esi,%esi
- xor %edx,%edx
-/ 𝑠𝑙𝑖𝑑𝑒
-
-/ Registers destructor to be called upon exit().
-/
-/ Destructors are called in reverse order. They won't be called
-/ if the program aborts or _exit() is called. Invocations of this
-/ function are usually generated by the C++ compiler.
-/
-/ @param rdi callback typed void(*)(T) (nullable)
-/ @param rsi callback arg typed T (nullable)
-/ @param rdx dso handle (nullable)
-/ @return 0 on success or nonzero if out of space
-/ @note folks have forked libc in past just to unbloat atexit()
-/ @see news.ycombinator.com/item?id=20228082
-/ @preinitsafe
-__cxa_atexit:
- push %rbp
- mov %rsp,%rbp
- .profilable
- push %r15
- ezlea g_cxa,cx
- mov %rcx,%r15
- mov (%r15),%rax # get state->block
- mov 8(%r15),%rcx # get state->offset
- test %rcx,%rcx
- jz 2f
-0: sub $24,%rcx
- mov %rdx,(%rax,%rcx) # set cb->dso
- mov %rsi,8(%rax,%rcx) # set cb->arg
- mov %rdi,16(%rax,%rcx) # set cb->fn
- mov %rcx,8(%r15) # set state->offset
- xor %eax,%eax # success
-1: pop %r15
- pop %rbp
- ret
-2: .weak calloc
- ezlea calloc,cx
- test %rcx,%rcx
- jz 1b # fail (no malloc)
- push %rax
- push %rdi
- push %rsi
- pushpop ATEXIT_MAX+1,%rdi
- pushpop 16,%rsi
- call *%rcx
- pop %rsi
- pop %rdi
- pop %rcx # rax=new rcx=old
- test %rax,%rax
- jz 1b # fail (no memory)
- mov $ATEXIT_MAX*8*3,%r8
- mov %rax,(%r15) # set state->block
- mov %rcx,(%rax,%r8) # set block->next
- mov %r8,%rcx
- jmp 0b
- .endfn __cxa_atexit,globl
- .endfn atexit,globl
-
-/ Triggers destructors.
-/
-/ This implementation supports DSO handles, but is optimized for
-/ cases when it's called only once by exit().
-/
-/ @param rdi is dso predicate or null to destroy all
-/ @see libc/exit.c
-__cxa_finalize:
- push %rbp
- mov %rsp,%rbp
- .profilable
- push %r14
- push %r13
- push %r12
- mov g_cxa(%rip),%rsi
- mov %rdi,%r14
-0: mov %rsi,%r12 # loop through blocks
- pushpop ATEXIT_MAX,%rcx
-1: lodsq #→ dso # loop through callbacks
- xchg %rax,%rdx
- lodsq #→ arg
- xchg %rax,%rdi
- lodsq #→ fn
- test %rax,%rax
- jz 2f # ignore empty slots
- test %r14,%r14
- jmp 5f # null predicate match all
- cmp %r14,%rdx
- jne 2f # predicate mismatch
-5: push %rsi
- push %rcx
- push %rcx
- call *%rax
- pop %rcx
- pop %rcx
- pop %rsi
- xor %eax,%eax # clear slot (never reused)
- mov %rax,-8(%rsi)
-2: loop 1b
- lodsq # get next block ptr
- test %rax,%rax # don't free static block no. 1
- jz 3f # which always has next == NULL
- test %r14,%r14
- jz 1f # don't free anything if just one dso
- push %rax
- mov %r12,%rdi
- .weak free
- call free # can't panic due to earlier test
-1: pop %rsi
- jmp 0b
-3: pop %r12 # align stack for next call
- test %r14,%r14 # no static dtor for dso exit
- jnz 9f
- ezlea __fini_array_end,ax # static dtors in reverse order
- .weak __fini_array_end # could be called multiple times
- ezlea __fini_array_start,cx # idempotency recommended
- .weak __fini_array_start # or consider atexit()
-8: sub $8,%rax # @see ape/ape.lds
- cmp %rcx,%rax
- jl 9f
- push %rax
- push %rcx
- call *(%rax)
- pop %rcx
- pop %rax
- jmp 8b
-9: pop %r13
- pop %r14
- pop %rbp
- ret
- .endfn __cxa_finalize,globl,hidden
-
- .bss
- .align 16 # static/dynamic hybrid linked list
-g_cxa: .quad 0 # last block ptr: (long (*)[32][3])
- .quad 0 # block byte offset moves backwards
- .endobj g_cxa
-g_cxa_static:
- .rept ATEXIT_MAX
- .quad 0 # dso
- .quad 0 # arg
- .quad 0 # fn (or NULL for empty)
- .endr
- .quad 0 # next (always NULL in static block)
- .endobj g_cxa_static
- .previous
-
- .init.start 300,_init_g_cxa
- ezlea g_cxa,cx
- lea g_cxa_static-g_cxa(%rcx),%rax
- mov %rax,(%rcx)
- movl $ATEXIT_MAX*8*3,8(%rcx)
- .init.end 300,_init_g_cxa
diff --git a/libc/runtime/cxaatexit.c b/libc/runtime/cxaatexit.c
new file mode 100644
index 000000000..b68ef8dd8
--- /dev/null
+++ b/libc/runtime/cxaatexit.c
@@ -0,0 +1,122 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/assert.h"
+#include "libc/bits/weaken.h"
+#include "libc/macros.h"
+#include "libc/mem/mem.h"
+#include "libc/nexgen32e/bsf.h"
+#include "libc/nexgen32e/bsr.h"
+#include "libc/runtime/runtime.h"
+#include "libc/sysv/errfuns.h"
+
+static struct CxaAtexitBlocks {
+ struct CxaAtexitBlock {
+ unsigned mask;
+ struct CxaAtexitBlock *next;
+ struct CxaAtexit {
+ void *fp;
+ void *arg;
+ void *pred;
+ } p[ATEXIT_MAX];
+ } * p, root;
+} __cxa_blocks;
+
+/**
+ * Adds global destructor.
+ *
+ * Destructors are called in reverse order. They won't be called if the
+ * program aborts or _exit() is called. Invocations of this function are
+ * usually generated by the C++ compiler. Behavior is limitless if you
+ * choose to link calloc() and free().
+ *
+ * @param fp is void(*)(T)
+ * @param arg is passed to callback
+ * @param pred can be non-null for things like dso modules
+ * @return 0 on success or nonzero w/ errno
+ * @note folks have forked libc in past just to unbloat atexit()
+ */
+int __cxa_atexit(void *fp, void *arg, void *pred) {
+ unsigned i;
+ struct CxaAtexitBlock *b, *b2;
+ b = __cxa_blocks.p;
+ if (!b) b = __cxa_blocks.p = &__cxa_blocks.root;
+ if (!~b->mask) {
+ if (weaken(calloc) &&
+ (b2 = weaken(calloc)(1, sizeof(struct CxaAtexitBlock)))) {
+ b2->next = b;
+ __cxa_blocks.p = b = b2;
+ } else {
+ return enomem();
+ }
+ }
+ i = bsr(~b->mask);
+ assert(i < ARRAYLEN(b->p));
+ b->mask |= 1u << i;
+ b->p[i].fp = fp;
+ b->p[i].arg = arg;
+ b->p[i].pred = pred;
+ return 0;
+}
+
+/**
+ * Triggers global destructors.
+ *
+ * @param pred can be null to match all
+ * @note reentrant emptor
+ */
+void __cxa_finalize(void *pred) {
+ unsigned i;
+ unsigned long mask;
+ struct CxaAtexitBlock *b, *b2;
+StartOver:
+ if ((b = __cxa_blocks.p)) {
+ for (;;) {
+ mask = b->mask;
+ while (mask) {
+ i = bsf(mask);
+ mask &= ~(1u << i);
+ if (!pred || pred == b->p[i].pred) {
+ b->mask &= ~(1u << i);
+ if (b->p[i].fp) {
+ ((void (*)(void *))b->p[i].fp)(b->p[i].arg);
+ goto StartOver;
+ }
+ }
+ }
+ if (!pred) {
+ b2 = b->next;
+ if (b2) {
+ assert(b != &__cxa_blocks.root);
+ if (weaken(free)) {
+ weaken(free)(b);
+ }
+ }
+ __cxa_blocks.p = b2;
+ goto StartOver;
+ } else {
+ if (b->next) {
+ b = b->next;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/libc/runtime/defer.greg.c b/libc/runtime/defer.greg.c
index 6128971d2..69a2b3343 100644
--- a/libc/runtime/defer.greg.c
+++ b/libc/runtime/defer.greg.c
@@ -53,10 +53,10 @@ void __defer(struct StackFrame *frame, void *fn, void *arg) {
frame2 = __builtin_frame_address(0);
assert(frame2->next == frame);
assert(PointerNotOwnedByParentStackFrame(frame2, frame, arg));
- if (append(&__garbage,
+ if (append(&g_garbage,
(&(const struct Garbage){frame->next, (intptr_t)fn, (intptr_t)arg,
frame->addr})) != -1) {
- atomic_store(&frame->addr, (intptr_t)&__gc);
+ atomic_store(&frame->addr, (intptr_t)&CollectGarbage);
} else {
abort();
}
diff --git a/libc/runtime/startmain.S b/libc/runtime/executive.S
similarity index 65%
rename from libc/runtime/startmain.S
rename to libc/runtime/executive.S
index 3fdc34226..d19775d11 100644
--- a/libc/runtime/startmain.S
+++ b/libc/runtime/executive.S
@@ -20,91 +20,37 @@
#include "libc/dce.h"
#include "libc/macros.h"
#include "libc/notice.inc"
-#include "libc/runtime/internal.h"
-#include "libc/sysv/consts/map.h"
#include "libc/dce.h"
-#include "libc/runtime/mappings.h"
-#include "libc/sysv/consts/prot.h"
.text.startup
.source __FILE__
-/ Cosmopolitan process entrypoint.
+/ Stack frame that owns process from spawn to exit.
/
-/ @param r12 is argc
-/ @param r13 is argv
-/ @param r14 is environ
-/ @param r15 is auxv
+/ @param edi is argc
+/ @param rsi is argv
+/ @param rdx is environ
+/ @param rcx is auxv
/ @noreturn
-__executive:
- .frame0
+_executive:
+ push %rbp
+ mov %rsp,%rbp
ezlea _base,bx
-
-#ifdef __FAST_MATH__
- call __fast_math
-#endif
- call _init
-#if IsModeDbg()
- call _init # _init() is idempotent
-#endif
-
-/*
-#if !IsTiny()
-/ “Memory obfuscation for glibc, not for we”
-/
-/ 64kb stack w/ 4kb guard alongside tuning in libc/integral/c.inc
-/ e.g. -Werror=frame-larger-than=4096 is intended to guarantee no
-/ stack overflow possible. We like malloc and only cleverly avoid
-/ its use at the lowest levels of the runtime stack, without MMU.
-/ We like this practicee because it's how Google runs production.
- mov $kStackCeiling-STACKSIZE,%rdi
- mov $STACKSIZE,%esi
- mov $PROT_READ|PROT_WRITE,%edx
- mov MAP_ANONYMOUS,%ecx
- or MAP_FIXED,%ecx
- or MAP_PRIVATE,%ecx
- mov $-1,%r8d
- xor %r9d,%r9d
- call mmap
- cmp $-1,%eax
- je abort
- lea STACKSIZE(%rax),%rsp
- xor %ebp,%ebp
- mov %rax,%rdi
- mov $PAGESIZE,%esi
- mov $PROT_NONE,%edx
- call mprotect
- cmp $-1,%eax
- je abort
-#endif
-*/
-
- orl $RUNSTATE_INITIALIZED,g_runstate(%rip)
- ezlea __init_array_start,ax # static ctors in forward order
- .weak __init_array_start # could be called multiple times
- ezlea __init_array_end,cx # idempotency recommended
- .weak __init_array_end # @see ape/ape.lds
-1: cmp %rax,%rcx
- je 2f
- push %rax
- push %rcx
- call *(%rax)
- pop %rcx
- pop %rax
- add $8,%rax
- jmp 1b
-2: nop
-#if !IsTrustworthy()
- mov $PROT_READ,%edi
- call __piro
-#endif
+ mov %edi,%r12d
+ mov %rsi,%r13
+ mov %rdx,%r14
+ mov %rcx,%r15
+ call _spawn
mov %r12,%rdi
mov %r13,%rsi
mov %r14,%rdx
+ mov %r15,%rcx
.weak main
call main
mov %eax,%edi
call exit
- .endfn __executive,weak,hidden
+9: .endfn _executive,weak,hidden
+
+ ud2
#ifdef __PG__
/ Enables plaintext function tracing if --ftrace flag passed.
diff --git a/libc/runtime/exit.S b/libc/runtime/exit.S
new file mode 100644
index 000000000..31bc9988c
--- /dev/null
+++ b/libc/runtime/exit.S
@@ -0,0 +1,39 @@
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/macros.h"
+.text.exit
+.source __FILE__
+
+/ Exits program with grace.
+/
+/ @param %dil has exit code
+/ @noreturn
+exit: push %rbp
+ mov %rsp,%rbp
+ push %rdi
+ push %rdi
+ xor %edi,%edi
+ call __cxa_finalize
+ pop %rdi
+ pop %rdi
+ call _Exit
+ .endfn exit,globl
+
+ ud2
diff --git a/libc/runtime/ezmap.c b/libc/runtime/ezmap.c
index 2ea74b298..311bee8bb 100644
--- a/libc/runtime/ezmap.c
+++ b/libc/runtime/ezmap.c
@@ -17,6 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/bits/pushpop.h"
#include "libc/bits/safemacros.h"
#include "libc/calls/calls.h"
#include "libc/limits.h"
@@ -48,11 +49,10 @@ int mapfileread(const char *filename, struct MappedFile *mf) {
* Releases resource returned by mapfileread().
*/
int unmapfile(struct MappedFile *mf) {
- int rc = 0;
- rc |= munmap(mf->addr, mf->size);
- rc |= close(mf->fd);
- mf->fd = -1;
- mf->addr = MAP_FAILED;
- mf->size = 0;
+ int rc;
+ rc = 0;
+ rc |= munmap_s(&mf->addr, mf->size);
+ rc |= close_s(&mf->fd);
+ pushmov(&mf->size, 0);
return rc;
}
diff --git a/libc/runtime/ezmap.h b/libc/runtime/ezmap.h
index 9db11de4d..f39cfc7c2 100644
--- a/libc/runtime/ezmap.h
+++ b/libc/runtime/ezmap.h
@@ -23,13 +23,13 @@
COSMOPOLITAN_C_START_
struct MappedFile {
- int64_t fd;
+ int fd;
void *addr;
size_t size;
};
-int mapfileread(const char *filename, struct MappedFile *mf) hidden;
-int unmapfile(struct MappedFile *mf) hidden;
+int mapfileread(const char *, struct MappedFile *) hidden;
+int unmapfile(struct MappedFile *) hidden;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/runtime/findmemoryinterval.c b/libc/runtime/findmemoryinterval.c
new file mode 100644
index 000000000..0efc6e31a
--- /dev/null
+++ b/libc/runtime/findmemoryinterval.c
@@ -0,0 +1,36 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/runtime/memtrack.h"
+
+unsigned FindMemoryInterval(const struct MemoryIntervals *mm, int x) {
+ unsigned l, m, r;
+ l = 0;
+ r = mm->i;
+ while (l < r) {
+ m = (l + r) >> 1;
+ if (mm->p[m].x < x) {
+ l = m + 1;
+ } else {
+ r = m;
+ }
+ }
+ if (l && mm->p[l - 1].y >= x) --l;
+ return l;
+}
diff --git a/libc/runtime/free_s.c b/libc/runtime/free_s.c
index 5562e438b..29b55cc7f 100644
--- a/libc/runtime/free_s.c
+++ b/libc/runtime/free_s.c
@@ -19,7 +19,6 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/bits.h"
#include "libc/mem/mem.h"
-#include "libc/runtime/mappings.h"
#include "libc/runtime/runtime.h"
/**
diff --git a/libc/runtime/ftrace.greg.c b/libc/runtime/ftrace.greg.c
index 5907e9f16..79a9e660b 100644
--- a/libc/runtime/ftrace.greg.c
+++ b/libc/runtime/ftrace.greg.c
@@ -98,116 +98,14 @@ privileged interruptfn void ftrace_hook(void) {
RESTORE_RBX();
}
-/**
- * Rewrites code in memory to log function calls.
- *
- * We do this by searching each function for the nop instruction
- * inserted by GCC when we use the -pg -mnop-mcount flags. There's no
- * risk of corrupting data since the linker scripts won't mix code and
- * data.
- *
- * Modules built with -O3 and without the profiling flags might have
- * these same nop instructions, but that shouldn't be problematic since
- * they're only there for the puposes of aligning jumps, and therefore
- * aren't actually executed. However codebases that use huge function
- * alignments with wide-nop slides could pose minor issues. Further note
- * that Cosmopolitan sources are almost never intentionally written to
- * use code alignment, since we've only seen a few cases where it helps.
- *
- * @see ape/ape.lds
- */
-privileged void ftrace_install(void) {
- /* TODO(jart): Is -fschedule-insns2 so aggro that we need XED here? */
- size_t i;
- intptr_t addr;
- sigset_t oldmask;
- uint64_t code, mcode;
- unsigned char *p, *pe;
- const intptr_t kMcount = (intptr_t)&mcount;
- const intptr_t kFtraceHook = (intptr_t)&ftrace_hook;
- const intptr_t kProgramCodeStart = (intptr_t)&_ereal;
- const intptr_t kPrivilegedStart = (intptr_t)&__privileged_start;
- const bool kIsBinaryAligned = !(kPrivilegedStart & (PAGESIZE - 1));
- g_buf[0] = '+';
- g_buf[1] = ' ';
- sigprocmask(SIG_BLOCK, &kSigsetFull, &oldmask);
- if (__mprotect((void *)g_symbols->addr_base,
- kPrivilegedStart - g_symbols->addr_base,
- kIsBinaryAligned ? PROT_READ | PROT_WRITE
- : PROT_READ | PROT_WRITE | PROT_EXEC) != -1) {
- for (i = 0; i < g_symbols->count - 1; ++i) {
- if (g_symbols->addr_base + g_symbols->symbols[i].addr_rva <
- kProgramCodeStart) {
- continue; /* skip over real mode symbols */
- }
- if (g_symbols->addr_base + g_symbols->symbols[i].addr_rva >=
- kPrivilegedStart) {
- break; /* stop before privileged symbols */
- }
- for (p = (unsigned char *)(g_symbols->addr_base +
- g_symbols->symbols[i].addr_rva),
- pe = (unsigned char *)(g_symbols->addr_base +
- g_symbols->symbols[i + 1].addr_rva);
- p < pe - 8; ++p) {
- code = read64le(p);
-
- /*
- * Test for -mrecord-mcount (w/ -fpie or -fpic)
- *
- * nopw 0x00(%rax,%rax,1) ← morphed by package.com
- * call *mcount(%rip) ← linked w/o -static
- * addr32 call mcount ← relaxed w/ -static
- * addr32 call mcount ← relaxed w/ -static
- *
- * Note that gcc refuses to insert the six byte nop.
- */
- if ((code & 0x0000FFFFFFFFFFFF) == 0x0000441F0F66 ||
- (code & 0x0000FFFFFFFFFFFF) ==
- ((((kMcount - ((intptr_t)&p[2] + 4)) << 16) | 0xE867) &
- 0x0000FFFFFFFFFFFF) ||
- (code & 0x0000FFFFFFFFFFFF) ==
- ((((kMcount - ((intptr_t)&p[2] + 4)) << 16) | 0xFF15) &
- 0x0000FFFFFFFFFFFF)) {
- p[0] = 0x67;
- p[1] = 0xE8;
- addr = kFtraceHook - ((intptr_t)&p[2] + 4);
- p[2] = addr >> 000;
- p[3] = addr >> 010;
- p[4] = addr >> 020;
- p[5] = addr >> 030;
- break;
- }
-
- /*
- * Test for -mnop-mcount (w/ -fno-pie)
- */
- mcode = code & 0x000000FFFFFFFFFF;
- if ((mcode == 0x00441F0F /* nopl 0x00(%eax,%eax,1) [canonical] */) ||
- (mcode == 0x00041F0F67 /* nopl (%eax,%eax,1) [older gcc] */)) {
- if (p[-1] != 0x66 /* nopw 0x0(%rax,%rax,1) [donotwant] */) {
- p[0] = 0xE8 /* call Jvds */;
- addr = kFtraceHook - ((intptr_t)&p[1] + 4);
- p[1] = addr >> 000;
- p[2] = addr >> 010;
- p[3] = addr >> 020;
- p[4] = addr >> 030;
- }
- break;
- }
- }
- }
- __mprotect((void *)g_symbols->addr_base,
- kPrivilegedStart - g_symbols->addr_base, PROT_READ | PROT_EXEC);
- }
- sigprocmask(SIG_SETMASK, &oldmask, NULL);
-}
-
/**
* Installs plaintext function tracer. Do not call.
* @see libc/runtime/_init.S for documentation
*/
textstartup void ftrace_init(void) {
+ g_buf[0] = '+';
+ g_buf[1] = ' ';
if ((g_symbols = opensymboltable(finddebugbinary()))) {
- ftrace_install();
+ __hook(ftrace_hook, g_symbols);
}
}
diff --git a/libc/runtime/gc.h b/libc/runtime/gc.h
index e054d587a..9c28da2d6 100644
--- a/libc/runtime/gc.h
+++ b/libc/runtime/gc.h
@@ -21,7 +21,7 @@ struct StackFrame;
* @warning do not return a gc()'d pointer
* @warning do not realloc() with gc()'d pointer
*/
-#define gc(THING) defer(weakfree, (THING))
+#define gc(THING) defer((void *)weakfree, (void *)(THING))
/**
* Same as longjmp() but runs gc() / defer() destructors.
@@ -31,14 +31,14 @@ void gclongjmp(jmp_buf, int) nothrow noreturn paramsnonnull();
/**
* Calls FN(ARG) when function returns.
*/
-#define defer(FN, ARG) \
- ({ \
- autotype(ARG) Arg = (ARG); \
- /* prevent weird opts like tail call */ \
- asm volatile("" : "+g"(Arg) : : "memory"); \
- __defer(__builtin_frame_address(0), FN, Arg); \
- asm volatile("" : "+g"(Arg) : : "memory"); \
- Arg; \
+#define defer(FN, ARG) \
+ ({ \
+ autotype(ARG) Arg = (ARG); \
+ /* prevent weird opts like tail call */ \
+ asm volatile("" : "+g"(Arg) : : "memory"); \
+ __defer((struct StackFrame *)__builtin_frame_address(0), FN, Arg); \
+ asm volatile("" : "+g"(Arg) : : "memory"); \
+ Arg; \
})
void __defer(struct StackFrame *, void *, void *) hidden paramsnonnull((1, 2));
diff --git a/libc/runtime/getdosargv.c b/libc/runtime/getdosargv.c
index 1ec8a435d..c160b7f07 100644
--- a/libc/runtime/getdosargv.c
+++ b/libc/runtime/getdosargv.c
@@ -17,6 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/assert.h"
#include "libc/bits/bits.h"
#include "libc/bits/pushpop.h"
#include "libc/bits/safemacros.h"
@@ -38,11 +39,11 @@ struct DosArgv {
wint_t wc;
};
-static inline textwindows void decodedosargv(struct DosArgv *st) {
+static textwindows void decodedosargv(struct DosArgv *st) {
st->s += getutf16(st->s, &st->wc);
}
-static inline textwindows void appenddosargv(struct DosArgv *st, wint_t wc) {
+static textwindows void appenddosargv(struct DosArgv *st, wint_t wc) {
AppendChar(&st->p, st->pe, wc);
}
diff --git a/libc/runtime/getdosenviron.h b/libc/runtime/getdosenviron.h
index ea5d1c6bd..8d09aedf6 100644
--- a/libc/runtime/getdosenviron.h
+++ b/libc/runtime/getdosenviron.h
@@ -19,6 +19,7 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#ifndef COSMOPOLITAN_LIBC_DOSENVIRON_H_
#define COSMOPOLITAN_LIBC_DOSENVIRON_H_
+#ifndef __STRICT_ANSI__
#include "libc/bits/safemacros.h"
#include "libc/str/appendchar.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
@@ -64,4 +65,5 @@ static inline int getdosenviron(const char16_t *env, char *buf, size_t size,
}
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_DOSENVIRON_H_ */
diff --git a/libc/runtime/grow.c b/libc/runtime/grow.c
index 85c883024..0ae68588e 100644
--- a/libc/runtime/grow.c
+++ b/libc/runtime/grow.c
@@ -20,11 +20,11 @@
#include "libc/assert.h"
#include "libc/bits/bits.h"
#include "libc/bits/safemacros.h"
+#include "libc/bits/weaken.h"
#include "libc/conv/conv.h"
#include "libc/conv/sizemultiply.h"
#include "libc/macros.h"
#include "libc/mem/mem.h"
-#include "libc/runtime/mappings.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/errfuns.h"
diff --git a/libc/runtime/hook.greg.c b/libc/runtime/hook.greg.c
new file mode 100644
index 000000000..a1820e595
--- /dev/null
+++ b/libc/runtime/hook.greg.c
@@ -0,0 +1,125 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/calls.h"
+#include "libc/calls/internal.h"
+#include "libc/calls/struct/sigset.h"
+#include "libc/runtime/runtime.h"
+#include "libc/runtime/symbols.h"
+#include "libc/sysv/consts/prot.h"
+
+/**
+ * Rewrites code in memory to hook function calls.
+ *
+ * We do this by searching each function for the nop instruction
+ * inserted by GCC when we use the -pg -mnop-mcount flags. There's no
+ * risk of corrupting data since the linker scripts won't mix code and
+ * data.
+ *
+ * Modules built with -O3 and without the profiling flags might have
+ * these same nop instructions, but that shouldn't be problematic since
+ * they're only there for the puposes of aligning jumps, and therefore
+ * aren't actually executed. However codebases that use huge function
+ * alignments with wide-nop slides could pose minor issues. Further note
+ * that Cosmopolitan sources are almost never intentionally written to
+ * use code alignment, since we've only seen a few cases where it helps.
+ *
+ * @see ape/ape.lds
+ */
+privileged void __hook(void ifunc(void), struct SymbolTable *symbols) {
+ size_t i;
+ intptr_t addr;
+ sigset_t oldmask;
+ uint64_t code, mcode;
+ unsigned char *p, *pe;
+ const intptr_t kMcount = (intptr_t)&mcount;
+ const intptr_t kProgramCodeStart = (intptr_t)&_ereal;
+ const intptr_t kPrivilegedStart = (intptr_t)&__privileged_start;
+ const bool kIsBinaryAligned = !(kPrivilegedStart & (PAGESIZE - 1));
+ sigprocmask(SIG_BLOCK, &kSigsetFull, &oldmask);
+ if (mprotect((void *)symbols->addr_base,
+ kPrivilegedStart - symbols->addr_base,
+ kIsBinaryAligned ? PROT_READ | PROT_WRITE
+ : PROT_READ | PROT_WRITE | PROT_EXEC) != -1) {
+ for (i = 0; i < symbols->count - 1; ++i) {
+ if (symbols->addr_base + symbols->symbols[i].addr_rva <
+ kProgramCodeStart) {
+ continue; /* skip over real mode symbols */
+ }
+ if (symbols->addr_base + symbols->symbols[i].addr_rva >=
+ kPrivilegedStart) {
+ break; /* stop before privileged symbols */
+ }
+ for (p = (unsigned char *)(symbols->addr_base +
+ symbols->symbols[i].addr_rva),
+ pe = (unsigned char *)(symbols->addr_base +
+ symbols->symbols[i + 1].addr_rva);
+ p < pe - 8; ++p) {
+ code = read64le(p);
+
+ /*
+ * Test for -mrecord-mcount (w/ -fpie or -fpic)
+ *
+ * nopw 0x00(%rax,%rax,1) ← morphed by package.com
+ * call *mcount(%rip) ← linked w/o -static
+ * addr32 call mcount ← relaxed w/ -static
+ * addr32 call mcount ← relaxed w/ -static
+ *
+ * Note that gcc refuses to insert the six byte nop.
+ */
+ if ((code & 0x0000FFFFFFFFFFFF) == 0x0000441F0F66 ||
+ (code & 0x0000FFFFFFFFFFFF) ==
+ ((((kMcount - ((intptr_t)&p[2] + 4)) << 16) | 0xE867) &
+ 0x0000FFFFFFFFFFFF) ||
+ (code & 0x0000FFFFFFFFFFFF) ==
+ ((((kMcount - ((intptr_t)&p[2] + 4)) << 16) | 0xFF15) &
+ 0x0000FFFFFFFFFFFF)) {
+ p[0] = 0x67;
+ p[1] = 0xE8;
+ addr = (intptr_t)ifunc - ((intptr_t)&p[2] + 4);
+ p[2] = addr >> 000;
+ p[3] = addr >> 010;
+ p[4] = addr >> 020;
+ p[5] = addr >> 030;
+ break;
+ }
+
+ /*
+ * Test for -mnop-mcount (w/ -fno-pie)
+ */
+ mcode = code & 0x000000FFFFFFFFFF;
+ if ((mcode == 0x00441F0F /* nopl 0x00(%eax,%eax,1) [canonical] */) ||
+ (mcode == 0x00041F0F67 /* nopl (%eax,%eax,1) [older gcc] */)) {
+ if (p[-1] != 0x66 /* nopw 0x0(%rax,%rax,1) [donotwant] */) {
+ p[0] = 0xE8 /* call Jvds */;
+ addr = (intptr_t)ifunc - ((intptr_t)&p[1] + 4);
+ p[1] = addr >> 000;
+ p[2] = addr >> 010;
+ p[3] = addr >> 020;
+ p[4] = addr >> 030;
+ }
+ break;
+ }
+ }
+ }
+ mprotect((void *)symbols->addr_base, kPrivilegedStart - symbols->addr_base,
+ PROT_READ | PROT_EXEC);
+ }
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+}
diff --git a/libc/runtime/internal.h b/libc/runtime/internal.h
index d9968d62b..486742643 100644
--- a/libc/runtime/internal.h
+++ b/libc/runtime/internal.h
@@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_RUNTIME_INTERNAL_H_
#define COSMOPOLITAN_LIBC_RUNTIME_INTERNAL_H_
+#ifndef __STRICT_ANSI__
#include "libc/dce.h"
#include "libc/runtime/runtime.h"
@@ -18,11 +19,12 @@ hidden extern unsigned g_runstate;
hidden extern void *g_stacktop;
void _init(void) hidden;
-void __piro(int) hidden;
+void _piro(int) hidden;
void *__cxa_finalize(void *) hidden;
-int getdosargv(const char16_t *, char *, size_t, char **, size_t) hidden;
+void _executive(int, char **, char **, long (*)[2]) hidden noreturn;
void __stack_chk_fail(void) noreturn relegated;
void __stack_chk_fail_local(void) noreturn relegated hidden;
+int getdosargv(const char16_t *, char *, size_t, char **, size_t) hidden;
forceinline void AssertNeverCalledWhileTerminating(void) {
if (!NoDebug() && (g_runstate & RUNSTATE_TERMINATE)) {
@@ -32,4 +34,5 @@ forceinline void AssertNeverCalledWhileTerminating(void) {
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* ANSI */
#endif /* COSMOPOLITAN_LIBC_RUNTIME_INTERNAL_H_ */
diff --git a/libc/runtime/isheap.c b/libc/runtime/isheap.c
index c83e6b7a7..3ff2f0ce1 100644
--- a/libc/runtime/isheap.c
+++ b/libc/runtime/isheap.c
@@ -17,22 +17,20 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/runtime/mappings.h"
+#include "libc/runtime/memtrack.h"
+#include "libc/runtime/runtime.h"
/**
- * Returns false if address can't be heap memory.
+ * Returns true if address isn't stack and was malloc'd or mmap'd.
+ *
+ * @assume stack addresses are always greater than heap addresses
+ * @assume stack memory isn't stored beneath %rsp (-mno-red-zone)
*/
bool isheap(void *p) {
- size_t i;
- struct MemoryCoord c;
- if (!(kStackBottom <= (intptr_t)p && (intptr_t)p < kStackCeiling)) {
- c = ADDRSIZE_TO_COORD(p, FRAMESIZE);
- if ((i = findmapping(c.x))) {
- return ISOVERLAPPING(_mm.p[i - 1], c);
- } else {
- return false;
- }
- } else {
- return false;
- }
+ int x, i;
+ register intptr_t rsp asm("rsp");
+ if ((intptr_t)p >= rsp) return false;
+ x = (intptr_t)p >> 16;
+ i = FindMemoryInterval(&_mmi, x);
+ return i < _mmi.i && x >= _mmi.p[i].x && x <= _mmi.p[i].y;
}
diff --git a/libc/runtime/mapanon.c b/libc/runtime/mapanon.c
index e32c4814c..504080aa7 100644
--- a/libc/runtime/mapanon.c
+++ b/libc/runtime/mapanon.c
@@ -22,7 +22,7 @@
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/prot.h"
-void *__mapanon(size_t mapsize) {
+void *mapanon(size_t mapsize) {
return mmap(NULL, mapsize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_NONBLOCK | MAP_ANONYMOUS, -1, 0);
}
diff --git a/libc/runtime/mappings.h b/libc/runtime/mappings.h
deleted file mode 100644
index c8814b1ad..000000000
--- a/libc/runtime/mappings.h
+++ /dev/null
@@ -1,78 +0,0 @@
-#ifndef COSMOPOLITAN_LIBC_RUNTIME_MAPPINGS_H_
-#define COSMOPOLITAN_LIBC_RUNTIME_MAPPINGS_H_
-#include "libc/dce.h"
-#include "libc/macros.h"
-#include "libc/runtime/runtime.h"
-
-#define MMAP_MAX 300 /* TODO: crunch */
-
-#define kStackCeiling 0x0000700000000000L
-#define kStackBottom 0x0000600000000000L
-
-#define kFixedMappingsStart 0x0000100000000000L /* cosmo won't auto-assign */
-#define kFixedMappingsSize 0x0000100000000000L /* 16TB */
-
-#define kMappingsStart 0x0000200000000000L /* cosmo auto-assigns here */
-#define kMappingsSize 0x0000100000000000L /* 16TB */
-
-#if !(__ASSEMBLER__ + __LINKER__ + 0)
-COSMOPOLITAN_C_START_
-
-#define ISOVERLAPPING(C1, C2) \
- (((C1).x >= (C2).x && (C1).x <= (C2).y) || \
- ((C1).y >= (C2).x && (C1).y <= (C2).y))
-
-#define ADDR_TO_COORD(ADDR) \
- (int)(((intptr_t)(ADDR) & ~(FRAMESIZE - 1)) / FRAMESIZE)
-
-#define COORD_TO_ADDR(COORD) (void *)((intptr_t)(COORD)*FRAMESIZE)
-#define COORD_TO_SIZE(COORD) (void *)((intptr_t)(COORD)*FRAMESIZE)
-
-#define ADDRSIZE_TO_COORD(ADDR, SIZE) \
- ((struct MemoryCoord){ \
- .x = ADDR_TO_COORD(ADDR), \
- .y = ADDR_TO_COORD(ADDR) + \
- ((unsigned)(ROUNDUP((SIZE), FRAMESIZE) / FRAMESIZE) - 1)})
-
-#define COORD_TO_ADDRSIZE(COORD) \
- ((struct AddrSize){ \
- .addr = COORD_TO_ADDR((COORD).x), \
- .size = ((size_t)((COORD).y - (COORD).x + 1) * FRAMESIZE)})
-
-#define GRANULATE_ADDRSIZE(ADDR, SIZE) \
- do { \
- struct AddrSize AdSiz; \
- struct MemoryCoord MemCo; \
- MemCo = ADDRSIZE_TO_COORD(*(ADDR), *(SIZE)); \
- AdSiz = COORD_TO_ADDRSIZE(MemCo); \
- *(ADDR) = AdSiz.addr; \
- *(SIZE) = AdSiz.size; \
- } while (0)
-
-struct AddrSize {
- void *addr;
- size_t size;
-};
-
-/**
- * Ordered inclusive 64kb-granular ranges on NexGen32e w/o PML5.
- * c.𝑥 ≤ c.𝑦 so say c all.
- * cₙ.𝑥 ≤ cₙ₊₁.𝑥 so say c all.
- */
-struct Mappings {
- size_t i;
- struct MemoryCoord {
- int32_t x, y;
- } p[MMAP_MAX];
- int64_t h[MMAP_MAX];
-};
-
-extern struct Mappings _mm;
-
-bool isheap(void *);
-size_t findmapping(int32_t);
-size_t findmapping_(int32_t, const struct MemoryCoord *, size_t);
-
-COSMOPOLITAN_C_END_
-#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#endif /* COSMOPOLITAN_LIBC_RUNTIME_MAPPINGS_H_ */
diff --git a/libc/runtime/memtrack.c b/libc/runtime/memtrack.c
new file mode 100644
index 000000000..33fc2944b
--- /dev/null
+++ b/libc/runtime/memtrack.c
@@ -0,0 +1,117 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/assert.h"
+#include "libc/macros.h"
+#include "libc/runtime/memtrack.h"
+#include "libc/runtime/runtime.h"
+#include "libc/str/str.h"
+#include "libc/sysv/errfuns.h"
+
+static void RemoveMemoryIntervals(struct MemoryIntervals *mm, int i, int n) {
+ assert(i >= 0);
+ assert(i + n <= mm->i);
+ memcpy(mm->p + i, mm->p + i + n,
+ (intptr_t)(mm->p + mm->i) - (intptr_t)(mm->p + i + n));
+ memcpy(mm->h + i, mm->h + i + n,
+ (intptr_t)(mm->h + mm->i) - (intptr_t)(mm->h + i + n));
+ mm->i -= n;
+}
+
+static void CreateMemoryInterval(struct MemoryIntervals *mm, int i) {
+ assert(i >= 0);
+ assert(i <= mm->i);
+ assert(mm->i < ARRAYLEN(mm->p));
+ memmove(mm->p + i + 1, mm->p + i,
+ (intptr_t)(mm->p + mm->i) - (intptr_t)(mm->p + i));
+ memmove(mm->h + i + 1, mm->h + i,
+ (intptr_t)(mm->h + mm->i) - (intptr_t)(mm->h + i));
+ ++mm->i;
+}
+
+static int PunchHole(struct MemoryIntervals *mm, int x, int y, int i) {
+ if (mm->i == ARRAYLEN(mm->p)) return enomem();
+ CreateMemoryInterval(mm, i);
+ mm->p[i].y = x - 1;
+ mm->p[i + 1].x = y + 1;
+ return 0;
+}
+
+int ReleaseMemoryIntervals(struct MemoryIntervals *mm, int x, int y,
+ void wincb(struct MemoryIntervals *, int, int)) {
+ unsigned l, r;
+ assert(y >= x);
+ assert(AreMemoryIntervalsOk(mm));
+ if (!mm->i) return 0;
+ l = FindMemoryInterval(mm, x);
+ if (l == mm->i) return 0;
+ if (!l && y < mm->p[l].x) return 0;
+ if (y < mm->p[l].x) return 0;
+ r = FindMemoryInterval(mm, y);
+ if (r == mm->i || (r > l && y < mm->p[r].x)) --r;
+ assert(r >= l);
+ assert(x <= mm->p[r].y);
+ if (l == r && x > mm->p[l].x && y < mm->p[l].y) {
+ return PunchHole(mm, x, y, l);
+ }
+ if (x > mm->p[l].x && x <= mm->p[l].y) {
+ assert(y >= mm->p[l].y);
+ if (IsWindows()) return einval();
+ mm->p[l].y = x - 1;
+ assert(mm->p[l].x <= mm->p[l].y);
+ ++l;
+ }
+ if (y >= mm->p[r].x && y < mm->p[r].y) {
+ assert(x <= mm->p[r].x);
+ if (IsWindows()) return einval();
+ mm->p[r].x = y + 1;
+ assert(mm->p[r].x <= mm->p[r].y);
+ --r;
+ }
+ if (l <= r) {
+ if (IsWindows() && wincb) {
+ wincb(mm, l, r);
+ }
+ RemoveMemoryIntervals(mm, l, r - l + 1);
+ }
+ return 0;
+}
+
+int TrackMemoryInterval(struct MemoryIntervals *mm, int x, int y, long h) {
+ unsigned i;
+ assert(y >= x);
+ assert(AreMemoryIntervalsOk(mm));
+ i = FindMemoryInterval(mm, x);
+ if (i && x == mm->p[i - 1].y + 1 && h == mm->h[i - 1]) {
+ mm->p[i - 1].y = y;
+ if (i < mm->i && y + 1 == mm->p[i].x && h == mm->h[i]) {
+ mm->p[i - 1].y = mm->p[i].y;
+ RemoveMemoryIntervals(mm, i, 1);
+ }
+ } else if (i < mm->i && y + 1 == mm->p[i].x && h == mm->h[i]) {
+ mm->p[i].x = x;
+ } else {
+ if (mm->i == ARRAYLEN(mm->p)) return enomem();
+ CreateMemoryInterval(mm, i);
+ mm->p[i].x = x;
+ mm->p[i].y = y;
+ mm->h[i] = h;
+ }
+ return 0;
+}
diff --git a/libc/runtime/memtrack.h b/libc/runtime/memtrack.h
new file mode 100644
index 000000000..507ab7682
--- /dev/null
+++ b/libc/runtime/memtrack.h
@@ -0,0 +1,33 @@
+#ifndef COSMOPOLITAN_LIBC_RUNTIME_MEMTRACK_H_
+#define COSMOPOLITAN_LIBC_RUNTIME_MEMTRACK_H_
+#include "libc/nexgen32e/vendor.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+#define kMappingsSize 0x100000000000 /* 16TB */
+#define kMappingsStart (IsGenuineCosmo() ? 0x300000000000 : 0x200000000000)
+#define kFixedMappingsStart 0x0000100000000000
+#define kFixedMappingsSize kMappingsSize
+
+struct MemoryIntervals {
+ int i;
+ struct MemoryInterval {
+ int x;
+ int y;
+ } p[32];
+ long h[32];
+};
+
+extern struct MemoryIntervals _mmi;
+
+unsigned FindMemoryInterval(const struct MemoryIntervals *, int) nosideeffect;
+bool AreMemoryIntervalsOk(const struct MemoryIntervals *) nosideeffect;
+void PrintMemoryIntervals(int, const struct MemoryIntervals *);
+int TrackMemoryInterval(struct MemoryIntervals *, int, int, long);
+int ReleaseMemoryIntervals(struct MemoryIntervals *, int, int,
+ void (*)(struct MemoryIntervals *, int, int));
+void ReleaseMemoryNt(struct MemoryIntervals *, int, int);
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_RUNTIME_MEMTRACK_H_ */
diff --git a/libc/runtime/memtracknt.c b/libc/runtime/memtracknt.c
new file mode 100644
index 000000000..eb738e752
--- /dev/null
+++ b/libc/runtime/memtracknt.c
@@ -0,0 +1,41 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/assert.h"
+#include "libc/nt/memory.h"
+#include "libc/nt/runtime.h"
+#include "libc/runtime/memtrack.h"
+#include "libc/runtime/runtime.h"
+
+static void *GetFrameAddr(int f) {
+ intptr_t a;
+ a = f;
+ a *= FRAMESIZE;
+ return (void *)a;
+}
+
+void ReleaseMemoryNt(struct MemoryIntervals *mm, int l, int r) {
+ int i, ok;
+ for (i = l; i <= r; ++i) {
+ ok = UnmapViewOfFile(GetFrameAddr(mm->p[i].x));
+ assert(ok);
+ ok = CloseHandle(mm->h[i]);
+ assert(ok);
+ }
+}
diff --git a/libc/runtime/missioncritical.h b/libc/runtime/missioncritical.h
index 047e0718a..fe437a703 100644
--- a/libc/runtime/missioncritical.h
+++ b/libc/runtime/missioncritical.h
@@ -1,9 +1,11 @@
#ifndef COSMOPOLITAN_LIBC_INTERNAL_MISSIONCRITICAL_H_
#define COSMOPOLITAN_LIBC_INTERNAL_MISSIONCRITICAL_H_
+#ifndef __STRICT_ANSI__
#include "libc/bits/bits.h"
#include "libc/calls/internal.h"
#include "libc/dce.h"
#include "libc/nexgen32e/nexgen32e.h"
+#include "libc/nexgen32e/tinystrlen.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/version.h"
#include "libc/nt/ntdll.h"
@@ -140,4 +142,5 @@ interruptfn void __print(const void *, size_t);
#define RESTORE_RBX() /* disabled for now b/c clang */
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_INTERNAL_MISSIONCRITICAL_H_ */
diff --git a/libc/runtime/mmap.c b/libc/runtime/mmap.c
index b9ed6d284..5dc21f38c 100644
--- a/libc/runtime/mmap.c
+++ b/libc/runtime/mmap.c
@@ -24,22 +24,33 @@
#include "libc/macros.h"
#include "libc/nt/memory.h"
#include "libc/nt/runtime.h"
-#include "libc/runtime/mappings.h"
+#include "libc/rand/rand.h"
+#include "libc/runtime/memtrack.h"
+#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/map.h"
+#include "libc/sysv/consts/prot.h"
#include "libc/sysv/errfuns.h"
-#define VIP(X) (void *)(intptr_t)(X)
+#define IP(X) (intptr_t)(X)
+#define VIP(X) (void *)IP(X)
+#define COORD(a) (int)(IP(a) >> 16)
+#define ADDR(c) (void *)(IP(c) << 16)
+#define ALIGNED(p) (!(IP(p) & (FRAMESIZE - 1)))
+#define CANONICAL(p) (-0x800000000000 <= IP(p) && IP(p) <= 0x7fffffffffff)
+#define LAST_COORD(a, n) (COORD(a) + (ROUNDUP(n, FRAMESIZE) >> 16) - 1)
struct DirectMap {
void *addr;
int64_t maphandle;
};
-static textwindows struct DirectMap directmap$nt(void *addr, size_t size,
- unsigned prot, unsigned flags,
- int fd, int64_t off) {
- struct DirectMap res;
+struct MemoryIntervals _mmi;
+
+static textwindows struct DirectMap DirectMapNt(void *addr, size_t size,
+ unsigned prot, unsigned flags,
+ int fd, int64_t off) {
+ struct DirectMap res; /* NT IS TORTURE */
if ((res.maphandle = CreateFileMappingNuma(
fd != -1 ? g_fds.p[fd].handle : kNtInvalidHandleValue,
&kNtIsInheritable, prot2nt(prot, flags), size >> 32, size, NULL,
@@ -58,22 +69,48 @@ static textwindows struct DirectMap directmap$nt(void *addr, size_t size,
return res;
}
-static struct DirectMap directmap(void *addr, size_t size, unsigned prot,
+static struct DirectMap DirectMap(void *addr, size_t size, unsigned prot,
unsigned flags, int fd, int64_t off) {
if (!IsWindows()) {
return (struct DirectMap){mmap$sysv(addr, size, prot, flags, fd, off),
kNtInvalidHandleValue};
} else {
- return directmap$nt(addr, size, prot, flags, fd, off);
+ return DirectMapNt(addr, size, prot, flags, fd, off);
}
}
+static int UntrackMemoryIntervals(void *addr, size_t size) {
+ return ReleaseMemoryIntervals(&_mmi, COORD(addr), LAST_COORD(addr, size),
+ ReleaseMemoryNt);
+}
+
+/**
+ * Releases memory pages.
+ *
+ * @param addr is a pointer within any memory mapped region the process
+ * has permission to control, such as address ranges returned by
+ * mmap(), the program image itself, etc.
+ * @param size is the amount of memory to unmap, which needn't be a
+ * multiple of FRAMESIZE, and may be a subset of that which was
+ * mapped previously, and may punch holes in existing mappings,
+ * but your mileage may vary on windows
+ * @return 0 on success, or -1 w/ errno
+ */
+int munmap(void *addr, size_t size) {
+ int rc;
+ if (!ALIGNED(addr) || !CANONICAL(addr) || !size) return einval();
+ size = ROUNDUP(size, FRAMESIZE);
+ if (UntrackMemoryIntervals(addr, size) == -1) return -1;
+ if (IsWindows()) return 0;
+ return munmap$sysv(addr, size);
+}
+
/**
* Beseeches system for page-table entries.
*
* @param addr optionally requests a particular virtual base address,
* which needs to be 64kb aligned if passed (for NT compatibility)
- * @param size should be >0 and multiple of PAGESIZE
+ * @param size must be >0 and needn't be a multiple of FRAMESIZE
* @param prot can have PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONE, etc.
* @param flags can have MAP_ANONYMOUS, MAP_SHARED, MAP_PRIVATE, etc.
* @param fd is an open()'d file descriptor whose contents shall be
@@ -83,73 +120,48 @@ static struct DirectMap directmap(void *addr, size_t size, unsigned prot,
* @return virtual base address of new mapping, or MAP_FAILED w/ errno
*/
void *mmap(void *addr, size_t size, int prot, int flags, int fd, int64_t off) {
- size_t i;
- intptr_t p;
+ int i;
+ long gap;
struct DirectMap dm;
- struct MemoryCoord c;
- p = (intptr_t)addr;
-
- assert(!(0 < p && p < 0x200000));
- assert(-0x800000000000L <= p && p <= 0x7fffffffffffL);
- assert((flags & MAP_PRIVATE) ^ (flags & MAP_SHARED));
- assert((flags & MAP_ANONYMOUS) ^ (fd != -1));
- assert(off % PAGESIZE == 0);
- assert(size > 0);
-
- if (!(IsWindows() && fd != -1)) {
- size = ROUNDUP(size, FRAMESIZE);
- }
-
+ if (!size) return VIP(einval());
+ if (!ALIGNED(off)) return VIP(einval());
+ if (!ALIGNED(addr)) return VIP(einval());
+ if (!CANONICAL(addr)) return VIP(einval());
+ if (!(!!(flags & MAP_ANONYMOUS) ^ (fd != -1))) return VIP(einval());
+ if (!(!!(flags & MAP_PRIVATE) ^ !!(flags & MAP_SHARED))) return VIP(einval());
+ if (!(IsWindows() && fd != -1)) size = ROUNDUP(size, FRAMESIZE);
if (flags & MAP_FIXED) {
- assert(addr != NULL);
- assert((intptr_t)addr % FRAMESIZE == 0);
- } else {
- if (!addr) {
- if (_mm.i) {
- addr = COORD_TO_ADDR(_mm.p[_mm.i - 1].y + 1);
- for (i = _mm.i; i; --i) {
- if (_mm.p[i - 1].y + 1 + size / FRAMESIZE <=
- ADDR_TO_COORD(kMappingsStart + kMappingsSize) &&
- _mm.p[i - 1].y + 1 >= ADDR_TO_COORD(kMappingsStart)) {
- addr = COORD_TO_ADDR(_mm.p[i - 1].y + 1);
- break;
- }
+ if (UntrackMemoryIntervals(addr, size) == -1) {
+ return MAP_FAILED;
+ }
+ } else if (_mmi.i) {
+ if (0 && IsModeDbg()) {
+ addr = VIP(rand64() & 0x00007ffffffff000);
+ } else {
+ for (i = _mmi.i - 1; i > 0; --i) {
+ gap = _mmi.p[i].x - _mmi.p[i - 1].y - 1;
+ assert(gap > 0);
+ if (gap >= (ROUNDUP(size, FRAMESIZE) >> 16)) {
+ addr = ADDR(_mmi.p[i - 1].y + 1);
+ break;
}
- } else {
- addr = VIP(kMappingsStart);
+ }
+ if (!addr) {
+ addr = ADDR(_mmi.p[_mmi.i - 1].y + 1);
}
}
- addr = (void *)ROUNDDOWN((intptr_t)addr, FRAMESIZE);
- }
-
- if (_mm.i == MMAP_MAX) {
- return VIP(enomem());
- }
-
- if (flags & MAP_FIXED) {
- munmap(addr, size);
} else {
- c = ADDRSIZE_TO_COORD(addr, size);
- if ((i = findmapping(c.y)) && ISOVERLAPPING(c, _mm.p[i - 1])) {
- return VIP(einval());
- }
+ addr = VIP(kMappingsStart);
}
-
- dm = directmap(addr, size, prot, flags | MAP_FIXED, fd, off);
- if (dm.addr == MAP_FAILED) return MAP_FAILED;
-
- i = findmapping(ADDR_TO_COORD(dm.addr));
- if (i < _mm.i) {
- memmove(&_mm.p[i + 1], &_mm.p[i],
- (intptr_t)&_mm.p[_mm.i] - (intptr_t)&_mm.p[i]);
- memmove(&_mm.h[i + 1], &_mm.h[i],
- (intptr_t)&_mm.h[_mm.i] - (intptr_t)&_mm.h[i]);
+ assert((flags & MAP_FIXED) ||
+ (!isheap(addr) && !isheap((char *)addr + size - 1)));
+ dm = DirectMap(addr, size, prot, flags | MAP_FIXED, fd, off);
+ if (dm.addr == MAP_FAILED || dm.addr != addr) {
+ return MAP_FAILED;
+ }
+ if (TrackMemoryInterval(&_mmi, COORD(dm.addr), LAST_COORD(dm.addr, size),
+ dm.maphandle) == -1) {
+ _Exit(1);
}
-
- _mm.p[i] = ADDRSIZE_TO_COORD(dm.addr, size);
- _mm.h[i] = dm.maphandle;
- _mm.i++;
-
- assert((intptr_t)dm.addr % __BIGGEST_ALIGNMENT__ == 0);
return dm.addr;
}
diff --git a/libc/runtime/msync-nt.c b/libc/runtime/msync-nt.c
index 55a30cbc9..db32f80ac 100644
--- a/libc/runtime/msync-nt.c
+++ b/libc/runtime/msync-nt.c
@@ -18,19 +18,24 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/internal.h"
+#include "libc/macros.h"
#include "libc/nt/files.h"
#include "libc/nt/memory.h"
-#include "libc/runtime/mappings.h"
+#include "libc/runtime/memtrack.h"
textwindows int msync$nt(void *addr, size_t size, int flags) {
- size_t i, j;
- struct MemoryCoord c;
+ int x, y, l, r, i;
if (!FlushViewOfFile(addr, size)) return winerr();
- j = findmapping(ADDR_TO_COORD(addr));
- c = ADDRSIZE_TO_COORD(addr, size);
- for (i = j; i; --i) {
- if (!ISOVERLAPPING(_mm.p[i - 1], c)) break;
- FlushFileBuffers(_mm.h[i - 1]);
+ x = (intptr_t)addr >> 16;
+ y = x + (ROUNDUP(size, 65536) >> 16) - 1;
+ l = FindMemoryInterval(&_mmi, x);
+ r = FindMemoryInterval(&_mmi, y);
+ if (l && x <= _mmi.p[l - 1].y) --l;
+ if (r && y <= _mmi.p[r - 1].y) --r;
+ if (l < _mmi.i) {
+ for (i = l; i <= r; --i) {
+ FlushFileBuffers(_mmi.h[i - 1]);
+ }
}
return 0;
}
diff --git a/libc/runtime/msync.c b/libc/runtime/msync.c
index f02f1f017..27c53e030 100644
--- a/libc/runtime/msync.c
+++ b/libc/runtime/msync.c
@@ -22,7 +22,6 @@
#include "libc/calls/internal.h"
#include "libc/dce.h"
#include "libc/macros.h"
-#include "libc/runtime/mappings.h"
#include "libc/sysv/consts/msync.h"
/**
diff --git a/libc/runtime/munmap.c b/libc/runtime/munmap.c
deleted file mode 100644
index e7865cc29..000000000
--- a/libc/runtime/munmap.c
+++ /dev/null
@@ -1,106 +0,0 @@
-/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
-│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/assert.h"
-#include "libc/calls/calls.h"
-#include "libc/calls/internal.h"
-#include "libc/dce.h"
-#include "libc/nt/memory.h"
-#include "libc/nt/runtime.h"
-#include "libc/runtime/mappings.h"
-#include "libc/str/str.h"
-#include "libc/sysv/consts/fileno.h"
-
-/**
- * Releases memory pages.
- *
- * @param addr is a pointer within any memory mapped region the process
- * has permission to control, such as address ranges returned by
- * mmap(), the program image itself, etc.
- * @param size is the amount of memory to unmap, which should be a
- * multiple of PAGESIZE, and may be a subset of that which was
- * mapped previously
- * @return 0 on success, or -1 w/ errno
- */
-int __munmap(void *addr, size_t size) {
- int rc;
- intptr_t p;
- size_t i, j;
- struct AddrSize a;
- struct MemoryCoord c, m;
-
- p = (intptr_t)addr;
- assert(!(0 < p && p < 0x200000));
- assert(-0x800000000000L <= p && p <= 0x7fffffffffffL);
-
- if (!size) return 0;
- if (!addr || addr == MAP_FAILED) return 0;
- if (addr == NULL && size <= 0x200000) return 0;
-
- addr = (void *)ROUNDDOWN((intptr_t)addr, FRAMESIZE);
- size = ROUNDUP(size, FRAMESIZE);
-
- rc = 0;
- c = ADDRSIZE_TO_COORD(addr, size);
- j = findmapping(c.y);
- for (i = j; i; --i) {
- m = _mm.p[i - 1];
- assert(m.x <= m.y);
- assert(c.y >= m.x);
- if (c.x > m.y) {
- break;
- } else if (c.x > m.x && c.y < m.y) {
- /* remove middle portion */
- assert(!"map hole punching not implemented");
- } else if (c.x > m.x && c.y >= m.y) {
- /* remove righthand portion */
- assert(!"map hole punching not implemented");
- /* _mm.p[i - 1].y = c.x - 1; */
- /* i++; */
- /* break; */
- } else if (c.x <= m.x && c.y < m.y) {
- /* remove lefthand portion */
- assert(!"map hole punching not implemented");
- /* _mm.p[i - 1].x = c.y + 1; */
- /* j--; */
- } else if ((m.x >= c.x && m.x <= c.y) && (m.y >= c.x && m.y <= c.y)) {
- a = COORD_TO_ADDRSIZE(m);
- if (!IsWindows()) {
- rc |= munmap$sysv(a.addr, a.size);
- } else {
- if (!UnmapViewOfFile(a.addr)) rc = -1;
- if (!CloseHandle(_mm.h[i - 1])) rc = -1;
- }
- } else {
- assert(!"wut");
- }
- }
-
- if (i < j) {
- if (j < _mm.i) {
- memmove(&_mm.p[i], &_mm.p[j],
- (intptr_t)&_mm.p[_mm.i] - (intptr_t)&_mm.p[j]);
- memmove(&_mm.h[i], &_mm.h[j],
- (intptr_t)&_mm.h[_mm.i] - (intptr_t)&_mm.h[j]);
- }
- _mm.i -= j - i;
- }
-
- return rc;
-}
diff --git a/libc/runtime/munmap_s.c b/libc/runtime/munmap_s.c
index fe7097df6..7fc1d6aea 100644
--- a/libc/runtime/munmap_s.c
+++ b/libc/runtime/munmap_s.c
@@ -20,11 +20,10 @@
#include "libc/bits/bits.h"
#include "libc/bits/pushpop.h"
#include "libc/calls/calls.h"
-#include "libc/runtime/mappings.h"
#include "libc/runtime/runtime.h"
/**
- * Closes memory mapping, the Cosmopolitan way.
+ * Closes memory mapping.
*
* The caller's address holder is set to MAP_FAILED (-1) which is a
* no-op for subsequent invocations.
diff --git a/libc/runtime/opensymboltable.c b/libc/runtime/opensymboltable.c
index 8033753d6..b915fb649 100644
--- a/libc/runtime/opensymboltable.c
+++ b/libc/runtime/opensymboltable.c
@@ -21,6 +21,7 @@
#include "libc/calls/calls.h"
#include "libc/elf/def.h"
#include "libc/elf/elf.h"
+#include "libc/runtime/carsort.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/symbols.h"
#include "libc/sysv/consts/map.h"
@@ -32,18 +33,19 @@
* @return object freeable with closesymboltable(), or NULL w/ errno
*/
struct SymbolTable *opensymboltable(const char *filename) {
- struct SymbolTable *t = MAP_FAILED;
- const Elf64_Sym *symtab;
+ unsigned i, j;
+ struct SymbolTable *t;
+ const Elf64_Sym *symtab, *sym;
+ t = MAP_FAILED;
if (filename && (t = mapanon(BIGPAGESIZE)) != MAP_FAILED &&
mapelfread(filename, &t->mf) &&
(t->name_base = getelfstringtable(t->elf, t->elfsize)) != NULL &&
(symtab = getelfsymboltable(t->elf, t->elfsize, &t->count)) &&
sizeof(struct SymbolTable) + sizeof(struct Symbol) * t->count <
(t->scratch = BIGPAGESIZE)) {
- unsigned j = 0;
getelfvirtualaddressrange(t->elf, t->elfsize, &t->addr_base, &t->addr_end);
- for (unsigned i = 0; i < t->count; ++i) {
- const Elf64_Sym *sym = &symtab[i];
+ for (j = i = 0; i < t->count; ++i) {
+ sym = &symtab[i];
if (iselfsymbolcontent(sym) &&
(sym->st_value >= t->addr_base && sym->st_value <= t->addr_end)) {
t->symbols[j].addr_rva = (unsigned)(sym->st_value - t->addr_base);
@@ -52,7 +54,7 @@ struct SymbolTable *opensymboltable(const char *filename) {
}
}
t->count = j;
- heapsortcar((int32_t(*)[2])t->symbols, t->count);
+ carsort1000(t->count, (void *)t->symbols);
} else {
closesymboltable(&t);
}
diff --git a/libc/runtime/piro.c b/libc/runtime/piro.c
index 84f2791d2..1e3248af8 100644
--- a/libc/runtime/piro.c
+++ b/libc/runtime/piro.c
@@ -33,17 +33,16 @@
╠──────────────────────────────────────────────────────▌▀▄─▐──▀▄─▐▄─▐▄▐▄─▐▄─▐▄─│
│ αcτµαlly pδrταblε εxεcµταblε § post-initialization read-only │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/bits/bits.h"
#include "libc/bits/safemacros.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
-#include "libc/dce.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/prot.h"
#define getaddr(section) ((intptr_t)weakaddr(section))
-static textstartup void __piro_protect(intptr_t start, intptr_t end, int prot) {
+static textstartup void _piro_protect(intptr_t start, intptr_t end, int prot) {
ssize_t len = end - start;
if (len > 0 && start && start % PAGESIZE == 0 && len % PAGESIZE == 0) {
if (mprotect((void *)(unsigned long)start, len, prot) == -1) abort();
@@ -62,10 +61,10 @@ static textstartup void __piro_protect(intptr_t start, intptr_t end, int prot) {
* @see ape/ape.lds
* @see libc/_start.S
*/
-textstartup void __piro(int prot) {
+textstartup void _piro(int prot) {
if (getaddr("main") < getaddr("__test_start")) {
- __piro_protect(getaddr("__test_start"), getaddr("__test_end"), PROT_NONE);
+ _piro_protect(getaddr("__test_start"), getaddr("__test_end"), PROT_NONE);
}
- __piro_protect(getaddr("__ro"), getaddr("_etext"), PROT_READ);
- __piro_protect(getaddr("__piro_start"), getaddr("__piro_end"), prot);
+ _piro_protect(getaddr("__ro"), getaddr("_etext"), PROT_READ);
+ _piro_protect(getaddr("__piro_start"), getaddr("__piro_end"), prot);
}
diff --git a/libc/runtime/printmemoryintervals.c b/libc/runtime/printmemoryintervals.c
new file mode 100644
index 000000000..9689ffaba
--- /dev/null
+++ b/libc/runtime/printmemoryintervals.c
@@ -0,0 +1,45 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/calls.h"
+#include "libc/log/log.h"
+#include "libc/runtime/memtrack.h"
+
+STATIC_YOINK("ntoa");
+STATIC_YOINK("stoa");
+
+void PrintMemoryIntervals(int fd, const struct MemoryIntervals *mm) {
+ int i, frames, maptally, gaptally;
+ maptally = 0;
+ gaptally = 0;
+ (dprintf)(fd, "%s%zd%s\n", "mm->i == ", mm->i, ";");
+ for (i = 0; i < mm->i; ++i) {
+ if (i && mm->p[i].x != mm->p[i - 1].y + 1) {
+ frames = mm->p[i].x - mm->p[i - 1].y - 1;
+ gaptally += frames;
+ (dprintf)(fd, "%s%,zd%s\n", "/* ", frames, " */");
+ }
+ frames = mm->p[i].y + 1 - mm->p[i].x;
+ maptally += frames;
+ (dprintf)(fd, "%s%3u%s0x%08x,0x%08x%s%,zd%s\n", "mm->p[", i, "]=={",
+ mm->p[i].x, mm->p[i].y, "}; /* ", frames, " */");
+ }
+ (dprintf)(fd, "%s%,zd%s%,zd%s\n\n", "/* ", maptally, " frames mapped w/ ",
+ gaptally, " frames gapped */");
+}
diff --git a/libc/runtime/quick_exit.c b/libc/runtime/quick_exit.c
index a45e0bcc0..4d3de77ef 100644
--- a/libc/runtime/quick_exit.c
+++ b/libc/runtime/quick_exit.c
@@ -17,7 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
diff --git a/libc/runtime/relocate.c b/libc/runtime/relocate.c
new file mode 100644
index 000000000..3192e9d54
--- /dev/null
+++ b/libc/runtime/relocate.c
@@ -0,0 +1,42 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/dce.h"
+#include "libc/runtime/runtime.h"
+
+/**
+ * Applies fixups to 64-bit rela addresses in binary.
+ */
+void __relocate(void) {
+ ptrdiff_t skew;
+ unsigned char **p;
+ p = __relo_start;
+ if (p != __relo_end) {
+ skew = (intptr_t)p - (intptr_t)*p;
+ do {
+ if (*p) {
+ *p += skew;
+ if (!NoDebug() && !(_base <= *p && *p < _end)) {
+ asm("int3");
+ }
+ }
+ } while (++p != __relo_end);
+ }
+ return;
+}
diff --git a/libc/runtime/runtime.h b/libc/runtime/runtime.h
index 4d206263d..51ff12fc7 100644
--- a/libc/runtime/runtime.h
+++ b/libc/runtime/runtime.h
@@ -7,6 +7,8 @@ COSMOPOLITAN_C_START_
│ cosmopolitan § runtime ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│*/
+struct SymbolTable;
+
struct StackFrame {
struct StackFrame *next;
intptr_t addr;
@@ -14,31 +16,31 @@ struct StackFrame {
typedef long jmp_buf[8] aligned(CACHELINE);
-extern int g_argc; /* CRT */
-extern char **g_argv; /* CRT */
-extern char **environ; /* CRT */
-extern unsigned long *g_auxv; /* CRT */
-extern jmp_buf g_winmain; /* CRT */
-extern char *program_invocation_name; /* RII */
-extern char *program_invocation_short_name; /* RII */
-extern uint64_t g_syscount; /* RII */
-extern const uint64_t kStartTsc; /* RII */
-extern const char kTmpPath[]; /* RII */
-extern const char kNtSystemDirectory[]; /* RII */
-extern const char kNtWindowsDirectory[]; /* RII */
-extern unsigned char _base[] aligned(PAGESIZE); /* αpε */
-extern char _ehead aligned(PAGESIZE); /* αpε */
-extern char _ereal; /* αpε */
-extern char __privileged_start; /* αpε */
-extern char __test_start; /* αpε */
-extern char __ro; /* αpε */
-extern char _etext aligned(PAGESIZE); /* αpε */
-extern char __piro_start; /* αpε */
-extern char _edata aligned(PAGESIZE); /* αpε */
-extern char __piro_end; /* αpε */
-extern char _end aligned(PAGESIZE); /* αpε */
-extern uint8_t __zip_start[]; /* αpε */
-extern uint8_t __zip_end[]; /* αpε */
+extern int g_argc; /* CRT */
+extern char **g_argv; /* CRT */
+extern char **environ; /* CRT */
+extern unsigned long *g_auxv; /* CRT */
+extern jmp_buf g_winmain; /* CRT */
+extern char *program_invocation_name; /* RII */
+extern char *program_invocation_short_name; /* RII */
+extern uint64_t g_syscount; /* RII */
+extern const uint64_t kStartTsc; /* RII */
+extern const char kTmpPath[]; /* RII */
+extern const char kNtSystemDirectory[]; /* RII */
+extern const char kNtWindowsDirectory[]; /* RII */
+extern unsigned char _base[] aligned(PAGESIZE); /* αpε */
+extern unsigned char _ehead[] aligned(PAGESIZE); /* αpε */
+extern unsigned char _etext[] aligned(PAGESIZE); /* αpε */
+extern unsigned char _edata[] aligned(PAGESIZE); /* αpε */
+extern unsigned char _end[] aligned(PAGESIZE); /* αpε */
+extern unsigned char _ereal; /* αpε */
+extern unsigned char __privileged_start; /* αpε */
+extern unsigned char __test_start; /* αpε */
+extern unsigned char __ro; /* αpε */
+extern unsigned char *__relo_start[]; /* αpε */
+extern unsigned char *__relo_end[]; /* αpε */
+extern uint8_t __zip_start[]; /* αpε */
+extern uint8_t __zip_end[]; /* αpε */
long missingno();
void mcount(void);
@@ -70,6 +72,8 @@ void loadxmm(void *);
void peekall(void);
int issetugid(void);
void weakfree(void *) libcesque;
+void __hook(void (*)(void), struct SymbolTable *);
+bool isheap(void *);
/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § runtime » optimizations ─╬─│┼
diff --git a/libc/runtime/runtime.mk b/libc/runtime/runtime.mk
index a34c41bdd..e0aaddf02 100644
--- a/libc/runtime/runtime.mk
+++ b/libc/runtime/runtime.mk
@@ -61,6 +61,8 @@ $(LIBC_RUNTIME_A).pkg: \
o/$(MODE)/libc/runtime/asan.greg.o \
o/$(MODE)/libc/runtime/shadowargs.o \
+o/$(MODE)/libc/runtime/hook.greg.o \
+o/$(MODE)/libc/runtime/ftrace.greg.o \
o/$(MODE)/libc/runtime/__stack_chk_fail.o \
o/$(MODE)/libc/runtime/__stack_chk_guard.o: \
OVERRIDE_COPTS += \
diff --git a/libc/runtime/spawn.S b/libc/runtime/spawn.S
new file mode 100644
index 000000000..701fe60a9
--- /dev/null
+++ b/libc/runtime/spawn.S
@@ -0,0 +1,51 @@
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/dce.h"
+#include "libc/sysv/consts/prot.h"
+#include "libc/macros.h"
+
+/ Self-bootstraps process upon existence before calling main.
+_spawn: push %rbp
+ mov %rsp,%rbp
+
+/ Tune FPU settings if -ffast-math is somehow used systemically.
+#ifdef __FAST_MATH__
+ call __fast_math
+#endif
+
+/ Call decentralized initialization assembly.
+ call _init
+#if IsModeDbg()
+ call _init # _init() is idempotent
+#endif
+
+/ Call global initialization functions.
+ call _construct
+
+/ Restricts .initbss memory so it's read-only after initialization.
+/ TODO: Delete this unless there's measurable performance advantage.
+#if !IsTrustworthy()
+ mov $PROT_READ,%edi
+ call _piro
+#endif
+
+ pop %rbp
+ ret
+ .endfn _spawn,globl
diff --git a/libc/runtime/sysconf.h b/libc/runtime/sysconf.h
index b7d7b6f92..0afd83eaf 100644
--- a/libc/runtime/sysconf.h
+++ b/libc/runtime/sysconf.h
@@ -13,8 +13,8 @@ COSMOPOLITAN_C_START_
long sysconf(int);
+#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
#define sysconf(X) __sysconf(X)
-
forceinline long __sysconf(int thing) {
switch (thing) {
case _SC_ARG_MAX:
@@ -31,6 +31,7 @@ forceinline long __sysconf(int thing) {
return -1;
}
}
+#endif /* GNU && !ANSI */
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/runtime/winmain.greg.c b/libc/runtime/winmain.greg.c
index 3bd64bc4c..8165e93d4 100644
--- a/libc/runtime/winmain.greg.c
+++ b/libc/runtime/winmain.greg.c
@@ -25,6 +25,7 @@
#include "libc/nt/runtime.h"
#include "libc/runtime/getdosenviron.h"
#include "libc/runtime/internal.h"
+#include "libc/runtime/missioncritical.h"
static void LoadFasterAndPreventHijacking(void) {
unsigned wrote;
@@ -34,14 +35,15 @@ static void LoadFasterAndPreventHijacking(void) {
}
}
-noreturn textwindows int WinMain(void *hInstance, void *hPrevInstance,
- const char *lpCmdLine, int nCmdShow) {
+textwindows int WinMain(void *hInstance, void *hPrevInstance,
+ const char *lpCmdLine, int nCmdShow) {
int i, count;
const char16_t *cmd16, *env16;
+ char *argarray[512], *envarray[512];
+ char argblock[ARG_MAX], envblock[ENV_MAX];
long auxarray[][2] = {{pushpop(0L), pushpop(0L)}};
- char envblock[ENV_MAX], *envarray[512], argblock[ARG_MAX], *argarray[512];
LoadFasterAndPreventHijacking();
- *(/*unconst*/ int *)&__hostos = pushpop(WINDOWS);
+ *(/*unconst*/ int *)&hostos = WINDOWS;
cmd16 = GetCommandLine();
env16 = GetEnvironmentStrings();
count = getdosargv(cmd16, argblock, ARG_MAX, argarray, 512);
@@ -50,13 +52,5 @@ noreturn textwindows int WinMain(void *hInstance, void *hPrevInstance,
}
getdosenviron(env16, envblock, ENV_MAX, envarray, 512);
FreeEnvironmentStrings(env16);
- register int argc asm("r12") = count;
- register char **argv asm("r13") = argarray;
- register char **envp asm("r14") = envarray;
- register long(*auxv)[2] asm("r15") = auxarray;
- asm volatile("jmp\t__executive"
- : /* no outputs */
- : "r"(argc), "r"(argv), "r"(envp), "r"(auxv)
- : "memory", "cc");
- unreachable;
+ _executive(count, argarray, envarray, auxarray);
}
diff --git a/libc/sock/internal.h b/libc/sock/internal.h
index 089c43bbb..d0174d9f1 100644
--- a/libc/sock/internal.h
+++ b/libc/sock/internal.h
@@ -19,6 +19,7 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#ifndef COSMOPOLITAN_LIBC_SOCK_INTERNAL_H_
#define COSMOPOLITAN_LIBC_SOCK_INTERNAL_H_
+#ifndef __STRICT_ANSI__
#include "libc/bits/bits.h"
#include "libc/nt/winsock.h"
#include "libc/sock/sock.h"
@@ -145,4 +146,5 @@ forceinline void sockaddr2linux(void *saddr) {
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_SOCK_INTERNAL_H_ */
diff --git a/libc/sock/ipclassify.h b/libc/sock/ipclassify.h
index 69c9db2b4..a0afd99e3 100644
--- a/libc/sock/ipclassify.h
+++ b/libc/sock/ipclassify.h
@@ -1,9 +1,10 @@
#ifndef COSMOPOLITAN_LIBC_SOCK_IPCLASSIFY_H_
#define COSMOPOLITAN_LIBC_SOCK_IPCLASSIFY_H_
+#ifndef __STRICT_ANSI__
#include "libc/sock/sock.h"
-#include "libc/sysv/errfuns.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/inaddr.h"
+#include "libc/sysv/errfuns.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@@ -65,4 +66,5 @@ forceinline bool ispublicip(int sin_family, void *sin_addr) {
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_SOCK_IPCLASSIFY_H_ */
diff --git a/libc/sock/sock.h b/libc/sock/sock.h
index 082ecd949..086975e5f 100644
--- a/libc/sock/sock.h
+++ b/libc/sock/sock.h
@@ -1,6 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_SOCK_SOCK_H_
#define COSMOPOLITAN_LIBC_SOCK_SOCK_H_
-#include "libc/bits/bits.h"
+#include "libc/bits/bswap.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
/*───────────────────────────────────────────────────────────────────────────│─╗
diff --git a/libc/sock/xinet_ntop.c b/libc/sock/xinet_ntop.c
index ef007d4e6..31f2a0064 100644
--- a/libc/sock/xinet_ntop.c
+++ b/libc/sock/xinet_ntop.c
@@ -17,6 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/bits/weaken.h"
#include "libc/log/log.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
diff --git a/libc/stdio/fclose.c b/libc/stdio/fclose.c
index 3daf83af0..0971f4913 100644
--- a/libc/stdio/fclose.c
+++ b/libc/stdio/fclose.c
@@ -38,7 +38,7 @@
int fclose(FILE *f) {
int rc;
if (!f) return 0; /* good java behavior; glibc crashes */
- fflushunregister(f);
+ _fflushunregister(f);
fflush(f);
free_s(&f->buf);
f->state = EOF;
diff --git a/libc/stdio/fclose_s.c b/libc/stdio/fclose_s.c
index c50ba0be5..0108cdeee 100644
--- a/libc/stdio/fclose_s.c
+++ b/libc/stdio/fclose_s.c
@@ -18,7 +18,6 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/bits.h"
-#include "libc/runtime/mappings.h"
#include "libc/stdio/stdio.h"
/**
diff --git a/libc/stdio/fdopen.c b/libc/stdio/fdopen.c
index a1101f732..4aae88d71 100644
--- a/libc/stdio/fdopen.c
+++ b/libc/stdio/fdopen.c
@@ -37,7 +37,7 @@ FILE *fdopen(int fd, const char *mode) {
res->reader = freadbuf;
res->writer = fwritebuf;
if ((res->iomode & O_ACCMODE) != O_RDONLY) {
- fflushregister(res);
+ _fflushregister(res);
}
}
return res;
diff --git a/libc/stdio/fflush.c b/libc/stdio/fflush.c
index bfa8a7631..76d8a1c4b 100644
--- a/libc/stdio/fflush.c
+++ b/libc/stdio/fflush.c
@@ -79,7 +79,7 @@ int fflush(FILE *f) {
return res;
}
-int fflushregister(FILE *f) {
+textstartup int _fflushregister(FILE *f) {
size_t i;
struct StdioFlush *sf;
sf = &g_fflush;
@@ -98,7 +98,7 @@ int fflushregister(FILE *f) {
return append(&sf->handles, &f);
}
-void fflushunregister(FILE *f) {
+void _fflushunregister(FILE *f) {
size_t i;
struct StdioFlush *sf;
sf = &g_fflush;
diff --git a/libc/stdio/fputc.c b/libc/stdio/fputc.c
index 551a3bac3..c6b522d9f 100644
--- a/libc/stdio/fputc.c
+++ b/libc/stdio/fputc.c
@@ -17,7 +17,8 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/stdio/fputc.h"
+#include "libc/calls/calls.h"
+#include "libc/stdio/internal.h"
#include "libc/stdio/stdio.h"
/**
@@ -25,4 +26,21 @@
*
* @return c (as unsigned char) if written or -1 w/ errno
*/
-int fputc(int c, FILE *f) { return __fputc(c, f); }
+noinstrument int fputc(int c, FILE *f) {
+ if (c != -1) {
+ c &= 0xff;
+ f->buf[f->end] = c;
+ f->end = (f->end + 1) & (f->size - 1);
+ if (unlikely(f->beg == f->end || f->bufmode == _IONBF ||
+ (f->bufmode == _IOLBF && c == '\n'))) {
+ if (f->writer) {
+ return f->writer(f);
+ } else if (f->beg == f->end) {
+ return fseteof(f);
+ }
+ }
+ return c;
+ } else {
+ return fseteof(f);
+ }
+}
diff --git a/libc/stdio/fputc.h b/libc/stdio/fputc.h
deleted file mode 100644
index e996e644c..000000000
--- a/libc/stdio/fputc.h
+++ /dev/null
@@ -1,36 +0,0 @@
-#ifndef COSMOPOLITAN_LIBC_STDIO_FPUTC_H_
-#define COSMOPOLITAN_LIBC_STDIO_FPUTC_H_
-#include "libc/assert.h"
-#include "libc/calls/calls.h"
-#include "libc/stdio/internal.h"
-#include "libc/stdio/stdio.h"
-#include "libc/sysv/consts/o.h"
-#if !(__ASSEMBLER__ + __LINKER__ + 0)
-
-/**
- * Writes byte to stream.
- *
- * @return c (as unsigned char) if written or -1 w/ errno
- */
-forceinline int __fputc(int c, FILE *f) {
- /* assert((f->iomode & O_ACCMODE) != O_RDONLY); */
- if (c != -1) {
- unsigned char ch = (unsigned char)c;
- f->buf[f->end] = ch;
- f->end = (f->end + 1) & (f->size - 1);
- if (f->beg == f->end || f->bufmode == _IONBF ||
- (f->bufmode == _IOLBF && ch == '\n')) {
- if (f->writer) {
- return f->writer(f);
- } else if (f->beg == f->end) {
- return fseteof(f);
- }
- }
- return ch;
- } else {
- return fseteof(f);
- }
-}
-
-#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#endif /* COSMOPOLITAN_LIBC_STDIO_FPUTC_H_ */
diff --git a/libc/stdio/fputs.c b/libc/stdio/fputs.c
index 2c1181afe..e66fc0f88 100644
--- a/libc/stdio/fputs.c
+++ b/libc/stdio/fputs.c
@@ -18,7 +18,6 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/errno.h"
-#include "libc/stdio/fputc.h"
#include "libc/stdio/stdio.h"
/**
@@ -36,7 +35,7 @@ int fputs(const char *s, FILE *f) {
unsigned char *p = (unsigned char *)s;
int res = 0;
while (*p) {
- if (__fputc(*p++, f) == -1) {
+ if (fputc(*p++, f) == -1) {
if (ferror(f) == EINTR) continue;
if (feof(f)) errno = f->state = EPIPE;
return -1;
diff --git a/libc/stdio/fputwc.c b/libc/stdio/fputwc.c
index 10a1680e2..6d945b9d2 100644
--- a/libc/stdio/fputwc.c
+++ b/libc/stdio/fputwc.c
@@ -18,7 +18,6 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/limits.h"
-#include "libc/stdio/fputc.h"
#include "libc/stdio/internal.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
@@ -35,7 +34,7 @@ wint_t fputwc(wchar_t wc, FILE *f) {
if (wc != -1) {
len = tpencode(buf, sizeof(buf), wc, false);
for (i = 0; i < len; ++i) {
- if (__fputc(buf[i], f) == -1) return -1;
+ if (fputc(buf[i], f) == -1) return -1;
}
return wc;
} else {
diff --git a/libc/stdio/fread.c b/libc/stdio/fread.c
index bb90aaade..a1ec63aba 100644
--- a/libc/stdio/fread.c
+++ b/libc/stdio/fread.c
@@ -18,7 +18,6 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/conv/conv.h"
-#include "libc/conv/sizemultiply.h"
#include "libc/errno.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/internal.h"
@@ -34,17 +33,16 @@
*/
size_t fread(void *buf, size_t stride, size_t count, FILE *f) {
int c;
- size_t i, bytes;
+ size_t i, n;
unsigned char *p;
- if (!sizemultiply(&bytes, stride, count)) {
- return fseterr(f, EOVERFLOW);
- }
- for (p = buf, i = 0; i < bytes; ++i) {
- if ((c = fgetc(f)) == -1) {
- if (i % stride != 0) abort(); /* todo(jart) */
+ for (n = stride * count, p = buf, i = 0; i < n; ++i) {
+ if ((c = fgetc(f)) != -1) {
+ p[i] = c & 0xff;
+ } else if (!(i % stride)) {
return i / stride;
+ } else {
+ return fseterr(f, EOVERFLOW);
}
- p[i] = c & 0xff;
}
return count;
}
diff --git a/libc/stdio/fseteof.c b/libc/stdio/fseteof.c
index 41a246b3a..1c6d12d8f 100644
--- a/libc/stdio/fseteof.c
+++ b/libc/stdio/fseteof.c
@@ -19,4 +19,6 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/stdio/internal.h"
-long fseteof(FILE *f) { return fseterr(f, -1); }
+long fseteof(FILE *f) {
+ return fseterr(f, -1);
+}
diff --git a/libc/stdio/fwrite.c b/libc/stdio/fwrite.c
index 23ac489f6..f5a7e5a45 100644
--- a/libc/stdio/fwrite.c
+++ b/libc/stdio/fwrite.c
@@ -17,10 +17,8 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/conv/sizemultiply.h"
#include "libc/errno.h"
#include "libc/runtime/runtime.h"
-#include "libc/stdio/fputc.h"
#include "libc/stdio/internal.h"
#include "libc/stdio/stdio.h"
@@ -32,14 +30,15 @@
* @return count on success, [0,count) on EOF, 0 on error or count==0
*/
size_t fwrite(const void *data, size_t stride, size_t count, FILE *f) {
- int rc;
- size_t i, bytes;
- const unsigned char *p = (const unsigned char *)data;
- if (!sizemultiply(&bytes, stride, count)) return fseterr(f, EOVERFLOW);
- for (i = 0; i < bytes; ++i) {
- if ((rc = __fputc(p[i], f)) == -1) {
- if (i % stride != 0) abort(); /* todo(jart) */
- return i / stride;
+ size_t i, n;
+ const unsigned char *p;
+ for (n = stride * count, p = data, i = 0; i < n; ++i) {
+ if (fputc(p[i], f) == -1) {
+ if (!(i % stride)) {
+ return i / stride;
+ } else {
+ return fseterr(f, EOVERFLOW);
+ }
}
}
return count;
diff --git a/libc/stdio/g_stderr.c b/libc/stdio/g_stderr.c
index 903ed5190..7f25f1fb2 100644
--- a/libc/stdio/g_stderr.c
+++ b/libc/stdio/g_stderr.c
@@ -26,7 +26,8 @@ FILE *stderr;
hidden FILE g_stderr;
hidden unsigned char g_stderr_buf[BUFSIZ] aligned(PAGESIZE);
-static textstartup void g_stderr_init() {
- fflushregister(stderr);
+static textstartup void _init_g_stderr2() {
+ _fflushregister(stderr);
}
-const void *const g_stderr_ctor[] initarray = {g_stderr_init};
+
+const void *const g_stderr_ctor[] initarray = {_init_g_stderr2};
diff --git a/libc/stdio/g_stdin.c b/libc/stdio/g_stdin.c
index 0829e6d93..a0f659561 100644
--- a/libc/stdio/g_stdin.c
+++ b/libc/stdio/g_stdin.c
@@ -27,6 +27,7 @@ hidden FILE g_stdin;
hidden unsigned char g_stdin_buf[BUFSIZ] aligned(PAGESIZE);
static textstartup void g_stdin_init() {
- fflushregister(stdin);
+ _fflushregister(stdin);
}
+
const void *const g_stdin_ctor[] initarray = {g_stdin_init};
diff --git a/libc/stdio/g_stdout.c b/libc/stdio/g_stdout.c
index af23b5fdb..251807e06 100644
--- a/libc/stdio/g_stdout.c
+++ b/libc/stdio/g_stdout.c
@@ -29,14 +29,14 @@ FILE *stdout;
hidden FILE g_stdout;
hidden unsigned char g_stdout_buf[BUFSIZ] aligned(PAGESIZE);
-static textstartup void g_stdout_init() {
+static textstartup void _init_g_stdout2() {
struct FILE *sf;
sf = stdout;
asm("" : "+r"(sf));
if (IsWindows() || ischardev(pushpop(sf->fd))) {
sf->bufmode = _IOLBF;
}
- fflushregister(sf);
+ _fflushregister(sf);
}
-const void *const g_stdout_ctor[] initarray = {g_stdout_init};
+const void *const g_stdout_ctor[] initarray = {_init_g_stdout2};
diff --git a/libc/stdio/internal.h b/libc/stdio/internal.h
index 3e50bf54b..d89ab1358 100644
--- a/libc/stdio/internal.h
+++ b/libc/stdio/internal.h
@@ -9,8 +9,8 @@ extern unsigned char g_stdinbuf[BUFSIZ];
extern unsigned char g_stdoutbuf[BUFSIZ];
extern unsigned char g_stderrbuf[BUFSIZ];
-int fflushregister(FILE *) hidden;
-void fflushunregister(FILE *) hidden;
+int _fflushregister(FILE *) hidden;
+void _fflushunregister(FILE *) hidden;
int freadbuf(FILE *) hidden;
int fwritebuf(FILE *) hidden;
diff --git a/libc/stdio/puts.c b/libc/stdio/puts.c
index d9b84eade..af832410b 100644
--- a/libc/stdio/puts.c
+++ b/libc/stdio/puts.c
@@ -17,7 +17,6 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/stdio/fputc.h"
#include "libc/stdio/stdio.h"
/**
diff --git a/libc/stdio/serialstdio.c b/libc/stdio/serialstdio.c
index 4b1b93c2e..013f99225 100644
--- a/libc/stdio/serialstdio.c
+++ b/libc/stdio/serialstdio.c
@@ -32,7 +32,7 @@ static void fout(FILE *f) {
f->beg = (f->beg + 1) & (f->size - 1);
}
-static int serialstdio(FILE *f, unsigned char status, void action(FILE *f)) {
+static int serialstdio(FILE *f, unsigned char status, void action(FILE *)) {
int block = 1;
unsigned tally = 0;
while (f->end != f->beg) {
@@ -47,5 +47,9 @@ static int serialstdio(FILE *f, unsigned char status, void action(FILE *f)) {
return (int)tally;
}
-int fsreadbuf(FILE *f) { return serialstdio(f, UART_TTYDA, fin); }
-int fswritebuf(FILE *f) { return serialstdio(f, UART_TTYTXR, fout); }
+int fsreadbuf(FILE *f) {
+ return serialstdio(f, UART_TTYDA, fin);
+}
+int fswritebuf(FILE *f) {
+ return serialstdio(f, UART_TTYTXR, fout);
+}
diff --git a/libc/stdio/stdio.h b/libc/stdio/stdio.h
index cad7d06cf..38cf17767 100644
--- a/libc/stdio/stdio.h
+++ b/libc/stdio/stdio.h
@@ -10,17 +10,17 @@ COSMOPOLITAN_C_START_
╚────────────────────────────────────────────────────────────────────────────│*/
typedef struct FILE {
- uint8_t bufmode; /* 0: _IOFBF, etc. (ignored if fd is -1) */
- bool noclose; /* 1: for fake dup() */
- uint32_t iomode; /* 4: O_RDONLY, etc. (ignored if fd is -1) */
- int32_t state; /* 8: 0=OK, -1=EOF, >0=errno */
- int fd; /* 12: ≥0=fd, -1=closed|buffer */
- uint32_t beg; /* 16 */
- uint32_t end; /* 20 */
- uint8_t *buf; /* 24 */
- size_t size; /* 32 */
- int (*reader)(struct FILE *f); /* 40 */
- int (*writer)(struct FILE *f); /* 48 */
+ uint8_t bufmode; // 0x00 _IOFBF, etc. (ignored if fd=-1)
+ bool noclose; // 0x01 for fake dup()
+ uint32_t iomode; // 0x04 O_RDONLY, etc. (ignored if fd=-1)
+ int32_t state; // 0x08 0=OK, -1=EOF, >0=errno
+ int fd; // 0x0c ≥0=fd, -1=closed|buffer
+ uint32_t beg; // 0x10
+ uint32_t end; // 0x14
+ uint8_t *buf; // 0x18
+ size_t size; // 0x20
+ int (*reader)(struct FILE *); // 0x28
+ int (*writer)(struct FILE *); // 0x30
} FILE;
extern FILE *stdin;
@@ -35,7 +35,7 @@ int putc(int, FILE *) paramsnonnull();
int fflush(FILE *);
int fgetc(FILE *) paramsnonnull();
int ungetc(int, FILE *) paramsnonnull();
-int fileno(FILE *) paramsnonnull();
+int fileno(FILE *) paramsnonnull() nosideeffect;
int fputc(int, FILE *) paramsnonnull();
int fputs(const char *, FILE *) paramsnonnull();
int fputws(const wchar_t *, FILE *) paramsnonnull();
diff --git a/libc/stdio/stdio.mk b/libc/stdio/stdio.mk
index 4fe660680..2e62bdaec 100644
--- a/libc/stdio/stdio.mk
+++ b/libc/stdio/stdio.mk
@@ -53,6 +53,10 @@ $(LIBC_STDIO_A).pkg: \
$(LIBC_STDIO_A_OBJS) \
$(foreach x,$(LIBC_STDIO_A_DIRECTDEPS),$($(x)_A).pkg)
+#o/$(MODE)/libc/stdio/fputc.o: \
+ OVERRIDE_CFLAGS += \
+ $(NO_MAGIC)
+
LIBC_STDIO_LIBS = $(foreach x,$(LIBC_STDIO_ARTIFACTS),$($(x)))
LIBC_STDIO_SRCS = $(foreach x,$(LIBC_STDIO_ARTIFACTS),$($(x)_SRCS))
LIBC_STDIO_HDRS = $(foreach x,$(LIBC_STDIO_ARTIFACTS),$($(x)_HDRS))
diff --git a/libc/stdio/system.c b/libc/stdio/system.c
index 84fb90810..ac2668cdb 100644
--- a/libc/stdio/system.c
+++ b/libc/stdio/system.c
@@ -17,7 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/hefty/ntspawn.h"
#include "libc/calls/internal.h"
diff --git a/libc/stdio/temp.h b/libc/stdio/temp.h
index 722dc40e2..9b6113f40 100644
--- a/libc/stdio/temp.h
+++ b/libc/stdio/temp.h
@@ -13,7 +13,7 @@ nodiscard int mkostempsm(char *, int, unsigned, int);
compatfn char *mktemp(char *);
int mkostempsmi(char *, int, unsigned, uint64_t *, int,
- int openit(const char *, int, ...)) hidden nodiscard;
+ int (*)(const char *, int, ...)) hidden nodiscard;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/stdio/vfprintf.c b/libc/stdio/vfprintf.c
index 7b2b86207..392755770 100644
--- a/libc/stdio/vfprintf.c
+++ b/libc/stdio/vfprintf.c
@@ -19,28 +19,23 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/fmt/fmt.h"
#include "libc/limits.h"
-#include "libc/stdio/fputc.h"
#include "libc/stdio/stdio.h"
#include "libc/sysv/errfuns.h"
struct state {
- FILE *const f;
- unsigned toto;
+ FILE *f;
+ int n;
};
static int vfprintfputchar(int c, struct state *st) {
- if (st->toto <= INT_MAX) {
- st->toto++;
- return __fputc(c, st->f);
- } else {
- return eoverflow();
- }
+ st->n++;
+ return fputc(c, st->f);
}
int(vfprintf)(FILE *f, const char *fmt, va_list va) {
struct state st[1] = {{f, 0}};
if (palandprintf(vfprintfputchar, st, fmt, va) != -1) {
- return st->toto;
+ return st->n;
} else {
return -1;
}
diff --git a/libc/str/appendchar.h b/libc/str/appendchar.h
index cc39c9432..643fa7be9 100644
--- a/libc/str/appendchar.h
+++ b/libc/str/appendchar.h
@@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_RUNTIME_APPENDCHAR_H_
#define COSMOPOLITAN_LIBC_RUNTIME_APPENDCHAR_H_
+#ifndef __STRICT_ANSI__
#include "libc/str/str.h"
#include "libc/str/tpenc.h"
#include "libc/str/tpencode.h"
@@ -15,4 +16,5 @@ static inline void AppendChar(char **p, char *pe, wint_t wc) {
}
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_RUNTIME_APPENDCHAR_H_ */
diff --git a/libc/nexgen32e/getcachesize.c b/libc/str/getcachesize.c
similarity index 100%
rename from libc/nexgen32e/getcachesize.c
rename to libc/str/getcachesize.c
diff --git a/libc/str/getutf16.ncabi.c b/libc/str/getutf16.ncabi.c
index 6e5570e81..669e3a856 100644
--- a/libc/str/getutf16.ncabi.c
+++ b/libc/str/getutf16.ncabi.c
@@ -28,7 +28,7 @@
* @note synchronization is performed to skip leading continuations;
* canonicalization and validation are performed to some extent
*/
-unsigned(getutf16)(const char16_t *p, wint_t *wc) {
+forcealignargpointer unsigned(getutf16)(const char16_t *p, wint_t *wc) {
unsigned skip = 0;
while ((p[skip] & UTF16_MASK) == UTF16_CONT) skip++;
if ((p[skip] & UTF16_MASK) != UTF16_MOAR) {
diff --git a/libc/nexgen32e/getx86processormodel.c b/libc/str/getx86processormodel.c
similarity index 100%
rename from libc/nexgen32e/getx86processormodel.c
rename to libc/str/getx86processormodel.c
diff --git a/libc/nexgen32e/insertionsort.greg.c b/libc/str/insertionsort.greg.c
similarity index 100%
rename from libc/nexgen32e/insertionsort.greg.c
rename to libc/str/insertionsort.greg.c
diff --git a/libc/str/internal.h b/libc/str/internal.h
index ec6ab016d..be45024c0 100644
--- a/libc/str/internal.h
+++ b/libc/str/internal.h
@@ -19,6 +19,7 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#ifndef COSMOPOLITAN_LIBC_STR_INTERNAL_H_
#define COSMOPOLITAN_LIBC_STR_INTERNAL_H_
+#ifndef __STRICT_ANSI__
#include "libc/str/str.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
@@ -48,16 +49,13 @@ nodebuginfo forceinline bool32 iscont(wint_t c) {
return (c & 0b11000000) == 0b10000000;
}
-extern const uint32_t kCrc32Tab[256];
-
char *strstr$sse42(const char *, const char *) strlenesque hidden;
char16_t *strstr16$sse42(const char16_t *, const char16_t *) strlenesque hidden;
void *memmem$sse42(const void *, size_t, const void *,
size_t) strlenesque hidden;
-uint32_t crc32c$sse42(uint32_t, const void *, size_t) strlenesque hidden;
-uint32_t crc32$pclmul(uint32_t, const void *, size_t) hidden;
void sha256$x86(uint32_t[hasatleast 8], const uint8_t[hasatleast 64],
uint32_t) hidden;
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_STR_INTERNAL_H_ */
diff --git a/libc/nexgen32e/knobs.c b/libc/str/knobs.c
similarity index 100%
rename from libc/nexgen32e/knobs.c
rename to libc/str/knobs.c
diff --git a/libc/str/knuthmultiplicativehash.h b/libc/str/knuthmultiplicativehash.h
index 5e0fc8d7a..872f1bf9b 100644
--- a/libc/str/knuthmultiplicativehash.h
+++ b/libc/str/knuthmultiplicativehash.h
@@ -1,26 +1,15 @@
#ifndef COSMOPOLITAN_LIBC_STR_KNUTHMULTIPLICATIVEHASH_H_
#define COSMOPOLITAN_LIBC_STR_KNUTHMULTIPLICATIVEHASH_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
-COSMOPOLITAN_C_START_
forceinline uint32_t KnuthMultiplicativeHash32(const void *buf, size_t size) {
- /* frozen due to presence in sqlite & promise in libc/getuid.c */
- const unsigned char *const p = (const unsigned char *)buf;
- uint32_t hash = 0, kPhiPrime = 0x9e3779b1;
size_t i;
- for (i = 0; i < size; i++) hash = (p[i] + hash) * kPhiPrime;
- return hash;
+ uint32_t h;
+ const uint32_t kPhiPrime = 0x9e3779b1;
+ const unsigned char *p = (const unsigned char *)buf;
+ for (h = i = 0; i < size; i++) h = (p[i] + h) * kPhiPrime;
+ return h;
}
-forceinline uint64_t KnuthMultiplicativeHash(const void *buf, size_t size) {
- /* TODO(jart): verify w/ primary source */
- const unsigned char *const p = (const unsigned char *)buf;
- uint64_t hash = 0, kPhiPrime = 0x9e3779b9925d4c17;
- size_t i;
- for (i = 0; i < size; i++) hash = (p[i] + hash) * kPhiPrime;
- return hash;
-}
-
-COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_STR_KNUTHMULTIPLICATIVEHASH_H_ */
diff --git a/libc/nexgen32e/kx86processormodels.c b/libc/str/kx86processormodels.c
similarity index 100%
rename from libc/nexgen32e/kx86processormodels.c
rename to libc/str/kx86processormodels.c
diff --git a/libc/nexgen32e/lz4check.c b/libc/str/lz4check.c
similarity index 100%
rename from libc/nexgen32e/lz4check.c
rename to libc/str/lz4check.c
diff --git a/libc/nexgen32e/lz4cpy.c b/libc/str/lz4cpy.initabi.c
similarity index 100%
rename from libc/nexgen32e/lz4cpy.c
rename to libc/str/lz4cpy.initabi.c
diff --git a/libc/nexgen32e/lz4decode.c b/libc/str/lz4decode.c
similarity index 100%
rename from libc/nexgen32e/lz4decode.c
rename to libc/str/lz4decode.c
diff --git a/libc/nexgen32e/memset16.c b/libc/str/memset16.c
similarity index 100%
rename from libc/nexgen32e/memset16.c
rename to libc/str/memset16.c
diff --git a/libc/str/sha256.c b/libc/str/sha256.c
index 59ebc0218..37cdf1c6b 100644
--- a/libc/str/sha256.c
+++ b/libc/str/sha256.c
@@ -31,7 +31,7 @@ static void sha256_transform(uint32_t state[hasatleast 8],
size_t i;
uint32_t a, b, c, d, e, f, g, h, t1, t2, m[64];
for (i = 0; i < 16; ++i, data += 4) {
- m[i] = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
+ m[i] = (uint32_t)data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
}
for (; i < 64; ++i) {
m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
diff --git a/libc/str/startswith.c b/libc/str/startswith.c
index 5513b5711..91dbfa1df 100644
--- a/libc/str/startswith.c
+++ b/libc/str/startswith.c
@@ -25,5 +25,10 @@
* @param prefix is also NUL-terminated
*/
bool(startswith)(const char *s, const char *prefix) {
- return strncmp(s, prefix, strlen(prefix)) == 0;
+ if (s == prefix) return true;
+ for (;;) {
+ if (!*prefix) return true;
+ if (!*s) return false;
+ if (*s++ != *prefix++) return false;
+ }
}
diff --git a/libc/str/stpcpy.c b/libc/str/stpcpy.c
index 949240f48..1596aeefd 100644
--- a/libc/str/stpcpy.c
+++ b/libc/str/stpcpy.c
@@ -17,13 +17,16 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/limits.h"
#include "libc/str/str.h"
/**
* Copies string, returning pointer to where copying ended.
+ *
* @see strcpy(), mempcpy()
* @asyncsignalsafe
*/
char *stpcpy(char *dst, const char *src) {
- return (char *)mempcpy(dst, src, strlen(src) + 1) - 1;
+ dst = memccpy(dst, src, '\0', SIZE_MAX);
+ return dst - 1;
}
diff --git a/libc/str/str.h b/libc/str/str.h
index c6e080f59..980b32deb 100644
--- a/libc/str/str.h
+++ b/libc/str/str.h
@@ -14,7 +14,7 @@ extern const uint8_t kToLower[256];
extern const uint8_t kToUpper[256];
extern const uint16_t kToLower16[256];
extern const uint8_t kBase36[256];
-extern const char16_t kCp437[256]; /** IBM Code Page 437 */
+extern const char16_t kCp437[256];
int isascii(int);
int isspace(int);
@@ -68,8 +68,8 @@ void *isnotplaintext(const void *, size_t) nothrow nocallback nosideeffect;
#define UTF16_MOAR 0b1101100000000000 /* 0xD800..0xDBFF */
#define UTF16_CONT 0b1101110000000000 /* 0xDC00..0xDBFF */
-unsigned getutf16(const char16_t *p, wint_t *wc);
-int pututf16(char16_t *s, size_t size, wint_t wc, bool awesome);
+unsigned getutf16(const char16_t *, wint_t *);
+int pututf16(char16_t *, size_t, wint_t, bool);
int iswalnum(wint_t);
int iswalpha(wint_t);
int iswblank(wint_t);
@@ -105,6 +105,7 @@ void *memchr(const void *, int, size_t) strlenesque;
char *strchrnul(const char *, int) strlenesque returnsnonnull;
void *rawmemchr(const void *, int) strlenesque returnsnonnull;
void bzero(void *, size_t) paramsnonnull() libcesque;
+void explicit_bzero(void *, size_t) paramsnonnull() libcesque;
size_t strlen16(const char16_t *) strlenesque;
size_t strnlen16(const char16_t *, size_t) strlenesque;
size_t strnlen16_s(const char16_t *, size_t);
@@ -165,7 +166,6 @@ char *strcpy(char *, const char *) memcpyesque;
char16_t *strcpy16(char16_t *, const char16_t *) memcpyesque;
char *strncat(char *, const char *, size_t) memcpyesque;
char *strncpy(char *, const char *, size_t) memcpyesque;
-char *_strncpy(char *, const char *, size_t) asm("strncpy") memcpyesque;
char *strtok(char *, const char *) paramsnonnull((2)) libcesque;
char *strtok_r(char *, const char *, char **) paramsnonnull((2, 3));
uint16_t *strcpyzbw(uint16_t *, const char *) memcpyesque;
@@ -179,7 +179,6 @@ bool endswith16(const char16_t *, const char16_t *) strlenesque;
bool wcsendswith(const wchar_t *, const wchar_t *) strlenesque;
const char *indexdoublenulstring(const char *, unsigned) strlenesque;
int getkvlin(const char *, const char *const[]);
-void crc32init(uint32_t[hasatleast 256], uint32_t);
wchar_t *wmemset(wchar_t *, wchar_t, size_t) memcpyesque;
char16_t *memset16(char16_t *, char16_t, size_t) memcpyesque;
compatfn wchar_t *wmemcpy(wchar_t *, const wchar_t *, size_t) memcpyesque;
@@ -190,28 +189,14 @@ char *tinystrstr(const char *, const char *) strlenesque;
char16_t *tinystrstr16(const char16_t *, const char16_t *) strlenesque;
void *tinymemmem(const void *, size_t, const void *, size_t) strlenesque;
-void *memtolower(void *p, size_t n);
-char *strntolower(char *s, size_t n);
-char *strtolower(char *s) paramsnonnull();
-char *strntoupper(char *s, size_t n);
-char *strtoupper(char *s) paramsnonnull();
-char *chomp(char *line);
-char16_t *chomp16(char16_t *line);
-wchar_t *wchomp(wchar_t *line);
-
-/* gcc -Werror=stringop-truncation misunderstands strncpy() api */
-#define strncpy(DEST, SRC, N) _strncpy(DEST, SRC, N)
-
-#define explicit_bzero(STR, BYTES) \
- do { \
- void *Str; \
- size_t Bytes; \
- asm volatile("call\texplicit_bzero" \
- : "=D"(Str), "=S"(Bytes) \
- : "0"(STR), "1"(BYTES) \
- : "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", \
- "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5"); \
- } while (0)
+void *memtolower(void *, size_t);
+char *strntolower(char *, size_t);
+char *strtolower(char *) paramsnonnull();
+char *strntoupper(char *, size_t);
+char *strtoupper(char *) paramsnonnull();
+char *chomp(char *);
+char16_t *chomp16(char16_t *);
+wchar_t *wchomp(wchar_t *);
/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § strings » multibyte ─╬─│┼
@@ -246,8 +231,8 @@ size_t strclen16(const char16_t *) nosideeffect;
size_t strnclen16(const char16_t *, size_t) nosideeffect;
typedef unsigned wctype_t;
-wctype_t wctype(const char *name) strlenesque;
-int iswctype(wint_t c, wctype_t type) pureconst;
+wctype_t wctype(const char *) strlenesque;
+int iswctype(wint_t, wctype_t) pureconst;
/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § strings » hashing ─╬─│┼
@@ -262,9 +247,6 @@ struct Sha256Ctx {
uint32_t state[8];
};
-uint32_t crc32_z(uint32_t, const void *, size_t);
-extern uint32_t (*const crc32c)(uint32_t, const void *, size_t) paramsnonnull();
-
void sha256_init(struct Sha256Ctx *);
void sha256_update(struct Sha256Ctx *, const uint8_t *, size_t);
void sha256_final(struct Sha256Ctx *, uint8_t *);
@@ -307,13 +289,6 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t);
: strstr16, default \
: strstr)(haystack, needle)
-#define strchr(s, c) \
- _Generic(*(s), wchar_t \
- : wcschr, char16_t \
- : strchr16, default \
- : (isconstant(s) && isconstant(c) ? __builtin_strchr : _strchr))(s, \
- c)
-
#define strrchr(s, c) \
_Generic(*(s), wchar_t \
: wcsrchr, char16_t \
@@ -419,10 +394,28 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t);
╚────────────────────────────────────────────────────────────────────────────│*/
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
+extern int (*const __memcmp)(const void *, const void *, size_t);
+#define memcmp(a, b, n) __memcmp(a, b, n)
+
+/* gcc -Werror=stringop-truncation misunderstands strncpy() api */
+char *_strncpy(char *, const char *, size_t) asm("strncpy") memcpyesque;
+#define strncpy(DEST, SRC, N) _strncpy(DEST, SRC, N)
+
+#define explicit_bzero(STR, BYTES) \
+ do { \
+ void *Str; \
+ size_t Bytes; \
+ asm volatile("call\texplicit_bzero" \
+ : "=D"(Str), "=S"(Bytes) \
+ : "0"(STR), "1"(BYTES) \
+ : "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", \
+ "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5"); \
+ } while (0)
+
#ifdef UNBLOAT_STDARG
#define __STR_XMM_CLOBBER
#else
-#define __STR_XMM_CLOBBER "xmm3",
+#define __STR_XMM_CLOBBER "xmm3", "xmm4",
#endif
#define __memcpy_isgoodsize(SIZE) \
@@ -437,7 +430,7 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t);
#define memcpy(DEST, SRC, SIZE) \
(__memcpy_isgoodsize(SIZE) ? __builtin_memcpy(DEST, SRC, SIZE) \
- : __memcpy("_memcpy", DEST, SRC, SIZE))
+ : __memcpy("MemCpy", DEST, SRC, SIZE))
#define memset(DEST, BYTE, SIZE) \
(__memset_isgoodsize(SIZE) ? __builtin_memset(DEST, BYTE, SIZE) \
@@ -445,7 +438,8 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t);
#if defined(__STDC_HOSTED__) && (defined(__SSE2__) || defined(UNBLOAT_STDARG))
-#define memmove(DEST, SRC, SIZE) __memcpy("_memmove", (DEST), (SRC), (SIZE))
+#define memmove(DEST, SRC, SIZE) __memcpy("MemMove", (DEST), (SRC), (SIZE))
+
#define __memcpy(FN, DEST, SRC, SIZE) \
({ \
void *DeSt = (DEST); \
@@ -457,16 +451,18 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t);
: __STR_XMM_CLOBBER "cc"); \
DeSt; \
})
+
#define mempcpy(DEST, SRC, SIZE) \
({ \
size_t SIze = (SIZE); \
(void *)((char *)memcpy((DEST), (SRC), SIze) + SIze); \
})
+
#define __memset(DEST, BYTE, SIZE) \
({ \
void *DeSt = (DEST); \
size_t SiZe = (SIZE); \
- asm("call\t_memset" \
+ asm("call\tMemSet" \
: "=m"(*(char(*)[SiZe])(DeSt)) \
: "D"(DeSt), "S"(BYTE), "d"(SiZe) \
: __STR_XMM_CLOBBER "cc"); \
@@ -476,6 +472,7 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t);
#else /* hosted/sse2/unbloat */
#define memmove(DEST, SRC, SIZE) __memcpy((DEST), (SRC), (SIZE))
+
#define mempcpy(DEST, SRC, SIZE) \
({ \
void *Rdi, *Dest = (DEST); \
@@ -488,6 +485,7 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t);
: "cc"); \
Rdi; \
})
+
#define __memcpy(FN, DEST, SRC, SIZE) \
({ \
void *Rdi, *Dest = (DEST); \
@@ -500,6 +498,7 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t);
: "cc"); \
Dest; \
})
+
#define __memset(DEST, BYTE, SIZE) \
({ \
void *Rdi, *Dest = (DEST); \
@@ -532,8 +531,8 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t);
#define pututf16(BUF, SIZE, CH, AWESOME) __pututf16(BUF, SIZE, CH, AWESOME)
#define getutf16(BUF, CHPTR) __getutf16(BUF, CHPTR)
size_t _strlen(const char *s) asm("strlen") strlenesque;
-char *_strchr(const char *, int) asm("strchr") strlenesque;
void *_memchr(const void *, int, size_t) asm("memchr") strlenesque;
+
forceinline int __pututf16(char16_t *s, size_t size, wint_t wc,
bool32 awesome) {
if (size >= 1 && (0x00 <= wc && wc <= 0xD7FF)) {
@@ -548,98 +547,25 @@ forceinline int __pututf16(char16_t *s, size_t size, wint_t wc,
}
int ax;
asm("call\tpututf16"
- : "=a"(ax), "=m"(*(char16_t(*)[size])s)
+ : "=a"(ax), "=m"(*(char(*)[size])s)
: "D"(s), "S"(size), "d"(wc)
: "cc");
return ax;
}
+
forceinline unsigned __getutf16(const char16_t *s, wint_t *wc) {
if ((0x00 <= s[0] && s[0] <= 0xD7FF)) {
*wc = s[0];
return 1;
}
unsigned ax;
- asm("call\tgetutf16" : "=a"(ax), "=m"(*wc) : "D"(s), "S"(wc), "m"(*s) : "cc");
+ asm("call\tgetutf16"
+ : "=a"(ax), "=m"(*wc)
+ : "D"(s), "S"(wc), "m"(*s), "m"(*(char(*)[4])s)
+ : "cc");
return ax;
}
-/*
- * GCC has builtins for these, that only do things for literals. They
- * also cause the compiler to whine in a kafkaesque way when flags like
- * -Werror=shadow and -Werror=implicit-function-declaration are passed.
- */
-#define isascii(c) isascii_(c)
-#define isspace(c) isspace_(c)
-#define isalpha(c) isalpha_(c)
-#define isdigit(c) isdigit_(c)
-#define isalnum(c) isalnum_(c)
-#define isxdigit(c) isxdigit_(c)
-#define isprint(c) isprint_(c)
-#define islower(c) islower_(c)
-#define isupper(c) isupper_(c)
-#define isblank(c) isblank_(c)
-#define iscntrl(c) iscntrl_(c)
-#define isgraph(c) isgraph_(c)
-#define tolower(c) tolower_(c)
-#define ispunct(c) ispunct_(c)
-#define toupper(c) toupper_(c)
-#define hextoint(c) hextoint_(c)
-#define DECLARE_CTYPE(NAME, EXPR) \
- pureconst forceinline nodebuginfo int NAME(int i) { \
- unsigned char c = (unsigned char)i; \
- return (EXPR); \
- }
-DECLARE_CTYPE(isascii_, 0 <= c && c <= 0x7f)
-DECLARE_CTYPE(isspace_, kCtype[c] & 0x01)
-DECLARE_CTYPE(isalpha_, kCtype[c] & 0x02)
-DECLARE_CTYPE(isdigit_, '0' <= c && c <= '9')
-DECLARE_CTYPE(isalnum_, kCtype[c] & 0x06)
-DECLARE_CTYPE(isxdigit_, kCtype[c] & 0x08)
-DECLARE_CTYPE(isprint_, kCtype[c] & 0x10)
-DECLARE_CTYPE(islower_, 'a' <= c && c <= 'z')
-DECLARE_CTYPE(isupper_, 'A' <= c && c <= 'Z')
-DECLARE_CTYPE(isblank_, kCtype[c] & 0x80)
-DECLARE_CTYPE(iscntrl_, !isprint_(c))
-DECLARE_CTYPE(isgraph_, isprint_(c) && (c) != ' ')
-DECLARE_CTYPE(tolower_, kToLower[c])
-DECLARE_CTYPE(ispunct_, isprint(c) && !(kCtype[c] & 0x07))
-DECLARE_CTYPE(toupper_, kToUpper[c])
-DECLARE_CTYPE(hextoint_, (c + 9 * (1 & (SAR(c, 6)))) & 0xf)
-#undef DECLARE_CTYPE
-#define iswalnum(c) iswalnum_(c)
-#define iswalpha(c) iswalpha_(c)
-#define iswblank(c) iswblank_(c)
-#define iswcntrl(c) iswcntrl_(c)
-#define iswdigit(c) iswdigit_(c)
-#define iswgraph(c) iswgraph_(c)
-#define iswlower(c) iswlower_(c)
-#define iswspace(c) iswspace_(c)
-#define iswupper(c) iswupper_(c)
-#define iswxdigit(c) iswxdigit_(c)
-#define iswpunct(c) iswpunct_(c)
-#define iswprint(c) iswprint_(c)
-#define towlower(c) towlower_(c)
-#define towupper(c) towupper_(c)
-#define DECLARE_WCTYPE(R, NAME, T, EXPR) \
- forceinline nodebuginfo R NAME(T c) { \
- return EXPR; \
- }
-DECLARE_WCTYPE(int, iswalnum_, wint_t, isascii(c) ? isalnum(c) : c)
-DECLARE_WCTYPE(int, iswalpha_, wint_t, isascii(c) ? isalpha(c) : c)
-DECLARE_WCTYPE(int, iswblank_, wint_t, isascii(c) ? isblank(c) : c)
-DECLARE_WCTYPE(int, iswcntrl_, wint_t, isascii(c) ? iscntrl(c) : c)
-DECLARE_WCTYPE(int, iswdigit_, wint_t, isascii(c) ? isdigit(c) : c)
-DECLARE_WCTYPE(int, iswgraph_, wint_t, isascii(c) ? isgraph(c) : c)
-DECLARE_WCTYPE(int, iswlower_, wint_t, isascii(c) ? islower(c) : c)
-DECLARE_WCTYPE(int, iswspace_, wint_t, isascii(c) ? isspace(c) : c)
-DECLARE_WCTYPE(int, iswupper_, wint_t, isascii(c) ? isupper(c) : c)
-DECLARE_WCTYPE(int, iswxdigit_, wint_t, isascii(c) ? isxdigit(c) : c)
-DECLARE_WCTYPE(int, iswpunct_, wint_t, !isascii(c) || ispunct(c))
-DECLARE_WCTYPE(int, iswprint_, wint_t, !isascii(c) || isprint(c))
-DECLARE_WCTYPE(unsigned, towlower_, unsigned, isascii(c) ? tolower(c) : c)
-DECLARE_WCTYPE(unsigned, towupper_, unsigned, isascii(c) ? toupper(c) : c)
-#undef DECLARE_WCTYPE
-
#endif /* __GNUC__ && !__STRICT_ANSI__ */
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
diff --git a/libc/nexgen32e/strcspn.c b/libc/str/strcspn.c
similarity index 100%
rename from libc/nexgen32e/strcspn.c
rename to libc/str/strcspn.c
diff --git a/libc/nexgen32e/strcspn16.c b/libc/str/strcspn16.c
similarity index 97%
rename from libc/nexgen32e/strcspn16.c
rename to libc/str/strcspn16.c
index f3b57d9df..46096f568 100644
--- a/libc/nexgen32e/strcspn16.c
+++ b/libc/str/strcspn16.c
@@ -23,6 +23,6 @@
#undef strcspn
#define char char16_t
#define HasCharacter HasCharacter16
-#define strcspn strcspn16
+#define strcspn strcspn16
-#include "libc/nexgen32e/strcspn.c"
+#include "libc/str/strcspn.c"
diff --git a/libc/nexgen32e/strpbrk.c b/libc/str/strpbrk.c
similarity index 100%
rename from libc/nexgen32e/strpbrk.c
rename to libc/str/strpbrk.c
diff --git a/libc/nexgen32e/strpbrk16.c b/libc/str/strpbrk16.c
similarity index 95%
rename from libc/nexgen32e/strpbrk16.c
rename to libc/str/strpbrk16.c
index ee9af7391..19bf75ef2 100644
--- a/libc/nexgen32e/strpbrk16.c
+++ b/libc/str/strpbrk16.c
@@ -23,6 +23,7 @@
#undef strpbrk
#define char char16_t
#define HasCharacter HasCharacter16
-#define strpbrk strpbrk16
+#define strpbrk strpbrk16
+#define strchr(x, y) strchr16(x, y)
-#include "libc/nexgen32e/strpbrk.c"
+#include "libc/str/strpbrk.c"
diff --git a/libc/str/strsignal.c b/libc/str/strsignal.c
index 24d6ee985..06f00cc56 100644
--- a/libc/str/strsignal.c
+++ b/libc/str/strsignal.c
@@ -23,14 +23,20 @@
static const char kSig[4] = "SIG";
static const char kUnknown[8] = "UNKNOWN";
+
alignas(1) static const char kStrSignals[][8] = {
"EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS",
"FPE", "KILL", "USR1", "SEGV", "USR2", "PIPE", "ALRM", "TERM",
"STKFLT", "CHLD", "CONT", "STOP", "TSTP", "TTIN", "TTOU", "URG",
- "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "IO", "PWR", "SYS"};
+ "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "IO", "PWR", "SYS",
+};
static char g_strsignal[4 + 8];
+/**
+ * Returns name associated with signal code.
+ * @see sigaction()
+ */
char *strsignal(int sig) {
if (0 <= sig && (unsigned)sig < ARRAYLEN(kStrSignals)) {
memcpy(g_strsignal, kSig, 4);
diff --git a/libc/nexgen32e/strspn.c b/libc/str/strspn.c
similarity index 96%
rename from libc/nexgen32e/strspn.c
rename to libc/str/strspn.c
index e5a3d893c..0889a50d2 100644
--- a/libc/nexgen32e/strspn.c
+++ b/libc/str/strspn.c
@@ -22,6 +22,9 @@
/**
* Returns prefix length, consisting of chars in accept.
+ *
+ * @param accept is nul-terminated character set
+ * @see strcspn(), strtok_r()
* @asyncsignalsafe
*/
size_t(strspn)(const char *s, const char *accept) {
diff --git a/libc/nexgen32e/strspn16.c b/libc/str/strspn16.c
similarity index 97%
rename from libc/nexgen32e/strspn16.c
rename to libc/str/strspn16.c
index 4379d923f..538ef2eec 100644
--- a/libc/nexgen32e/strspn16.c
+++ b/libc/str/strspn16.c
@@ -23,6 +23,6 @@
#undef strspn
#define char char16_t
#define HasCharacter HasCharacter16
-#define strspn strspn16
+#define strspn strspn16
-#include "libc/nexgen32e/strspn.c"
+#include "libc/str/strspn.c"
diff --git a/libc/str/strtok_r.c b/libc/str/strtok_r.c
index 2e05b4182..9c7cb17fa 100644
--- a/libc/str/strtok_r.c
+++ b/libc/str/strtok_r.c
@@ -30,16 +30,17 @@
* @asyncsignalsafe
*/
char *strtok_r(char *s, const char *sep, char **state) {
+ size_t leadingseps, tokenlen;
if (!s) {
s = *state;
if (!s) {
return NULL;
}
}
- size_t leadingseps = strspn(s, sep);
+ leadingseps = strspn(s, sep);
s += leadingseps;
if (*s) {
- size_t tokenlen = strcspn(s, sep);
+ tokenlen = strcspn(s, sep);
if (s[tokenlen]) {
s[tokenlen] = '\0';
*state = &s[tokenlen + 1];
diff --git a/libc/str/tinymemccpy.c b/libc/str/tinymemccpy.c
new file mode 100644
index 000000000..1992bfa44
--- /dev/null
+++ b/libc/str/tinymemccpy.c
@@ -0,0 +1,31 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/str/str.h"
+#include "libc/str/tinymemccpy.h"
+
+void *tinymemccpy(void *dst, const void *src, int termchar, size_t limit) {
+ size_t i;
+ unsigned char *d;
+ const unsigned char *s;
+ for (termchar &= 0xff, d = dst, s = src, i = 0; i < limit; ++i) {
+ if ((d[i] = s[i]) == termchar) return d + i + 1;
+ }
+ return NULL;
+}
diff --git a/libc/str/tinymemccpy.h b/libc/str/tinymemccpy.h
new file mode 100644
index 000000000..971448373
--- /dev/null
+++ b/libc/str/tinymemccpy.h
@@ -0,0 +1,10 @@
+#ifndef COSMOPOLITAN_LIBC_STR_TINYMEMCCPY_H_
+#define COSMOPOLITAN_LIBC_STR_TINYMEMCCPY_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+void *tinymemccpy(void *, const void *, int, size_t);
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_STR_TINYMEMCCPY_H_ */
diff --git a/libc/str/tinymemmem.h b/libc/str/tinymemmem.h
index 1f8f89cd4..c96fccd32 100644
--- a/libc/str/tinymemmem.h
+++ b/libc/str/tinymemmem.h
@@ -1,12 +1,13 @@
#ifndef COSMOPOLITAN_LIBC_STR_TINYSTRSTR_H_
#define COSMOPOLITAN_LIBC_STR_TINYSTRSTR_H_
+#ifndef __STRICT_ANSI__
#include "libc/str/str.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
forceinline void *tinymemmemi(const void *haystk, size_t haystksize,
const void *needle, size_t needlesize) {
- const char *p = haystk;
- const char *pe = p + haystksize;
+ const char *p = (const char *)haystk;
+ const char *pe = (const char *)haystk + haystksize;
while (p < pe) {
size_t i = 0;
++p;
@@ -21,4 +22,5 @@ forceinline void *tinymemmemi(const void *haystk, size_t haystksize,
}
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* !ANSI */
#endif /* COSMOPOLITAN_LIBC_STR_TINYMEMMEM_H_ */
diff --git a/libc/str/tpdecode.h b/libc/str/tpdecode.h
index bbeff3a53..549a496c2 100644
--- a/libc/str/tpdecode.h
+++ b/libc/str/tpdecode.h
@@ -8,11 +8,11 @@ int tpdecode(const char *, wint_t *) paramsnonnull((1)) libcesque;
#ifndef __STRICT_ANSI__
#define tpdecode(S, OUT) __tpdecode(S, OUT)
forceinline int __tpdecode(const char *s, wint_t *out) {
+ int ax;
if (0 <= *s && *s <= 0x7f) {
*out = *s;
return 1;
}
- int ax;
asm("call\ttpdecode"
: "=a"(ax), "=m"(*(char(*)[6])s)
: "D"(s), "S"(out)
diff --git a/libc/str/tpdecodecb.h b/libc/str/tpdecodecb.h
index 09cb926c3..c7098d178 100644
--- a/libc/str/tpdecodecb.h
+++ b/libc/str/tpdecodecb.h
@@ -19,6 +19,7 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#ifndef COSMOPOLITAN_LIBC_STR_TPDECODECB_H_
#define COSMOPOLITAN_LIBC_STR_TPDECODECB_H_
+#include "libc/nexgen32e/bsr.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
/**
@@ -28,17 +29,16 @@
*/
forceinline int tpdecodecb(wint_t *out, int first,
int get(void *arg, uint32_t i), void *arg) {
- uint32_t wc, cb, need, msb, i = 1;
- if ((wc = first) == -1) return -1;
- while ((wc & 0b11000000) == 0b10000000) {
+ uint32_t wc, cb, need, msb, j, i = 1;
+ if (unlikely((wc = first) == -1)) return -1;
+ while (unlikely((wc & 0b11000000) == 0b10000000)) {
if ((wc = get(arg, i++)) == -1) return -1;
}
- if ((wc & 0b10000000) == 0b10000000) {
- asm("bsr\t%1,%0" : "=r"(msb) : "rm"(~wc & 0xff) : "cc");
- if (!msb) msb = 1;
+ if (unlikely(!(0 <= wc && wc <= 0x7F))) {
+ msb = wc < 252 ? bsr(~wc & 0xff) : 1;
need = 7 - msb;
wc &= ((1u << msb) - 1) | 0b00000011;
- for (uint32_t j = 1; j < need; ++j) {
+ for (j = 1; j < need; ++j) {
if ((cb = get(arg, i++)) == -1) return -1;
if ((cb & 0b11000000) == 0b10000000) {
wc = wc << 6 | (cb & 0b00111111);
@@ -48,7 +48,7 @@ forceinline int tpdecodecb(wint_t *out, int first,
}
}
}
- if (out) *out = (wint_t)wc;
+ if (likely(out)) *out = (wint_t)wc;
return i;
}
diff --git a/libc/str/tpenc.h b/libc/str/tpenc.h
index 9cc5d26a2..a6cb8dede 100644
--- a/libc/str/tpenc.h
+++ b/libc/str/tpenc.h
@@ -6,16 +6,19 @@ COSMOPOLITAN_C_START_
uint64_t tpenc(int32_t) pureconst;
#ifndef __STRICT_ANSI__
-#define tpenc(CODE) \
- ({ \
- long Buf; \
- int32_t Code = (CODE); \
- if (0 <= Code && Code <= 127) { \
- Buf = Code; \
- } else { \
- asm("call\ttpenc" : "=a"(Buf), "+D"(Code) : /* inputs */ : "cc"); \
- } \
- Buf; \
+#define tpenc(CODE) \
+ ({ \
+ long Buf; \
+ int32_t Code = (CODE); \
+ if (0 <= Code && Code <= 127) { \
+ Buf = Code; \
+ } else { \
+ asm("call\ttpenc" \
+ : "=a"(Buf), "+D"(Code) \
+ : /* inputs */ \
+ : "cc"); \
+ } \
+ Buf; \
})
#endif
diff --git a/libc/str/varint.h b/libc/str/varint.h
deleted file mode 100644
index a0b753dcc..000000000
--- a/libc/str/varint.h
+++ /dev/null
@@ -1,87 +0,0 @@
-#ifndef COSMOPOLITAN_LIBC_VARINT_H_
-#define COSMOPOLITAN_LIBC_VARINT_H_
-#include "libc/limits.h"
-#if !(__ASSEMBLER__ + __LINKER__ + 0)
-
-#define unzigzag(x) ((x >> 1) ^ -(x & 1))
-#define zigzag(x) _Generic((x), int32_t : zigzag32, default : zigzag64)(x)
-
-forceinline int32_t zigzag32(int32_t x) {
- return (int64_t)((uint64_t)x << 1) ^ (x >> 31);
-}
-
-forceinline int64_t zigzag64(int64_t x) {
- return (int64_t)((uint64_t)x << 1) ^ (x >> 63);
-}
-
-/**
- * Copies variable-length integer to buffer.
- *
- * @param p needs 10+ bytes for 64-bit and 5+ for 32-bit
- * @param x is unsigned number to encode
- * @return pointer past last written byte
- * @see writesint() which has more efficient encoding for signed numbers
- */
-forceinline uint8_t *writevint(uint8_t *p, uint64_t x) {
- do {
- *p++ = (uint8_t)(x | 0x80);
- x >>= 7;
- } while (x > 0);
- p[-1] &= 0x7f;
- return p;
-}
-
-/**
- * Copies variable-length signed integer to buffer.
- *
- * @param p needs 10+ bytes for 64-bit and 5+ for 32-bit
- * @param x is unsigned number to encode
- * @return pointer past last written byte
- */
-forceinline uint8_t *writesint(uint8_t *p, int64_t x) {
- return writevint(p, zigzag(x));
-}
-
-/**
- * Reads variable-length integer from buffer.
- *
- * @param x is unsigned number to encode
- * @return pointer past last read byte or -1ul on error
- */
-forceinline uint8_t *readvint(uint8_t *p, uint8_t *pe, uint64_t *res) {
- uint8_t b;
- uint64_t x, o;
- x = 0;
- b = 0;
- while (p < pe) {
- o = *p++;
- x |= ((uint64_t)o & 0x7f) << b;
- if (!(o & 0x80)) {
- *res = x;
- return p;
- }
- if ((b += 7) > 64) {
- break;
- }
- }
- return (uint8_t *)-1ul;
-}
-
-/**
- * Reads variable-length zig-zagged integer from buffer.
- *
- * @param x is unsigned number to encode
- * @return pointer past last read byte or -1ul on error
- */
-forceinline uint8_t *readsint(uint8_t *p, uint8_t *pe, int64_t *res) {
- uint64_t u;
- if ((p = readvint(p, pe, &u)) != (uint8_t *)-1ul) {
- *res = unzigzag((int64_t)u);
- return p;
- } else {
- return (uint8_t *)-1ul;
- }
-}
-
-#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#endif /* COSMOPOLITAN_LIBC_VARINT_H_ */
diff --git a/libc/nexgen32e/wcscspn.c b/libc/str/wcscspn.c
similarity index 97%
rename from libc/nexgen32e/wcscspn.c
rename to libc/str/wcscspn.c
index c45b61ebc..b25cdb09b 100644
--- a/libc/nexgen32e/wcscspn.c
+++ b/libc/str/wcscspn.c
@@ -23,6 +23,6 @@
#undef strcspn
#define char wchar_t
#define HasCharacter HasCharacterWide
-#define strcspn wcscspn
+#define strcspn wcscspn
-#include "libc/nexgen32e/strcspn.c"
+#include "libc/str/strcspn.c"
diff --git a/libc/nexgen32e/wcspbrk.c b/libc/str/wcspbrk.c
similarity index 95%
rename from libc/nexgen32e/wcspbrk.c
rename to libc/str/wcspbrk.c
index 347f28eee..1a1a76761 100644
--- a/libc/nexgen32e/wcspbrk.c
+++ b/libc/str/wcspbrk.c
@@ -23,6 +23,7 @@
#undef strpbrk
#define char wchar_t
#define HasCharacter HasCharacterWide
-#define strpbrk wcspbrk
+#define strpbrk wcspbrk
+#define strchr(x, y) wcschr(x, y)
-#include "libc/nexgen32e/strpbrk.c"
+#include "libc/str/strpbrk.c"
diff --git a/libc/nexgen32e/wcsspn.c b/libc/str/wcsspn.c
similarity index 97%
rename from libc/nexgen32e/wcsspn.c
rename to libc/str/wcsspn.c
index a0549b312..9381a9aed 100644
--- a/libc/nexgen32e/wcsspn.c
+++ b/libc/str/wcsspn.c
@@ -23,6 +23,6 @@
#undef strspn
#define char wchar_t
#define HasCharacter HasCharacterWide
-#define strspn wcsspn
+#define strspn wcsspn
-#include "libc/nexgen32e/strspn.c"
+#include "libc/str/strspn.c"
diff --git a/libc/nexgen32e/wmemset.c b/libc/str/wmemset.c
similarity index 100%
rename from libc/nexgen32e/wmemset.c
rename to libc/str/wmemset.c
diff --git a/libc/stubs/abort.S b/libc/stubs/abort.S
index de14fbc29..e2445e669 100644
--- a/libc/stubs/abort.S
+++ b/libc/stubs/abort.S
@@ -29,4 +29,4 @@ abort: push %bp
mov %sp,%bp
rlcall panic
int3
- .endfn abort,weak,protected
+ .endfn abort,weak
diff --git a/libc/stubs/addvdi3.S b/libc/stubs/addvdi3.S
index 203589142..9de3dcd54 100644
--- a/libc/stubs/addvdi3.S
+++ b/libc/stubs/addvdi3.S
@@ -18,9 +18,9 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.align 16
-.text.likely
.source __FILE__
+.privileged
+.alignfunc
/ Returns 𝑥+𝑦, aborting on overflow.
/
diff --git a/libc/stubs/addvsi3.S b/libc/stubs/addvsi3.S
index 5ee51f10e..5c0cdb09c 100644
--- a/libc/stubs/addvsi3.S
+++ b/libc/stubs/addvsi3.S
@@ -18,9 +18,9 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.align 16
-.text.likely
.source __FILE__
+.privileged
+.alignfunc
/ Returns 𝑥+𝑦, aborting on overflow.
/
diff --git a/libc/stubs/addvti3.S b/libc/stubs/addvti3.S
index cb32e8bec..b9cfda253 100644
--- a/libc/stubs/addvti3.S
+++ b/libc/stubs/addvti3.S
@@ -18,9 +18,9 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.align 16
-.text.likely
.source __FILE__
+.privileged
+.alignfunc
/ Returns 𝑥+𝑦, aborting on overflow.
/
@@ -30,8 +30,9 @@
/ @see -ftrapv
__addvti3:
mov %rdi,%rax
- add %rsi,%rax
- adc %rdi,%rdx
+ add %rdx,%rax
+ mov %rsi,%rdx
+ adc %rcx,%rdx
jo 1f
ret
1: push %rbp
diff --git a/libc/stubs/assertfail.S b/libc/stubs/assertfail.S
index afee3605b..f5aef21d9 100644
--- a/libc/stubs/assertfail.S
+++ b/libc/stubs/assertfail.S
@@ -28,6 +28,7 @@
/ @mode long,legacy,real
/ @noreturn
__assert_fail:
+ int3
push %bp
mov %sp,%bp
rlcall panic
diff --git a/libc/stubs/errnolocation.S b/libc/stubs/errnolocation.S
index 7a5222050..cebb3f35b 100644
--- a/libc/stubs/errnolocation.S
+++ b/libc/stubs/errnolocation.S
@@ -24,7 +24,6 @@
/ Returns address of errno variable.
/ @note this isn't a universal definition
__errno_location:
- .leafprologue
- lea errno(%rip),%rax
- .leafepilogue
+ ezlea errno,ax
+ ret
.endfn __errno_location,globl,hidden
diff --git a/libc/stubs/exit.S b/libc/stubs/exit.S
index 6a454d895..c11b56643 100644
--- a/libc/stubs/exit.S
+++ b/libc/stubs/exit.S
@@ -32,6 +32,7 @@
/ @noreturn
_exit: push %bp
mov %sp,%bp
+ .weak _Exit
rlcall _Exit
int3
- .endfn _exit,weak,protected
+ .endfn _exit,weak
diff --git a/libc/stubs/exit11.S b/libc/stubs/exit11.S
deleted file mode 100644
index 6be10457e..000000000
--- a/libc/stubs/exit11.S
+++ /dev/null
@@ -1,36 +0,0 @@
-/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
-│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "ape/macros.h"
-.real
-.source __FILE__
-.code16 # ∩ .code32 ∩ .code64
-
-/ Thrice re-imagined Unix user process termination API stub.
-/
-/ @param edi is exit code ∈ [0,256)
-/ @see libc/runtime/_exit.c
-/ @mode long,legacy,real
-/ @asyncsignalsafe
-/ @noreturn
-_Exit: push %bp
- mov %sp,%bp
- rlcall panic
- int3
- .endfn _Exit,weak,protected
diff --git a/libc/stubs/ld.S b/libc/stubs/ld.S
index 1c10e93d5..2c87b6f25 100644
--- a/libc/stubs/ld.S
+++ b/libc/stubs/ld.S
@@ -34,15 +34,13 @@
__privileged_start = 0
__test_start = 0
__ro = 0
- __piro_start = 0
+ __relo_start = 0
__relo_end = 0
- __piro_end = 0
.globl _base
.globl ape.xlm
- .globl __piro_start
+ .globl __relo_start
.globl __relo_end
- .globl __piro_end
.globl __privileged_start
.globl __ro
.globl __test_start
@@ -54,9 +52,8 @@
.weak _base
.weak ape.xlm
- .weak __piro_start
+ .weak __relo_start
.weak __relo_end
- .weak __piro_end
.weak __privileged_start
.weak __ro
.weak __test_start
diff --git a/libc/stubs/mulvdi3.S b/libc/stubs/mulvdi3.S
index 59bd96bdc..923e4ef45 100644
--- a/libc/stubs/mulvdi3.S
+++ b/libc/stubs/mulvdi3.S
@@ -18,9 +18,9 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.align 16
-.text.likely
.source __FILE__
+.privileged
+.alignfunc
/ Returns 𝑥*𝑦, aborting on overflow.
/
@@ -31,6 +31,11 @@
__mulvdi3:
mov %rdi,%rax
imul %rsi
- jc __on_arithmetic_overflow
+ jc 1f
+ ret
+1: push %rbp
+ mov %rsp,%rbp
+ call __on_arithmetic_overflow
+ pop %rbp
ret
.endfn __mulvdi3,globl
diff --git a/libc/stubs/mulvsi3.S b/libc/stubs/mulvsi3.S
index 1b50ac3c7..69280efce 100644
--- a/libc/stubs/mulvsi3.S
+++ b/libc/stubs/mulvsi3.S
@@ -18,9 +18,9 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.align 16
-.text.likely
.source __FILE__
+.privileged
+.alignfunc
/ Returns 𝑥*𝑦, aborting on overflow.
/
@@ -31,6 +31,11 @@
__mulvsi3:
mov %edi,%eax
imul %esi
- jc __on_arithmetic_overflow
+ jc 1f
+ ret
+1: push %rbp
+ mov %rsp,%rbp
+ call __on_arithmetic_overflow
+ pop %rbp
ret
.endfn __mulvsi3,globl
diff --git a/libc/stubs/mulvti3.S b/libc/stubs/mulvti3.S
index 5a46debaf..31979b6bc 100644
--- a/libc/stubs/mulvti3.S
+++ b/libc/stubs/mulvti3.S
@@ -18,27 +18,119 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.align 16
-.text.likely
.source __FILE__
+.privileged
+.alignfunc
/ Returns 𝑥*𝑦, aborting on overflow.
/
-/ TODO(jart): is this ok?
-/
/ @param rdi:rsi is int128 𝑥
/ @param rdx:rcx is int128 𝑦
/ @return rdx:rax is 𝑥*𝑦
/ @see -ftrapv
__mulvti3:
+ push %rbp
+ mov %rsp,%rbp
+ push %rbx
+ push %rbx
+ push %r12
+ push %r13
+ push %r14
+ push %r15
+ mov %rdx,%r10
+ mov %rdi,%rdx
+ xor %r11d,%r11d
+ mov %r10,%rax
+ sar $63,%rdx
+ sar $63,%rax
+ cmp %rsi,%rdx
+ jne 4f
+ cmp %rcx,%rax
+ jne 5f
+ mov %rdi,%rax
+ imul %r10
+ mov %rax,%r14
+ mov %rdx,%r8
+ jmp 2f
+5: mov %r10,%r12
+ mov %rcx,%r13
+ mov %rcx,%r8
+ mov %rdi,%rbx
+ jmp 6f
+4: cmp %rcx,%rax
+ jne 7f
+ mov %rdi,%r12
+ mov %rsi,%r13
+ mov %rsi,%r8
+ mov %r10,%rbx
+6: mov %rdi,%rax
+ mul %r10
+ mov %rax,%r14
+ mov %rbx,%rax
+ mov %rdx,%r15
+ mul %r8
+ test %r8,%r8
+ jns 8f
+ xor %r8d,%r8d
+ sub %r8,%rax
+ sbb %rbx,%rdx
+8: test %rbx,%rbx
+ jns 9f
+ sub %r12,%rax
+ sbb %r13,%rdx
+9: mov %r15,%r8
+ xor %r9d,%r9d
+ add %rax,%r8
+ adc %rdx,%r9
+ mov %r8,%rdx
+ sar $63,%rdx
+ cmp %r9,%rdx
+ je 2f
+ imul %r10,%rsi
mov %rdi,%rax
- imul %rdx,%rsi
imul %rdi,%rcx
- add %rsi,%rcx
- mul %rdx
- jc 1f
- add %rcx,%rdx
- jo 1f
+ mul %r10
+ lea (%rsi,%rcx),%r8
+ add %rdx,%r8
+ mov %rax,%r14
+ jmp 3f
+7: mov %rsi,%r8
+ mov %rcx,%rdx
+ mov %rdi,%rax
+ imul %rdi,%rdx
+ imul %r10,%r8
+ add %rdx,%r8
+ mul %r10
+ mov %rax,%r14
+ lea 1(%rsi),%rax
+ add %rdx,%r8
+ cmp $1,%rax
+ ja 3f
+ lea 1(%rcx),%rax
+ cmp $1,%rax
+ ja 3f
+ cmp %rcx,%rsi
+ jne 11f
+ cmp %r14,%r11
+ mov %r11,%rax
+ sbb %r8,%rax
+ jl 2f
+ jmp 3f
+11: test %r8,%r8
+ js 2f
+3: mov $1,%r11d
+2: test %r11,%r11
+ je 1f
+ mov %r8,-8(%rbp)
+ call __on_arithmetic_overflow
+ mov -8(%rbp),%r8
+1: mov %r14,%rax
+ mov %r8,%rdx
+ pop %r15
+ pop %r14
+ pop %r13
+ pop %r12
+ pop %rbx
+ leave
ret
-1: jmp __on_arithmetic_overflow
.endfn __mulvti3,globl
diff --git a/libc/stubs/negvdi2.S b/libc/stubs/negvdi2.S
index 35b318d6e..04e5a4dc4 100644
--- a/libc/stubs/negvdi2.S
+++ b/libc/stubs/negvdi2.S
@@ -18,9 +18,9 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.align 16
-.text.likely
.source __FILE__
+.privileged
+.alignfunc
/ Returns -𝑥, aborting on overflow (two's complement bane).
/
diff --git a/libc/stubs/negvsi2.S b/libc/stubs/negvsi2.S
index 17498f221..86da30041 100644
--- a/libc/stubs/negvsi2.S
+++ b/libc/stubs/negvsi2.S
@@ -18,9 +18,9 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.align 16
-.text.likely
.source __FILE__
+.privileged
+.alignfunc
/ Returns -𝑥, aborting on overflow (two's complement bane).
/
diff --git a/libc/stubs/negvti2.S b/libc/stubs/negvti2.S
index 77895fea1..a9d95dbfd 100644
--- a/libc/stubs/negvti2.S
+++ b/libc/stubs/negvti2.S
@@ -18,9 +18,9 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.align 16
-.text.likely
.source __FILE__
+.privileged
+.alignfunc
/ Returns -𝑥, aborting on overflow.
/
@@ -31,11 +31,14 @@ __negvti2:
mov %rdi,%rax
mov %rsi,%rdx
neg %rax
+ not %rdx
+ cmc
adc $0,%rdx
-/ TODO(jart): Make sure this is correct.
- jo 1f
- neg %rdx
jo 1f
ret
-1: jmp __on_arithmetic_overflow
+1: push %rbp
+ mov %rsp,%rbp
+ call __on_arithmetic_overflow
+ pop %rbp
+ ret
.endfn __negvti2,globl
diff --git a/libc/stubs/onarithmeticoverflow.S b/libc/stubs/onarithmeticoverflow.S
index 6f537e325..fc6738ad9 100644
--- a/libc/stubs/onarithmeticoverflow.S
+++ b/libc/stubs/onarithmeticoverflow.S
@@ -18,12 +18,10 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.text.unlikely
+.privileged
.source __FILE__
/ Arithmetic overflow handler.
-/
-/ @see libc/testlib/arithmeticoverflowhook.S
/ @see -ftrapv
__on_arithmetic_overflow:
push %rbp
diff --git a/libc/stubs/panic.S b/libc/stubs/panic.S
index 4ccc602b5..6c7e20020 100644
--- a/libc/stubs/panic.S
+++ b/libc/stubs/panic.S
@@ -30,4 +30,4 @@ panic: push %bp
cli
1: hlt
jmp 1b
- .endfn panic,weak,protected
+ .endfn panic,weak
diff --git a/libc/runtime/munmap-thunk.S b/libc/stubs/retpoline.S
similarity index 92%
rename from libc/runtime/munmap-thunk.S
rename to libc/stubs/retpoline.S
index 7ed2c55ff..55d2d0cb0 100644
--- a/libc/runtime/munmap-thunk.S
+++ b/libc/stubs/retpoline.S
@@ -18,16 +18,9 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.source __FILE__
-munmap: push %rbp
- mov %rsp,%rbp
- push %rbx
- push %rbx
- ezlea _base,bx
- call __munmap
- pop %rbx
- pop %rbx
- pop %rbp
+/ See -mfunction-return=thunk
+__x86_return_thunk:
ret
- .endfn munmap,globl
+ .endfn __x86_return_thunk,weak
+ .source __FILE__
diff --git a/libc/sysv/stackchkguard.S b/libc/stubs/stackchkguard.S
similarity index 85%
rename from libc/sysv/stackchkguard.S
rename to libc/stubs/stackchkguard.S
index 2099c060a..e8069fec8 100644
--- a/libc/sysv/stackchkguard.S
+++ b/libc/stubs/stackchkguard.S
@@ -19,7 +19,6 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
#include "libc/notice.inc"
-#include "libc/sysv/consts/auxv.h"
.source __FILE__
/ Canary for -fstack-protector.
@@ -28,24 +27,15 @@
/ The -mstack-protector-guard=global flag might need to be passed.
/
/ @note this value is protected by piro
- .initbss 301,_init___stack_chk_guard
+ .initbss 200,_init___stack_chk_guard
__stack_chk_guard:
.quad 0
.endobj __stack_chk_guard,globl
.previous
.init.start 301,_init___stack_chk_guard
- mov kStartTsc(%rip),%rax
- mov AT_RANDOM,%edx # modern linux provides this
- test %edx,%edx
- jz 1f
- pushpop -16,%rcx
-0: add $16,%rcx # ax ^= di[1][0] if di[0]=AT_RANDOM
- cmpq $0,(%r15,%rcx) # %r15 is qword(*auxv)[2]
- jz 1f # @see libc/crt/crt.S
- cmp %rdx,(%r15,%rcx)
- jne 0b
- mov 8(%r15,%rcx),%rcx
- xor (%rcx),%rax
-1: stosq
+ rdtsc
+ stosl
+ xchg %edx,%eax
+ stosl
.init.end 301,_init___stack_chk_guard
diff --git a/libc/stubs/stubs.mk b/libc/stubs/stubs.mk
index 397eefdad..b61c9baf6 100644
--- a/libc/stubs/stubs.mk
+++ b/libc/stubs/stubs.mk
@@ -17,7 +17,7 @@ PKGS += LIBC_STUBS
LIBC_STUBS_ARTIFACTS += LIBC_STUBS_A
LIBC_STUBS = $(LIBC_STUBS_A)
-LIBC_STUBS_A = o/libc/stubs/stubs.a
+LIBC_STUBS_A = o/$(MODE)/libc/stubs/stubs.a
LIBC_STUBS_A_FILES := $(wildcard libc/stubs/*.S)
LIBC_STUBS_A_HDRS = \
@@ -28,7 +28,7 @@ LIBC_STUBS_A_SRCS = \
LIBC_STUBS_A_OBJS = \
$(LIBC_STUBS_A_SRCS:%=o/$(MODE)/%.zip.o) \
- $(LIBC_STUBS_A_SRCS:%.S=o/%.o)
+ $(LIBC_STUBS_A_SRCS:%.S=o/$(MODE)/%.o)
LIBC_STUBS_A_CHECKS = \
$(LIBC_STUBS_A).pkg \
@@ -47,7 +47,7 @@ LIBC_STUBS_LIBS = $(foreach x,$(LIBC_STUBS_ARTIFACTS),$($(x)))
LIBC_STUBS_SRCS = $(foreach x,$(LIBC_STUBS_ARTIFACTS),$($(x)_SRCS))
LIBC_STUBS_CHECKS = $(foreach x,$(LIBC_STUBS_ARTIFACTS),$($(x)_CHECKS))
LIBC_STUBS_OBJS = $(foreach x,$(LIBC_STUBS_ARTIFACTS),$($(x)_OBJS))
-$(LIBC_STUBS_OBJS): $(BUILD_FILES) libc/stubs/stubs.mk
-.PHONY: o/libc/stubs
-o/libc/stubs: $(LIBC_STUBS_CHECKS)
+.PHONY: o/$(MODE)/libc/stubs
+o/$(MODE)/libc/stubs: \
+ $(LIBC_STUBS_CHECKS)
diff --git a/libc/stubs/subvdi3.S b/libc/stubs/subvdi3.S
index 7a3fde19d..5d2b857d3 100644
--- a/libc/stubs/subvdi3.S
+++ b/libc/stubs/subvdi3.S
@@ -18,9 +18,9 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.align 16
-.text.likely
.source __FILE__
+.privileged
+.alignfunc
/ Returns 𝑥-𝑦, aborting on overflow.
/
diff --git a/libc/stubs/subvsi3.S b/libc/stubs/subvsi3.S
index 91046921c..0b5c3d69f 100644
--- a/libc/stubs/subvsi3.S
+++ b/libc/stubs/subvsi3.S
@@ -18,9 +18,9 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.align 16
-.text.likely
.source __FILE__
+.privileged
+.alignfunc
/ Returns 𝑥-𝑦, aborting on overflow.
/
diff --git a/libc/stubs/subvti3.S b/libc/stubs/subvti3.S
index 5c59b966d..5b3cc09fc 100644
--- a/libc/stubs/subvti3.S
+++ b/libc/stubs/subvti3.S
@@ -18,9 +18,9 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.align 16
-.text.likely
.source __FILE__
+.privileged
+.alignfunc
/ Returns 𝑥-𝑦, aborting on overflow.
/
@@ -30,8 +30,9 @@
/ @see -ftrapv
__subvti3:
mov %rdi,%rax
- sub %rsi,%rax
- sbb %rdi,%rdx
+ sub %rdx,%rax
+ mov %rsi,%rdx
+ sbb %rcx,%rdx
jo 1f
ret
1: push %rbp
diff --git a/libc/nexgen32e/sysv2nt.S b/libc/stubs/sysv2nt.S
similarity index 100%
rename from libc/nexgen32e/sysv2nt.S
rename to libc/stubs/sysv2nt.S
diff --git a/libc/stubs/zip.S b/libc/stubs/zip.S
index 6c9e860cd..4348e00c7 100644
--- a/libc/stubs/zip.S
+++ b/libc/stubs/zip.S
@@ -18,9 +18,33 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
+#include "ape/relocations.h"
+#include "libc/zip.h"
+/ ZIP Central Directory.
+ .section .piro.data.sort.zip.3,"a",@progbits
+ .hidden __zip_start
+ .globl __zip_start
+ .type __zip_start,@object
+ .align kZipCdirAlign
__zip_start:
- .endfn __zip_start,weak
-
+ .previous/*
+ ...
+ decentralized content
+ ...
+ */.section .piro.data.sort.zip.5,"a",@progbits
+ .align kZipCdirAlign
__zip_end:
- .endfn __zip_end,weak
+ .long kZipCdirHdrMagic # magic
+ .short 0 # disk
+ .short 0 # starting disk
+ .short v_zip_records # records on disk
+ .short v_zip_records # records
+ .long v_zip_cdirsize # size of central directory
+ .long RVA(__zip_start) # central directory offset
+ .short v_zip_commentsize # comment size
+ .endobj __zip_end,globl,hidden
+ .weak v_zip_records
+ .weak v_zip_cdirsize
+ .weak v_zip_commentsize
+ .previous
diff --git a/libc/sysv/calls/__utimensat-sysv.s b/libc/sysv/calls/__utimensat-sysv.s
new file mode 100644
index 000000000..dc63f6d80
--- /dev/null
+++ b/libc/sysv/calls/__utimensat-sysv.s
@@ -0,0 +1,2 @@
+.include "o/libc/sysv/macros.inc"
+.scall __utimensat$sysv 0x00540223ffff0118 globl hidden
diff --git a/libc/sysv/calls/futimens-sysv.s b/libc/sysv/calls/futimens-sysv.s
new file mode 100644
index 000000000..083893909
--- /dev/null
+++ b/libc/sysv/calls/futimens-sysv.s
@@ -0,0 +1,2 @@
+.include "o/libc/sysv/macros.inc"
+.scall futimens$sysv 0x00550222ffffffff globl hidden
diff --git a/libc/sysv/calls/futimens.s b/libc/sysv/calls/futimens.s
deleted file mode 100644
index f4df3fb17..000000000
--- a/libc/sysv/calls/futimens.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "o/libc/sysv/macros.inc"
-.scall futimens 0x00550222ffffffff globl
diff --git a/libc/sysv/calls/futimes-sysv.s b/libc/sysv/calls/futimes-sysv.s
new file mode 100644
index 000000000..b2a9f9167
--- /dev/null
+++ b/libc/sysv/calls/futimes-sysv.s
@@ -0,0 +1,2 @@
+.include "o/libc/sysv/macros.inc"
+.scall futimes$sysv 0x004d00ce208bffff globl hidden
diff --git a/libc/sysv/calls/futimes.s b/libc/sysv/calls/futimes.s
deleted file mode 100644
index d4e0336cf..000000000
--- a/libc/sysv/calls/futimes.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "o/libc/sysv/macros.inc"
-.scall futimes 0x004d00ce208bffff globl
diff --git a/libc/sysv/calls/futimesat-sysv.s b/libc/sysv/calls/futimesat-sysv.s
new file mode 100644
index 000000000..c1afdd06b
--- /dev/null
+++ b/libc/sysv/calls/futimesat-sysv.s
@@ -0,0 +1,2 @@
+.include "o/libc/sysv/macros.inc"
+.scall futimesat$sysv 0xffff01eeffff0105 globl hidden
diff --git a/libc/sysv/calls/futimesat.s b/libc/sysv/calls/futimesat.s
deleted file mode 100644
index 59605f0eb..000000000
--- a/libc/sysv/calls/futimesat.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "o/libc/sysv/macros.inc"
-.scall futimesat 0xffff01eeffff0105 globl hidden
diff --git a/libc/sysv/calls/utimensat.s b/libc/sysv/calls/utimensat.s
deleted file mode 100644
index 89fe61031..000000000
--- a/libc/sysv/calls/utimensat.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "o/libc/sysv/macros.inc"
-.scall utimensat 0x00540223ffff0118 globl hidden
diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh
index e8221fd4b..5b68920cd 100755
--- a/libc/sysv/consts.sh
+++ b/libc/sysv/consts.sh
@@ -410,15 +410,21 @@ syscon fcntl F_GETPIPE_SZ 0x0408 0 0 0 0
#
# group name GNU/Systemd XNU's Not UNIX FreeBSD OpenBSD XENIX Commentary
syscon at AT_FDCWD -100 -2 -100 -100 -100 # faked nt
-syscon at AT_SYMLINK_FOLLOW 0x0400 0x40 0x0400 4 0
syscon at AT_SYMLINK_NOFOLLOW 0x0100 0x20 0x0200 2 0 # TODO(jart): What should NT do?
syscon at AT_REMOVEDIR 0x0200 0x80 0x0800 8 0x0200 # faked nt
syscon at AT_EACCESS 0x0200 0x10 0x0100 1 0
+syscon at AT_SYMLINK_FOLLOW 0x0400 0x40 0x0400 4 0
syscon at AT_EMPTY_PATH 0x1000 0 0 0 0 # linux 2.6.39+; see unlink, O_TMPFILE, etc.
syscon memfd MFD_CLOEXEC 1 0 0 0 0
syscon memfd MFD_ALLOW_SEALING 2 0 0 0 0
+# utimensat()
+#
+# group name GNU/Systemd XNU's Not UNIX FreeBSD OpenBSD XENIX Commentary
+syscon utime UTIME_NOW 0x3fffffff 0x3fffffff -1 -2 0x3fffffff # polyfilled xnu/nt
+syscon utime UTIME_OMIT 0x3ffffffe 0x3ffffffe -2 -1 0x3ffffffe # polyfilled xnu/nt
+
# getauxval() keys
#
# group name GNU/Systemd XNU's Not UNIX FreeBSD OpenBSD XENIX Commentary
@@ -2701,9 +2707,6 @@ syscon misc T_FMT_AMPM 0x02002b 4 4 3 0
syscon misc UL_GETFSIZE 1 1 1 0 0
syscon misc UL_SETFSIZE 2 2 2 0 0
-syscon misc UTIME_NOW 0x3fffffff 0 -1 -2 0
-syscon misc UTIME_OMIT 0x3ffffffe 0 -2 -1 0
-
syscon misc XATTR_CREATE 1 2 0 0 0
syscon misc XATTR_REPLACE 2 4 0 0 0
diff --git a/libc/sysv/consts/GLOB_ABORTED.s b/libc/sysv/consts/GLOB_ABORTED.s
deleted file mode 100644
index 2c5a73568..000000000
--- a/libc/sysv/consts/GLOB_ABORTED.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "libc/sysv/consts/syscon.inc"
-.syscon glob GLOB_ABORTED 2 -2 -2 -2 0
diff --git a/libc/sysv/consts/GLOB_APPEND.s b/libc/sysv/consts/GLOB_APPEND.s
deleted file mode 100644
index 51eb92357..000000000
--- a/libc/sysv/consts/GLOB_APPEND.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "libc/sysv/consts/syscon.inc"
-.syscon glob GLOB_APPEND 0x20 1 1 1 0
diff --git a/libc/sysv/consts/GLOB_DOOFFS.s b/libc/sysv/consts/GLOB_DOOFFS.s
deleted file mode 100644
index 21265c623..000000000
--- a/libc/sysv/consts/GLOB_DOOFFS.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "libc/sysv/consts/syscon.inc"
-.syscon glob GLOB_DOOFFS 8 2 2 2 0
diff --git a/libc/sysv/consts/GLOB_ERR.s b/libc/sysv/consts/GLOB_ERR.s
deleted file mode 100644
index 54e71bfd1..000000000
--- a/libc/sysv/consts/GLOB_ERR.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "libc/sysv/consts/syscon.inc"
-.syscon glob GLOB_ERR 1 4 4 4 0
diff --git a/libc/sysv/consts/GLOB_MARK.s b/libc/sysv/consts/GLOB_MARK.s
deleted file mode 100644
index e59b22a44..000000000
--- a/libc/sysv/consts/GLOB_MARK.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "libc/sysv/consts/syscon.inc"
-.syscon glob GLOB_MARK 2 8 8 8 0
diff --git a/libc/sysv/consts/GLOB_NOCHECK.s b/libc/sysv/consts/GLOB_NOCHECK.s
deleted file mode 100644
index fd1e2341a..000000000
--- a/libc/sysv/consts/GLOB_NOCHECK.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "libc/sysv/consts/syscon.inc"
-.syscon glob GLOB_NOCHECK 0x10 0x10 0x10 0x10 0
diff --git a/libc/sysv/consts/GLOB_NOESCAPE.s b/libc/sysv/consts/GLOB_NOESCAPE.s
deleted file mode 100644
index 649644ae7..000000000
--- a/libc/sysv/consts/GLOB_NOESCAPE.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "libc/sysv/consts/syscon.inc"
-.syscon glob GLOB_NOESCAPE 0x40 0x2000 0x2000 0x1000 0
diff --git a/libc/sysv/consts/GLOB_NOMATCH.s b/libc/sysv/consts/GLOB_NOMATCH.s
deleted file mode 100644
index 19cba7629..000000000
--- a/libc/sysv/consts/GLOB_NOMATCH.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "libc/sysv/consts/syscon.inc"
-.syscon glob GLOB_NOMATCH 3 -3 -3 -3 0
diff --git a/libc/sysv/consts/GLOB_NOSORT.s b/libc/sysv/consts/GLOB_NOSORT.s
deleted file mode 100644
index 67112d817..000000000
--- a/libc/sysv/consts/GLOB_NOSORT.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "libc/sysv/consts/syscon.inc"
-.syscon glob GLOB_NOSORT 4 0x20 0x20 0x20 0
diff --git a/libc/sysv/consts/GLOB_NOSPACE.s b/libc/sysv/consts/GLOB_NOSPACE.s
deleted file mode 100644
index 15e3a0e82..000000000
--- a/libc/sysv/consts/GLOB_NOSPACE.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "libc/sysv/consts/syscon.inc"
-.syscon glob GLOB_NOSPACE 1 -1 -1 -1 0
diff --git a/libc/sysv/consts/GLOB_NOSYS.s b/libc/sysv/consts/GLOB_NOSYS.s
deleted file mode 100644
index 2d74a61d8..000000000
--- a/libc/sysv/consts/GLOB_NOSYS.s
+++ /dev/null
@@ -1,2 +0,0 @@
-.include "libc/sysv/consts/syscon.inc"
-.syscon glob GLOB_NOSYS 4 -4 -4 -4 0
diff --git a/libc/sysv/consts/UTIME_NOW.s b/libc/sysv/consts/UTIME_NOW.s
index 509c26430..610beeb16 100644
--- a/libc/sysv/consts/UTIME_NOW.s
+++ b/libc/sysv/consts/UTIME_NOW.s
@@ -1,2 +1,2 @@
.include "libc/sysv/consts/syscon.inc"
-.syscon misc UTIME_NOW 0x3fffffff 0 -1 -2 0
+.syscon utime UTIME_NOW 0x3fffffff 0x3fffffff -1 -2 0x3fffffff
diff --git a/libc/sysv/consts/UTIME_OMIT.s b/libc/sysv/consts/UTIME_OMIT.s
index db9491736..6a6d6d3ac 100644
--- a/libc/sysv/consts/UTIME_OMIT.s
+++ b/libc/sysv/consts/UTIME_OMIT.s
@@ -1,2 +1,2 @@
.include "libc/sysv/consts/syscon.inc"
-.syscon misc UTIME_OMIT 0x3ffffffe 0 -2 -1 0
+.syscon utime UTIME_OMIT 0x3ffffffe 0x3ffffffe -2 -1 0x3ffffffe
diff --git a/libc/sysv/consts/at.h b/libc/sysv/consts/at.h
index 5503c6d76..aee4dfc32 100644
--- a/libc/sysv/consts/at.h
+++ b/libc/sysv/consts/at.h
@@ -14,14 +14,16 @@ hidden extern const long AT_SYMLINK_FOLLOW;
hidden extern const long AT_SYMLINK_NOFOLLOW;
hidden extern const long AT_REMOVEDIR;
hidden extern const long AT_EACCESS;
+hidden extern const long AT_EMPTY_PATH;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#define AT_FDCWD SYMBOLIC(AT_FDCWD)
-#define AT_SYMLINK_FOLLOW SYMBOLIC(AT_SYMLINK_FOLLOW)
+#define AT_FDCWD SYMBOLIC(AT_FDCWD)
+#define AT_SYMLINK_FOLLOW SYMBOLIC(AT_SYMLINK_FOLLOW)
#define AT_SYMLINK_NOFOLLOW SYMBOLIC(AT_SYMLINK_NOFOLLOW)
-#define AT_REMOVEDIR SYMBOLIC(AT_REMOVEDIR)
-#define AT_EACCESS SYMBOLIC(AT_EACCESS)
+#define AT_REMOVEDIR SYMBOLIC(AT_REMOVEDIR)
+#define AT_EACCESS SYMBOLIC(AT_EACCESS)
+#define AT_EMPTY_PATH SYMBOLIC(AT_EMPTY_PATH)
#endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_AT_H_ */
diff --git a/libc/sysv/consts/auxv.h b/libc/sysv/consts/auxv.h
index 7c2a94359..3b32bc3cf 100644
--- a/libc/sysv/consts/auxv.h
+++ b/libc/sysv/consts/auxv.h
@@ -9,7 +9,6 @@ hidden extern const long AT_BASE_PLATFORM;
hidden extern const long AT_CLKTCK;
hidden extern const long AT_DCACHEBSIZE;
hidden extern const long AT_EGID;
-hidden extern const long AT_EMPTY_PATH;
hidden extern const long AT_ENTRY;
hidden extern const long AT_EUID;
hidden extern const long AT_EXECFD;
@@ -33,30 +32,29 @@ hidden extern const long AT_UID;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#define AT_BASE SYMBOLIC(AT_BASE)
+#define AT_BASE SYMBOLIC(AT_BASE)
#define AT_BASE_PLATFORM SYMBOLIC(AT_BASE_PLATFORM)
-#define AT_CLKTCK SYMBOLIC(AT_CLKTCK)
-#define AT_DCACHEBSIZE SYMBOLIC(AT_DCACHEBSIZE)
-#define AT_EGID SYMBOLIC(AT_EGID)
-#define AT_EMPTY_PATH SYMBOLIC(AT_EMPTY_PATH)
-#define AT_ENTRY SYMBOLIC(AT_ENTRY)
-#define AT_EUID SYMBOLIC(AT_EUID)
-#define AT_EXECFD SYMBOLIC(AT_EXECFD)
-#define AT_EXECFN SYMBOLIC(AT_EXECFN)
-#define AT_GID SYMBOLIC(AT_GID)
-#define AT_ICACHEBSIZE SYMBOLIC(AT_ICACHEBSIZE)
-#define AT_NOTELF SYMBOLIC(AT_NOTELF)
-#define AT_NO_AUTOMOUNT SYMBOLIC(AT_NO_AUTOMOUNT)
-#define AT_OSRELDATE SYMBOLIC(AT_OSRELDATE)
-#define AT_PAGESZ SYMBOLIC(AT_PAGESZ)
-#define AT_PHDR SYMBOLIC(AT_PHDR)
-#define AT_PHENT SYMBOLIC(AT_PHENT)
-#define AT_PHNUM SYMBOLIC(AT_PHNUM)
-#define AT_PLATFORM SYMBOLIC(AT_PLATFORM)
-#define AT_RANDOM SYMBOLIC(AT_RANDOM)
-#define AT_SECURE SYMBOLIC(AT_SECURE)
-#define AT_SYSINFO_EHDR SYMBOLIC(AT_SYSINFO_EHDR)
-#define AT_UCACHEBSIZE SYMBOLIC(AT_UCACHEBSIZE)
-#define AT_UID SYMBOLIC(AT_UID)
+#define AT_CLKTCK SYMBOLIC(AT_CLKTCK)
+#define AT_DCACHEBSIZE SYMBOLIC(AT_DCACHEBSIZE)
+#define AT_EGID SYMBOLIC(AT_EGID)
+#define AT_ENTRY SYMBOLIC(AT_ENTRY)
+#define AT_EUID SYMBOLIC(AT_EUID)
+#define AT_EXECFD SYMBOLIC(AT_EXECFD)
+#define AT_EXECFN SYMBOLIC(AT_EXECFN)
+#define AT_GID SYMBOLIC(AT_GID)
+#define AT_ICACHEBSIZE SYMBOLIC(AT_ICACHEBSIZE)
+#define AT_NOTELF SYMBOLIC(AT_NOTELF)
+#define AT_NO_AUTOMOUNT SYMBOLIC(AT_NO_AUTOMOUNT)
+#define AT_OSRELDATE SYMBOLIC(AT_OSRELDATE)
+#define AT_PAGESZ SYMBOLIC(AT_PAGESZ)
+#define AT_PHDR SYMBOLIC(AT_PHDR)
+#define AT_PHENT SYMBOLIC(AT_PHENT)
+#define AT_PHNUM SYMBOLIC(AT_PHNUM)
+#define AT_PLATFORM SYMBOLIC(AT_PLATFORM)
+#define AT_RANDOM SYMBOLIC(AT_RANDOM)
+#define AT_SECURE SYMBOLIC(AT_SECURE)
+#define AT_SYSINFO_EHDR SYMBOLIC(AT_SYSINFO_EHDR)
+#define AT_UCACHEBSIZE SYMBOLIC(AT_UCACHEBSIZE)
+#define AT_UID SYMBOLIC(AT_UID)
#endif /* COSMOPOLITAN_LIBC_CALLS_AUXV_H_ */
diff --git a/libc/sysv/consts/utime.h b/libc/sysv/consts/utime.h
new file mode 100644
index 000000000..1a1342809
--- /dev/null
+++ b/libc/sysv/consts/utime.h
@@ -0,0 +1,16 @@
+#ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_UTIME_H_
+#define COSMOPOLITAN_LIBC_SYSV_CONSTS_UTIME_H_
+#include "libc/runtime/symbolic.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+hidden extern const int UTIME_NOW;
+hidden extern const int UTIME_OMIT;
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+
+#define UTIME_NOW SYMBOLIC(UTIME_NOW)
+#define UTIME_OMIT SYMBOLIC(UTIME_OMIT)
+
+#endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_UTIME_H_ */
diff --git a/libc/sysv/syscall.S b/libc/sysv/syscall.S
index d85e2ef3d..0a60b846d 100644
--- a/libc/sysv/syscall.S
+++ b/libc/sysv/syscall.S
@@ -18,6 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
+.source __FILE__
/ Performs raw System Five system call.
/
@@ -41,4 +42,3 @@ syscall:mov %rdi,%rax
mov 8(%rsp),%r9
jmp *systemfive(%rip)
.endfn syscall,globl
- .source __FILE__
diff --git a/libc/sysv/syscalls.sh b/libc/sysv/syscalls.sh
index 87a37ffcd..e6a9c1169 100755
--- a/libc/sysv/syscalls.sh
+++ b/libc/sysv/syscalls.sh
@@ -184,8 +184,6 @@ scall settimeofday 0x0044007a207a00a4 globl
scall mount 0x0015001520a700a5 globl
scall reboot 0x00370037203700a9 globl
scall quotactl 0x0094009420a500b3 globl
-scall 'utime$sysv' 0xffffffffffff0084 globl hidden
-scall 'utimes$sysv' 0x004c008a208a00eb globl hidden
scall setfsuid 0xffffffffffff007a globl
scall setfsgid 0xffffffffffff007b globl
scall capget 0xffffffffffff007d globl
@@ -282,7 +280,11 @@ scall inotify_rm_watch 0xffffffffffff00ff globl
scall 'openat$sysv' 0x014101f321cf0101 globl hidden # Linux 2.6.16+ (c. 2007)
scall 'mkdirat$sysv' 0x013e01f021db0102 globl hidden
scall 'fchownat$sysv' 0x013b01eb21d40104 globl hidden # @asyncsignalsafe
-scall futimesat 0xffff01eeffff0105 globl hidden # @asyncsignalsafe
+scall 'utime$sysv' 0xffffffffffff0084 globl hidden
+scall 'utimes$sysv' 0x004c008a208a00eb globl hidden
+scall 'futimesat$sysv' 0xffff01eeffff0105 globl hidden # @asyncsignalsafe
+scall 'futimes$sysv' 0x004d00ce208bffff globl hidden
+scall 'futimens$sysv' 0x00550222ffffffff globl hidden
scall '__fstatat$sysv' 0x002a022821d60106 globl hidden # a.k.a. newfstatat(); FreeBSD 12+; needs stat2linux()
scall 'unlinkat$sysv' 0x014501f721d80107 globl hidden
scall 'renameat$sysv' 0x014301f521d10108 globl hidden
@@ -301,7 +303,7 @@ scall move_pages 0xffffffffffff0117 globl # NOTE: We view Red Hat versio
#──────────────────────RHEL 5.0 LIMIT──────────────────────── # ←┬─ last gplv2 distro w/ sysv init was rhel5 c. 2007
scall '__preadv$sysv' 0x010b0121ffff0127 globl hidden # ├─ cosmopolitan at minimum requires rhel5
scall '__pwritev$sysv' 0x010c0122ffff0128 globl hidden # ├─ python modules need to work on this (pep513)
-scall utimensat 0x00540223ffff0118 globl hidden # └─ end of life 2020-11-30 (extended)
+scall '__utimensat$sysv' 0x00540223ffff0118 globl hidden # └─ end of life 2020-11-30 (extended)
scall 'fallocate$sysv' 0xffffffffffff011d globl hidden
scall 'posix_fallocate$sysv' 0xffff0212ffffffff globl hidden
scall '__accept4$sysv' 0x005d021dffff0120 globl hidden # Linux 2.6.28+
@@ -380,7 +382,6 @@ scall getfh 0x00a100a120a1ffff globl
scall chflags 0x002200222022ffff globl
scall getfsstat 0x003e022d215bffff globl
scall nfssvc 0x009b009b209bffff globl
-scall futimes 0x004d00ce208bffff globl
scall adjtime 0x008c008c208cffff globl
scall fchflags 0x002300232023ffff globl
scall '__seteuid$bsd' 0x00b700b720b7ffff globl hidden # wrapped via setreuid()
@@ -424,7 +425,6 @@ scall auditon 0xffff01be215fffff globl
scall msgsys 0xffff00aa20fcffff globl
scall shmsys 0xffff00ab20fdffff globl
#─────────────────────FREEBSD & OPENBSD──────────────────────
-scall futimens 0x00550222ffffffff globl
scall fhstat 0x01260229ffffffff globl
scall chflagsat 0x006b021cffffffff globl
scall profil 0x002c002cffffffff globl
diff --git a/libc/sysv/systemfive.S b/libc/sysv/systemfive.S
index 21b19846e..24b0666aa 100644
--- a/libc/sysv/systemfive.S
+++ b/libc/sysv/systemfive.S
@@ -97,7 +97,10 @@ systemfive.linux:
test %eax,%eax
js systemfive.enosys
mov %rcx,%r10
+ push %rbp
+ mov %rsp,%rbp
syscall
+ pop %rbp
cmp $-4095,%rax
jae systemfive.error
ret
@@ -123,7 +126,10 @@ systemfive.bsd:
cmp $0xfff,%ax
jae systemfive.enosys
mov %rcx,%r10
+ push %rbp
+ mov %rsp,%rbp
syscall
+ pop %rbp
jc systemfive.errno
ret
systemfive.xnu:
@@ -160,52 +166,62 @@ systemfive.xnu:
push %rbx
push %rsi
testb $XNU,(%rdi) # @see libc/crt/crt.S
- jnz .Lsystemfive.init.xnu
+ jnz systemfive.init.xnu
testb $FREEBSD,(%rdi) # @see libc/crt/crt.S
- jnz .Lsystemfive.init.freebsd
+ jnz systemfive.init.freebsd
testb $WINDOWS,(%rdi) # @see libc/runtime/winmain.c
- jnz .Lsystemfive.init.windows
+ jnz systemfive.init.windows
cmpq $0,(%r15) # OpenBSD doesn't have auxv
- je .Lsystemfive.init.openbsd
+ je systemfive.init.openbsd
/ default state is safe state
/ 𝑠𝑙𝑖𝑑𝑒
-.Lsystemfive.init.linux:
+systemfive.init.linux:
pushb systemfive.linux-.Lanchorpoint
push $LINUX
ezlea syscon.linux,si
- jmp .Lsystemfive.init.os
-.Lsystemfive.init.windows:
+ jmp systemfive.init.os
+systemfive.init.windows:
pushb systemfive.enosys-.Lanchorpoint
push $WINDOWS
ezlea syscon.windows,si
- jmp .Lsystemfive.init.os
-.Lsystemfive.init.freebsd:
+ jmp systemfive.init.os
+systemfive.init.freebsd:
pushb systemfive.freebsd-.Lanchorpoint
push $FREEBSD
ezlea syscon.freebsd,si
- jmp .Lsystemfive.init.os
-.Lsystemfive.init.openbsd:
+ jmp systemfive.init.os
+systemfive.init.openbsd:
pushb systemfive.openbsd-.Lanchorpoint
push $OPENBSD
ezlea syscon.openbsd,si
- jmp .Lsystemfive.init.os
-.Lsystemfive.init.xnu:
+ jmp systemfive.init.os
+systemfive.init.xnu:
pushb systemfive.xnu-.Lanchorpoint
push $XNU
ezlea syscon.xnu,si
/ 𝑠𝑙𝑖𝑑𝑒
-.Lsystemfive.init.os:
+systemfive.init.os:
ezlea .Lanchorpoint,cx
pop %rax
stosq #→ hostos
pop %rax
add %rcx,%rax
stosq #→ systemfive
-.Lsystemfive.sleb128unpacker:
push %rdi
- ezlea .Lsyscon.start,di
- orq $-1,%r9
- ezlea .Lsyscon.end,bx
+ ezlea syscon.start,di
+ ezlea syscon.end,bx
+ call systemfive.sleb128unpacker
+ pop %rdi
+/ 𝑠𝑙𝑖𝑑𝑒
+systemfive.init.done:
+ pop %rsi
+ pop %rbx
+ .init.end 300,_init_systemfive,globl,hidden
+
+ .text.startup
+systemfive.sleb128unpacker:
+ .leafprologue
+ or $-1,%r9
2: cmp %rbx,%rdi
jnb 5f
xor %ecx,%ecx
@@ -228,12 +244,8 @@ systemfive.xnu:
cmovne (%rdi),%rax # @see WinMain() for example
stosq
jmp 2b
-5: pop %rdi
-/ 𝑠𝑙𝑖𝑑𝑒
-.Lsystemfive.init.done:
- pop %rsi
- pop %rbx
- .init.end 300,_init_systemfive,globl,hidden
+5: .leafepilogue
+ .previous
/ Sections for varint encoded numbers.
/
@@ -244,11 +256,11 @@ systemfive.xnu:
/ @see libc/sysv/consts/syscon.h
.section .piro.bss.sort.syscon.1,"aw",@nobits
.align 8
-.Lsyscon.start:/*
+syscon.start:/*
...decentralized quadwords...
*/.previous
.section .piro.bss.sort.syscon.3,"aw",@nobits
-.Lsyscon.end:
+syscon.end:
.previous
.section .sort.rodata.syscon.linux.1,"a",@progbits
.align 1
@@ -275,10 +287,19 @@ syscon.openbsd:/*
syscon.windows:/*
...decentralized leb128...
*/.previous
- .type .Lsyscon.start,@object
- .type .Lsyscon.end,@object
+
+ .type syscon.start,@object
+ .type syscon.end,@object
.type syscon.linux,@object
.type syscon.xnu,@object
.type syscon.freebsd,@object
.type syscon.openbsd,@object
.type syscon.windows,@object
+
+ .globl syscon.start
+ .globl syscon.end
+ .globl syscon.linux
+ .globl syscon.xnu
+ .globl syscon.freebsd
+ .globl syscon.openbsd
+ .globl syscon.windows
diff --git a/libc/sysv/sysv.mk b/libc/sysv/sysv.mk
index b42b2ae46..76a72e972 100644
--- a/libc/sysv/sysv.mk
+++ b/libc/sysv/sysv.mk
@@ -25,15 +25,13 @@ LIBC_SYSV_A_SRCS_S = $(filter %.S,$(LIBC_SYSV_A_FILES))
LIBC_SYSV_A_CHECKS = $(LIBC_SYSV_A).pkg
LIBC_SYSV_A_DIRECTDEPS = \
- LIBC_STUBS \
- LIBC_NEXGEN32E
+ LIBC_STUBS
LIBC_SYSV_A_FILES := \
libc/sysv/macros.h \
libc/sysv/errfuns.h \
libc/sysv/g_syscount.S \
libc/sysv/restorert.S \
- libc/sysv/stackchkguard.S \
libc/sysv/syscall.S \
libc/sysv/systemfive.S \
$(wildcard libc/sysv/stubs/*) \
@@ -45,9 +43,12 @@ LIBC_SYSV_A_SRCS = \
$(LIBC_SYSV_A_SRCS_S)
LIBC_SYSV_A_OBJS = \
- $(LIBC_SYSV_A_SRCS_A:%.s=o/%.o) \
+ $(LIBC_SYSV_A_SRCS_A:%.s=o/$(MODE)/%.o) \
$(LIBC_SYSV_A_SRCS_S:%.S=o/$(MODE)/%.o) \
- $(LIBC_SYSV_A_SRCS:%=o/$(MODE)/%.zip.o)
+ o/$(MODE)/libc/sysv/g_syscount.S.zip.o \
+ o/$(MODE)/libc/sysv/restorert.S.zip.o \
+ o/$(MODE)/libc/sysv/syscall.S.zip.o \
+ o/$(MODE)/libc/sysv/systemfive.S.zip.o
LIBC_SYSV_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_SYSV_A_DIRECTDEPS),$($(x))))
@@ -62,8 +63,7 @@ $(LIBC_SYSV_A).pkg: \
$(foreach x,$(LIBC_SYSV_A_DIRECTDEPS),$($(x)_A).pkg)
$(LIBC_SYSV_A_OBJS): \
- libc/sysv/consts/syscon.inc \
- libc/sysv/sysv.mk
+ libc/sysv/consts/syscon.inc
libc/sysv/consts/syscon.inc: libc/macros.inc
@@ -74,9 +74,9 @@ LIBC_SYSV_CALLS = \
$(LIBC_SYSV_CALLS_A)
LIBC_SYSV_ARTIFACTS += LIBC_SYSV_CALLS_A
-LIBC_SYSV_CALLS_A = o/libc/sysv/calls.a
+LIBC_SYSV_CALLS_A = o/$(MODE)/libc/sysv/calls.a
LIBC_SYSV_CALLS_A_SRCS := $(wildcard libc/sysv/calls/*.s)
-LIBC_SYSV_CALLS_A_OBJS = $(LIBC_SYSV_CALLS_A_SRCS:%.s=o/%.o)
+LIBC_SYSV_CALLS_A_OBJS = $(LIBC_SYSV_CALLS_A_SRCS:%.s=o/$(MODE)/%.o)
LIBC_SYSV_CALLS_A_CHECKS = $(LIBC_SYSV_CALLS_A).pkg
LIBC_SYSV_CALLS_A_DIRECTDEPS = \
@@ -86,7 +86,6 @@ LIBC_SYSV_CALLS_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_SYSV_CALLS_A_DIRECTDEPS),$($(x))))
$(LIBC_SYSV_CALLS_A): \
- libc/sysv/calls/ \
$(LIBC_SYSV_CALLS_A).pkg \
$(LIBC_SYSV_CALLS_A_OBJS)
@@ -104,9 +103,9 @@ LIBC_SYSV_MACHCALLS = \
$(LIBC_SYSV_MACHCALLS_A)
LIBC_SYSV_ARTIFACTS += LIBC_SYSV_MACHCALLS_A
-LIBC_SYSV_MACHCALLS_A = o/libc/sysv/machcalls.a
+LIBC_SYSV_MACHCALLS_A = o/$(MODE)/libc/sysv/machcalls.a
LIBC_SYSV_MACHCALLS_A_SRCS := $(wildcard libc/sysv/machcalls/*.s)
-LIBC_SYSV_MACHCALLS_A_OBJS = $(LIBC_SYSV_MACHCALLS_A_SRCS:%.s=o/%.o)
+LIBC_SYSV_MACHCALLS_A_OBJS = $(LIBC_SYSV_MACHCALLS_A_SRCS:%.s=o/$(MODE)/%.o)
LIBC_SYSV_MACHCALLS_A_CHECKS = $(LIBC_SYSV_MACHCALLS_A).pkg
LIBC_SYSV_MACHCALLS_A_DIRECTDEPS = \
@@ -129,8 +128,5 @@ $(LIBC_SYSV_MACHCALLS_A_OBJS): \
#───────────────────────────────────────────────────────────────────────────────
-.PHONY: o/libc/sysv
-o/libc/sysv: $(LIBC_SYSV_CHECKS)
-
.PHONY: o/$(MODE)/libc/sysv
o/$(MODE)/libc/sysv: $(LIBC_SYSV_CHECKS)
diff --git a/libc/testlib/almostequallongdouble.c b/libc/testlib/almostequallongdouble.c
index bfa974ef3..b50cc5be8 100644
--- a/libc/testlib/almostequallongdouble.c
+++ b/libc/testlib/almostequallongdouble.c
@@ -20,7 +20,7 @@
#include "libc/math.h"
#include "libc/testlib/testlib.h"
-#define EPSILON 0.0000000000001L
+#define EPSILON 0.00000001L
testonly bool testlib_almostequallongdouble(long double x, long double y) {
/* TODO(jart): This algorithm has to be binary. */
diff --git a/libc/testlib/bench.h b/libc/testlib/bench.h
index bb6d0f4d6..eea7c6e30 100644
--- a/libc/testlib/bench.h
+++ b/libc/testlib/bench.h
@@ -1,6 +1,5 @@
#ifndef COSMOPOLITAN_LIBC_BENCH_H_
#define COSMOPOLITAN_LIBC_BENCH_H_
-#include "libc/bits/safemacros.h"
#include "libc/nexgen32e/bench.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@@ -9,19 +8,19 @@ COSMOPOLITAN_C_START_
* @fileoverview Microbenchmarking tools.
*/
-#define BENCHLOOP(START, STOP, N, INIT, EXPR) \
- ({ \
- int Iter, Count; \
- long double Average, Sample, Time1, Time2; \
- for (Average = 1.0, Iter = 1, Count = (N); Iter < Count; ++Iter) { \
- INIT; \
- Time1 = START(); \
- EXPR; \
- Time2 = STOP(); \
- Sample = Time2 - Time1; \
- Average += (Sample - Average) / Iter; \
- } \
- Average; \
+#define BENCHLOOP(START, STOP, N, INIT, EXPR) \
+ ({ \
+ unsigned long Iter, Count; \
+ double Average, Sample, Time1, Time2; \
+ for (Average = 1, Iter = 1, Count = (N); Iter < Count; ++Iter) { \
+ INIT; \
+ Time1 = START(); \
+ EXPR; \
+ Time2 = STOP(); \
+ Sample = Time2 - Time1; \
+ Average += 1. / Iter * (Sample - Average); \
+ } \
+ Average; \
})
COSMOPOLITAN_C_END_
diff --git a/libc/testlib/clearxmmregisters.c b/libc/testlib/clearxmmregisters.c
new file mode 100644
index 000000000..1a0c5800d
--- /dev/null
+++ b/libc/testlib/clearxmmregisters.c
@@ -0,0 +1,31 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/testlib/testlib.h"
+
+void testlib_clearxmmregisters(void) {
+ asm("pxor\t%xmm0,%xmm0\n\t"
+ "pxor\t%xmm1,%xmm1\n\t"
+ "pxor\t%xmm2,%xmm2\n\t"
+ "pxor\t%xmm3,%xmm3\n\t"
+ "pxor\t%xmm4,%xmm4\n\t"
+ "pxor\t%xmm5,%xmm5\n\t"
+ "pxor\t%xmm6,%xmm6\n\t"
+ "pxor\t%xmm7,%xmm7");
+}
diff --git a/libc/testlib/ezbenchreport.c b/libc/testlib/ezbenchreport.c
index 713abdaeb..b02c6f2fe 100644
--- a/libc/testlib/ezbenchreport.c
+++ b/libc/testlib/ezbenchreport.c
@@ -24,11 +24,12 @@
STATIC_YOINK("ntoa");
STATIC_YOINK("stoa");
+STATIC_YOINK("strnwidth");
void __testlib_ezbenchreport(const char *form, uint64_t c1, uint64_t c2) {
uint64_t ns1, ns2;
- ns1 = lrintl(converttickstonanos(c1));
- ns2 = lrintl(converttickstonanos(c2));
+ ns1 = rintl(converttickstonanos(c1));
+ ns2 = rintl(converttickstonanos(c2));
(fprintf)(stderr,
VEIL("r", "%-30s l: %,10lu𝑐 %,10lu𝑛𝑠 m: %,10lu𝑐 %,10lu𝑛𝑠\n"),
form, c1, ns1, c2, ns2);
diff --git a/libc/testlib/fixturerunner.c b/libc/testlib/fixturerunner.c
index e282219b0..facac541a 100644
--- a/libc/testlib/fixturerunner.c
+++ b/libc/testlib/fixturerunner.c
@@ -40,9 +40,9 @@ testonly void testlib_runfixtures(testfn_t *test_start, testfn_t *test_end,
for (i = 0; i < count && !g_testlib_failed; ++i) {
snprintf(g_fixturename, sizeof(g_fixturename), "%s_%s",
fixture_start[i].group, fixture_start[i].name);
- __piro(PROT_READ | PROT_WRITE);
+ _piro(PROT_READ | PROT_WRITE);
fixture_start[i].fn();
- __piro(PROT_READ);
+ _piro(PROT_READ);
testlib_runtestcases(test_start, test_end, NULL);
}
}
diff --git a/libc/testlib/showerror.c b/libc/testlib/showerror.c
index 811b4ed05..67c661cd3 100644
--- a/libc/testlib/showerror.c
+++ b/libc/testlib/showerror.c
@@ -42,7 +42,7 @@ testonly void testlib_showerror(const char *file, int line, const char *func,
"\t%s%s\n"
"\t%s%s\n",
RED2, "error", UNBOLD, BLUE1, file, line, RESET, method, "in", func,
- g_fixturename, code, "want", v1, symbol, " got", v2, SUBTLE,
+ g_fixturename, code, "need", v1, symbol, " got", v2, SUBTLE,
strerror(errno), program_invocation_name, RESET);
free_s(&v1);
free_s(&v2);
diff --git a/libc/testlib/showerror_.c b/libc/testlib/showerror_.c
index 82b21810d..193a3c8b3 100644
--- a/libc/testlib/showerror_.c
+++ b/libc/testlib/showerror_.c
@@ -60,7 +60,7 @@ testonly void testlib_showerror_(int line, const char *wantcode,
", %s)\n"
"\t\t%s %s %s\n"
"\t\t%s %s\n",
- gotcode, "want", FREED_want, testlib_showerror_symbol, " got",
+ gotcode, "need", FREED_want, testlib_showerror_symbol, " got",
FREED_got);
} else {
fprintf(stderr,
diff --git a/libc/testlib/testlib.h b/libc/testlib/testlib.h
index 7c81ccaeb..8e4a903aa 100644
--- a/libc/testlib/testlib.h
+++ b/libc/testlib/testlib.h
@@ -1,6 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_TESTLIB_H_
#define COSMOPOLITAN_LIBC_TESTLIB_H_
-#include "libc/bits/bits.h"
+#include "libc/bits/weaken.h"
#include "libc/runtime/gc.h"
#include "libc/testlib/ugly.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
@@ -15,7 +15,7 @@ COSMOPOLITAN_C_START_
* Test cases are guaranteed by the linker to be run in order, sorted by
* the (SUITE, NAME) tuple passed here.
*/
-#define TEST(SUITE, NAME) __TEST_PROTOTYPE(SUITE, NAME, __TEST_ARRAY)
+#define TEST(SUITE, NAME) __TEST_PROTOTYPE(SUITE, NAME, __TEST_ARRAY, )
/**
* Declares function that globally modifies program state.
@@ -48,7 +48,7 @@ COSMOPOLITAN_C_START_
*/
#define BENCH(SUITE, NAME) \
STATIC_YOINK("__bench_start"); \
- __TEST_PROTOTYPE(SUITE, NAME, __BENCH_ARRAY)
+ __TEST_PROTOTYPE(SUITE, NAME, __BENCH_ARRAY, optimizespeed)
#define ASSERT_GE(C, X) _TEST2("ASSERT_GE", C, >=, (X), #C, " ≥ ", #X, 1)
#define ASSERT_GT(C, X) _TEST2("ASSERT_GT", C, >, (X), #C, " > ", #X, 1)
@@ -82,9 +82,11 @@ COSMOPOLITAN_C_START_
#define ASSERT_EQ(WANT, GOT, ...) \
__TEST_EQ(assert, __FILE__, __LINE__, __FUNCTION__, #WANT, #GOT, WANT, GOT, \
__VA_ARGS__)
+
#define EXPECT_EQ(WANT, GOT, ...) \
__TEST_EQ(expect, __FILE__, __LINE__, __FUNCTION__, #WANT, #GOT, WANT, GOT, \
__VA_ARGS__)
+
#define __TEST_EQ(KIND, FILE, LINE, FUNC, WANTCODE, GOTCODE, WANT, GOT, ...) \
({ \
autotype(GOT) Got = _I(GOT); \
@@ -272,6 +274,7 @@ void *tunbing(const char16_t *)
void *tunbinga(size_t, const char16_t *)
paramsnonnull() returnsnonnull returnspointerwithnoaliases nodiscard
attributeallocalign((1));
+void testlib_clearxmmregisters(void);
#define tgc(TMEM) \
({ \
@@ -533,8 +536,8 @@ forceinline void assertStartsWith(FILIFU_ARGS size_t cw, const char *prefix,
if (testlib_startswith(cw, s, prefix)) return;
if (g_testlib_shoulddebugbreak) DebugBreak();
testlib_showerror(file, line, func, "assertStartsWith", "≠", gotcode,
- testlib_formatstr(1, s, -1),
- testlib_formatstr(1, prefix, -1));
+ testlib_formatstr(1, prefix, -1),
+ testlib_formatstr(1, s, -1));
testlib_onfail2(isfatal);
}
diff --git a/libc/testlib/testlib.mk b/libc/testlib/testlib.mk
index 6c038c4b4..2044a9f84 100644
--- a/libc/testlib/testlib.mk
+++ b/libc/testlib/testlib.mk
@@ -43,6 +43,7 @@ LIBC_TESTLIB_A_SRCS_C = \
libc/testlib/almostequallongdouble.c \
libc/testlib/hexequals.c \
libc/testlib/binequals.c \
+ libc/testlib/clearxmmregisters.c \
libc/testlib/formatbool.c \
libc/testlib/formatrange.c \
libc/testlib/globals.c \
diff --git a/libc/testlib/testmain.c b/libc/testlib/testmain.c
index 69e6491a3..a10d526bd 100644
--- a/libc/testlib/testmain.c
+++ b/libc/testlib/testmain.c
@@ -74,6 +74,7 @@ testonly int main(int argc, char *argv[]) {
showcrashreports();
g_testlib_shoulddebugbreak = IsDebuggerPresent(false);
getpid$sysv(); /* make strace easier to read */
+ testlib_clearxmmregisters();
testlib_runalltests();
if (!g_testlib_failed && runbenchmarks_ && weaken(testlib_runallbenchmarks)) {
weaken(testlib_runallbenchmarks)();
diff --git a/libc/testlib/testmem.c b/libc/testlib/testmem.c
index e95356b18..b66d4ad52 100644
--- a/libc/testlib/testmem.c
+++ b/libc/testlib/testmem.c
@@ -94,7 +94,7 @@ static void testmem_fini(void) {
}
}
-static void testmem_init(void) {
+static textstartup void testmem_init(void) {
atexit(testmem_fini);
g_testmem.p = g_testmem_scratch[0];
g_testmem.n = ARRAYLEN(g_testmem_scratch[0]);
diff --git a/libc/testlib/testrunner.c b/libc/testlib/testrunner.c
index 025b5e802..68448e0c5 100644
--- a/libc/testlib/testrunner.c
+++ b/libc/testlib/testrunner.c
@@ -17,8 +17,8 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/bits/bits.h"
#include "libc/bits/safemacros.h"
+#include "libc/bits/weaken.h"
#include "libc/calls/internal.h"
#include "libc/errno.h"
#include "libc/nt/process.h"
@@ -70,6 +70,7 @@ testonly void testlib_runtestcases(testfn_t *start, testfn_t *end,
SetLastError(0);
getpid$sysv();
if (warmup) warmup();
+ testlib_clearxmmregisters();
(*fn)();
getpid$sysv();
if (weaken(TearDown)) weaken(TearDown)();
diff --git a/libc/testlib/ugly.h b/libc/testlib/ugly.h
index e51f17609..241a65cd5 100644
--- a/libc/testlib/ugly.h
+++ b/libc/testlib/ugly.h
@@ -14,10 +14,10 @@
#define __BENCH_ARRAY(S) \
_Section(".piro.relo.sort.bench.2." #S ",\"aw\",@init_array #")
-#define __TEST_PROTOTYPE(S, N, A) \
+#define __TEST_PROTOTYPE(S, N, A, K) \
testonly void S##_##N(void); \
alignas(8) const void *const S##_##N##_ptr[] A(S) = {S##_##N}; \
- testonly void S##_##N(void)
+ testonly K void S##_##N(void)
#define __TEST_SECTION(NAME, CONTENT) \
".section " NAME "\n" CONTENT "\n\t.previous\n"
diff --git a/libc/time/clock_gettime.c b/libc/time/clock_gettime.c
index 56004bb2a..afc4f6a3d 100644
--- a/libc/time/clock_gettime.c
+++ b/libc/time/clock_gettime.c
@@ -44,7 +44,7 @@
* @param clockid can be CLOCK_REALTIME, CLOCK_MONOTONIC, etc. noting
* that on Linux CLOCK_MONOTONIC is redefined to use the monotonic
* clock that's actually monotonic lool
- * @param out_ts is where the nanoseconds are stored
+ * @param out_ts is where the nanoseconds are stored if non-NULL
* @return 0 on success or -1 w/ errno on error
* @error ENOSYS if clockid isn't available; in which case this function
* guarantees an ordinary timestamp is still stored to out_ts; and
@@ -56,21 +56,28 @@ int clock_gettime(int clockid, struct timespec *out_ts) {
/* TODO(jart): Just ignore O/S for MONOTONIC and measure RDTSC on start */
if (!IsWindows()) {
if (!IsXnu()) {
- out_ts->tv_sec = 0;
- out_ts->tv_nsec = 0;
+ if (out_ts) {
+ out_ts->tv_sec = 0;
+ out_ts->tv_nsec = 0;
+ }
return clock_gettime$sysv(clockid, out_ts);
} else {
+ int rc;
static_assert(sizeof(struct timeval) == sizeof(struct timespec));
- out_ts->tv_sec = 0;
- out_ts->tv_nsec = 0;
- int rc = gettimeofday$sysv((struct timeval *)out_ts, NULL);
- out_ts->tv_nsec *= 1000;
+ if (out_ts) {
+ out_ts->tv_sec = 0;
+ out_ts->tv_nsec = 0;
+ }
+ rc = gettimeofday$sysv((struct timeval *)out_ts, NULL);
+ if (out_ts) {
+ out_ts->tv_nsec *= 1000;
+ }
return rc;
}
} else {
struct NtFileTime ft;
GetSystemTimeAsFileTime(&ft);
- filetimetotimespec(out_ts, ft);
+ *out_ts = filetimetotimespec(ft);
return 0;
}
}
diff --git a/libc/time/dsleep.c b/libc/time/dsleep.c
index c9cb18586..6a8acc413 100644
--- a/libc/time/dsleep.c
+++ b/libc/time/dsleep.c
@@ -29,7 +29,7 @@ long double dsleep(long double secs) {
struct timespec dur, rem;
dur.tv_sec = secs;
dur.tv_nsec = secs * 1e9;
- dur.tv_nsec = mod1000000000int64(dur.tv_nsec);
+ dur.tv_nsec = rem1000000000int64(dur.tv_nsec);
if (secs > 1e-6) {
nanosleep(&dur, &rem);
secs = rem.tv_nsec;
diff --git a/libc/time/futimens.c b/libc/time/futimens.c
new file mode 100644
index 000000000..69c2fd154
--- /dev/null
+++ b/libc/time/futimens.c
@@ -0,0 +1,31 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/time/time.h"
+
+/**
+ * Sets atime/mtime on file descriptor.
+ *
+ * @param ts is atime/mtime, or null for current time
+ * @note better than microsecond precision on most platforms
+ * @see fstat() for reading timestamps
+ */
+int futimens(int fd, const struct timespec ts[hasatleast 2]) {
+ return utimensat(fd, NULL, ts, 0);
+}
diff --git a/libc/time/futimes.c b/libc/time/futimes.c
new file mode 100644
index 000000000..657282042
--- /dev/null
+++ b/libc/time/futimes.c
@@ -0,0 +1,40 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/time/time.h"
+
+/**
+ * Sets atime/mtime on file descriptor.
+ *
+ * @param ts is atime/mtime, or null for current time
+ * @note better than microsecond precision on most platforms
+ * @see fstat() for reading timestamps
+ */
+int futimes(int fd, const struct timeval tv[hasatleast 2]) {
+ struct timespec ts[2];
+ if (tv) {
+ ts[0].tv_sec = tv[0].tv_sec;
+ ts[0].tv_nsec = tv[0].tv_usec * 1000;
+ ts[1].tv_sec = tv[1].tv_sec;
+ ts[1].tv_nsec = tv[1].tv_usec * 1000;
+ return utimensat(fd, NULL, ts, 0);
+ } else {
+ return utimensat(fd, NULL, NULL, 0);
+ }
+}
diff --git a/libc/time/futimesat.c b/libc/time/futimesat.c
new file mode 100644
index 000000000..2683c822e
--- /dev/null
+++ b/libc/time/futimesat.c
@@ -0,0 +1,40 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/time/time.h"
+
+/**
+ * Changes last accessed/modified times on file.
+ *
+ * @param times is access/modified and NULL means now
+ * @return 0 on success or -1 w/ errno
+ * @see utimensat() which uses nanos
+ */
+int futimesat(int dirfd, const char *pathname, const struct timeval tv[2]) {
+ struct timespec ts[2];
+ if (tv) {
+ ts[0].tv_sec = tv[0].tv_sec;
+ ts[0].tv_nsec = tv[0].tv_usec * 1000;
+ ts[1].tv_sec = tv[1].tv_sec;
+ ts[1].tv_nsec = tv[1].tv_usec * 1000;
+ return utimensat(dirfd, pathname, ts, 0);
+ } else {
+ return utimensat(dirfd, pathname, NULL, 0);
+ }
+}
diff --git a/libc/time/gettimeofday.c b/libc/time/gettimeofday.c
index 8d74e360f..6cf9ca642 100644
--- a/libc/time/gettimeofday.c
+++ b/libc/time/gettimeofday.c
@@ -24,7 +24,7 @@
/**
* Returns system wall time in microseconds.
*
- * @param tv points to timeval that receives result
+ * @param tv points to timeval that receives result if non-NULL
* @param tz receives UTC timezone if non-NULL
* @return always zero
* @see clock_gettime() for nanosecond precision
diff --git a/libc/time/localtime.c b/libc/time/localtime.c
index 302fe321e..12ee429e7 100644
--- a/libc/time/localtime.c
+++ b/libc/time/localtime.c
@@ -1,7 +1,9 @@
+#include "libc/bits/initializer.h"
#include "libc/calls/calls.h"
#include "libc/macros.h"
#include "libc/math.h"
#include "libc/mem/mem.h"
+#include "libc/nexgen32e/nexgen32e.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
@@ -871,9 +873,9 @@ const int32_t offset;
*/
m1 = (rulep->r_mon + 9) % 12 + 1;
yy0 = (rulep->r_mon <= 2) ? (year - 1) : year;
- yy1 = yy0 / 100;
- yy2 = yy0 % 100;
- dow = ((26 * m1 - 2) / 10 +
+ yy1 = div100int64(yy0);
+ yy2 = rem100int64(yy0);
+ dow = (div10int64(26 * m1 - 2) +
1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
if (dow < 0)
dow += DAYSPERWEEK;
@@ -1440,7 +1442,7 @@ pureconst static int
leaps_thru_end_of(y)
register const int y;
{
- return (y >= 0) ? (y / 4 - y / 100 + y / 400) :
+ return (y >= 0) ? (y / 4 - div100int64(y) + y / 400) :
-(leaps_thru_end_of(-(y + 1)) + 1);
}
diff --git a/libc/time/nanosleep.c b/libc/time/nanosleep.c
index 2d364b7d4..e31ecb504 100644
--- a/libc/time/nanosleep.c
+++ b/libc/time/nanosleep.c
@@ -23,11 +23,13 @@
#include "libc/conv/conv.h"
#include "libc/dce.h"
#include "libc/macros.h"
+#include "libc/nexgen32e/nexgen32e.h"
#include "libc/nt/enum/status.h"
#include "libc/nt/errors.h"
#include "libc/nt/nt/time.h"
#include "libc/nt/synchronization.h"
#include "libc/sock/internal.h"
+#include "libc/str/str.h"
#include "libc/sysv/errfuns.h"
/**
@@ -35,24 +37,28 @@
*/
int nanosleep(const struct timespec *req, struct timespec *rem) {
long res, millis, hectonanos;
+ if (!req) return efault();
if (!IsWindows()) {
if (!IsXnu()) {
return nanosleep$sysv(req, rem);
} else {
- return select$sysv(0, 0, 0, 0, /* lool */
- &(struct timeval){req->tv_sec, req->tv_nsec / 1000});
+ return select$sysv(
+ 0, 0, 0, 0, /* lool */
+ &(struct timeval){req->tv_sec, div1000int64(req->tv_nsec)});
}
} else {
+ if (rem) memcpy(rem, req, sizeof(*rem));
if (req->tv_sec && req->tv_nsec) {
- hectonanos = MAX(1, req->tv_sec * 10000000L + req->tv_nsec / 100L);
+ hectonanos = MAX(1, req->tv_sec * 10000000L + div100int64(req->tv_nsec));
} else {
hectonanos = 1;
}
if (NtError(NtDelayExecution(true, &hectonanos))) {
- millis = hectonanos / 10000;
+ millis = div10000int64(hectonanos);
res = SleepEx(millis, true);
if (res == kNtWaitIoCompletion) return eintr();
}
+ if (rem) memset(rem, 0, sizeof(*rem));
return 0;
}
}
diff --git a/libc/time/now.c b/libc/time/now.c
index 928fec00e..7ea7adcfb 100644
--- a/libc/time/now.c
+++ b/libc/time/now.c
@@ -18,6 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/bits.h"
+#include "libc/bits/initializer.h"
#include "libc/bits/safemacros.h"
#include "libc/calls/calls.h"
#include "libc/dce.h"
@@ -27,6 +28,7 @@
#include "libc/time/time.h"
static struct Now {
+ bool once;
uint64_t k0;
long double r0, cpn;
} now_;
@@ -37,25 +39,7 @@ static struct Now {
*/
long double (*nowl)(void);
-long double converttickstonanos(uint64_t ticks) {
- return ticks * now_.cpn; /* pico scale */
-}
-
-long double converttickstoseconds(uint64_t ticks) {
- return 1 / 1e9 * converttickstonanos(ticks);
-}
-
-static long double nowl$sys(void) {
- return dtime(CLOCK_REALTIME);
-}
-
-static long double nowl$art(void) {
- uint64_t ticks;
- ticks = unsignedsubtract(rdtsc(), now_.k0);
- return now_.r0 + converttickstoseconds(ticks);
-}
-
-static long double GetSample(void) {
+static long double GetTimeSample(void) {
uint64_t tick1, tick2;
long double time1, time2;
sched_yield();
@@ -71,17 +55,41 @@ static long double MeasureNanosPerCycle(void) {
int i;
long double avg, samp;
for (avg = 1.0L, i = 1; i < 5; ++i) {
- samp = GetSample();
+ samp = GetTimeSample();
avg += (samp - avg) / i;
}
return avg;
}
-INITIALIZER(301, _init_time, {
+static void InitTime(void) {
+ now_.cpn = MeasureNanosPerCycle();
+ now_.r0 = dtime(CLOCK_REALTIME);
+ now_.k0 = rdtsc();
+ now_.once = true;
+}
+
+long double converttickstonanos(uint64_t ticks) {
+ if (!now_.once) InitTime();
+ return ticks * now_.cpn; /* pico scale */
+}
+
+long double converttickstoseconds(uint64_t ticks) {
+ return 1 / 1e9 * converttickstonanos(ticks);
+}
+
+long double nowl$sys(void) {
+ return dtime(CLOCK_REALTIME);
+}
+
+long double nowl$art(void) {
+ uint64_t ticks;
+ if (!now_.once) InitTime();
+ ticks = unsignedsubtract(rdtsc(), now_.k0);
+ return now_.r0 + converttickstoseconds(ticks);
+}
+
+INITIALIZER(301, _init_nowl, {
if (X86_HAVE(INVTSC)) {
- now_.cpn = MeasureNanosPerCycle();
- now_.r0 = dtime(CLOCK_REALTIME);
- now_.k0 = rdtsc();
nowl = nowl$art;
} else {
nowl = nowl$sys;
diff --git a/libc/time/strftime.c b/libc/time/strftime.c
index 07c76c31b..4cfe17ce5 100644
--- a/libc/time/strftime.c
+++ b/libc/time/strftime.c
@@ -19,6 +19,7 @@
#include "libc/calls/calls.h"
#include "libc/fmt/fmt.h"
#include "libc/macros.h"
+#include "libc/nexgen32e/nexgen32e.h"
#include "libc/time/struct/tm.h"
#include "libc/time/time.h"
#include "libc/tzfile.h"
@@ -100,7 +101,7 @@ static char *strftime_timefmt(char *pt, const char *ptlim, const char *format,
** something completely different.
** (ado, 5/24/93)
*/
- pt = strftime_conv(pt, ptlim, (t->tm_year + TM_YEAR_BASE) / 100,
+ pt = strftime_conv(pt, ptlim, div100int64(t->tm_year + TM_YEAR_BASE),
"%02d");
continue;
case 'D':
diff --git a/libc/time/struct/utimbuf.h b/libc/time/struct/utimbuf.h
new file mode 100644
index 000000000..b5f5e35b5
--- /dev/null
+++ b/libc/time/struct/utimbuf.h
@@ -0,0 +1,11 @@
+#ifndef COSMOPOLITAN_LIBC_TIME_STRUCT_UTIMBUF_H_
+#define COSMOPOLITAN_LIBC_TIME_STRUCT_UTIMBUF_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+
+struct utimbuf {
+ int64_t actime; /* access time */
+ int64_t modtime; /* modified time */
+};
+
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_LIBC_TIME_STRUCT_UTIMBUF_H_ */
diff --git a/libc/time/time.h b/libc/time/time.h
index e20d76e54..f50921494 100644
--- a/libc/time/time.h
+++ b/libc/time/time.h
@@ -1,13 +1,14 @@
#ifndef COSMOPOLITAN_LIBC_TIME_TIME_H_
#define COSMOPOLITAN_LIBC_TIME_TIME_H_
+#include "libc/calls/struct/timespec.h"
+#include "libc/calls/struct/timeval.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
struct itimerval;
-struct timespec;
-struct timeval;
struct timezone;
struct tm;
+struct utimbuf;
extern const char kWeekdayNameShort[7][4];
extern const char kWeekdayName[7][10];
@@ -20,12 +21,12 @@ extern long CLOCKS_PER_SEC;
int64_t clock(void);
int64_t time(int64_t *);
int gettimeofday(struct timeval *, struct timezone *);
-int clock_gettime(int, struct timespec *) paramsnonnull();
+int clock_gettime(int, struct timespec *);
int clock_getres(int, struct timespec *);
int sleep(uint32_t);
int usleep(uint32_t);
-int nanosleep(const struct timespec *, struct timespec *) paramsnonnull((1));
+int nanosleep(const struct timespec *, struct timespec *);
unsigned alarm(unsigned);
int getitimer(int, struct itimerval *) paramsnonnull();
int setitimer(int, const struct itimerval *, struct itimerval *)
@@ -51,7 +52,12 @@ char *asctime_r(const struct tm *, char * /*[64]*/);
char *ctime(const int64_t *);
char *ctime_r(const int64_t *, char * /*[64]*/);
-int utimes(const char *, const struct timeval *);
+int futimens(int, const struct timespec[2]);
+int utimensat(int, const char *, const struct timespec[2], int);
+int utimes(const char *, const struct timeval[2]);
+int utime(const char *, const struct utimbuf *);
+int futimes(int, const struct timeval[2]);
+int futimesat(int, const char *, const struct timeval[2]);
long double dtime(int);
long double dsleep(long double);
diff --git a/libc/time/times.c b/libc/time/times.c
index 870f7bf87..25e04f1ca 100644
--- a/libc/time/times.c
+++ b/libc/time/times.c
@@ -25,39 +25,31 @@
#include "libc/conv/conv.h"
#include "libc/dce.h"
#include "libc/nt/accounting.h"
-#include "libc/nt/process.h"
#include "libc/nt/runtime.h"
-#include "libc/nt/struct/filetime.h"
#include "libc/runtime/sysconf.h"
#include "libc/sysv/consts/rusage.h"
#include "libc/time/time.h"
-/**
- * Returns accounting data for process on time-sharing system.
- */
-long times(struct tms *out_times) {
+static noinline long times2(struct tms *out_times, struct rusage *ru) {
+ long tick;
struct timeval tv;
- long tick = sysconf(_SC_CLK_TCK);
+ tick = sysconf(_SC_CLK_TCK);
if (!IsWindows()) {
- struct rusage ru;
- if (getrusage(RUSAGE_SELF, &ru) == -1) return -1;
- out_times->tms_utime = convertmicros(&ru.ru_utime, tick);
- out_times->tms_stime = convertmicros(&ru.ru_stime, tick);
- if (getrusage(RUSAGE_CHILDREN, &ru) == -1) return -1;
- out_times->tms_cutime = convertmicros(&ru.ru_utime, tick);
- out_times->tms_cstime = convertmicros(&ru.ru_stime, tick);
+ if (getrusage(RUSAGE_SELF, ru) == -1) return -1;
+ out_times->tms_utime = convertmicros(&ru->ru_utime, tick);
+ out_times->tms_stime = convertmicros(&ru->ru_stime, tick);
+ if (getrusage(RUSAGE_CHILDREN, ru) == -1) return -1;
+ out_times->tms_cutime = convertmicros(&ru->ru_utime, tick);
+ out_times->tms_cstime = convertmicros(&ru->ru_stime, tick);
} else {
- struct NtFileTime CreationFileTime;
- struct NtFileTime ExitFileTime;
- struct NtFileTime KernelFileTime;
- struct NtFileTime UserFileTime;
- if (!GetProcessTimes(GetCurrentProcess(), &CreationFileTime, &ExitFileTime,
- &KernelFileTime, &UserFileTime)) {
+ struct NtFileTime CreationTime, ExitTime, KernelTime, UserTime;
+ if (!GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime,
+ &KernelTime, &UserTime)) {
return winerr();
}
- filetimetotimeval(&tv, UserFileTime);
+ filetimetotimeval(&tv, UserTime);
out_times->tms_utime = convertmicros(&tv, tick);
- filetimetotimeval(&tv, KernelFileTime);
+ filetimetotimeval(&tv, KernelTime);
out_times->tms_stime = convertmicros(&tv, tick);
out_times->tms_cutime = 0;
out_times->tms_cstime = 0;
@@ -65,3 +57,11 @@ long times(struct tms *out_times) {
if (gettimeofday(&tv, NULL) == -1) return -1;
return convertmicros(&tv, tick);
}
+
+/**
+ * Returns accounting data for process on time-sharing system.
+ */
+long times(struct tms *out_times) {
+ struct rusage ru;
+ return times2(out_times, &ru);
+}
diff --git a/libc/time/utime.c b/libc/time/utime.c
index 2bbbf38a4..78fd22273 100644
--- a/libc/time/utime.c
+++ b/libc/time/utime.c
@@ -18,9 +18,9 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/str/str.h"
-#include "libc/calls/struct/timeval.h"
+#include "libc/sysv/consts/at.h"
+#include "libc/time/struct/utimbuf.h"
#include "libc/time/time.h"
-#include "libc/time/utime.h"
/**
* Changes last accessed/modified times on file.
@@ -29,11 +29,14 @@
* @return 0 on success or -1 w/ errno
*/
int utime(const char *path, const struct utimbuf *times) {
- struct timeval tv[2];
- memset(tv, 0, sizeof(tv));
+ struct timespec ts[2];
if (times) {
- tv[0].tv_sec = times->actime;
- tv[1].tv_sec = times->modtime;
+ ts[0].tv_sec = times->actime;
+ ts[0].tv_nsec = 0;
+ ts[1].tv_sec = times->modtime;
+ ts[1].tv_nsec = 0;
+ return utimensat(AT_FDCWD, path, ts, 0);
+ } else {
+ return utimensat(AT_FDCWD, path, NULL, 0);
}
- return utimes(path, times ? tv : NULL);
}
diff --git a/libc/time/utime.h b/libc/time/utime.h
deleted file mode 100644
index d8f19c7d1..000000000
--- a/libc/time/utime.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#ifndef COSMOPOLITAN_LIBC_CALLS_UTIME_H_
-#define COSMOPOLITAN_LIBC_CALLS_UTIME_H_
-#if !(__ASSEMBLER__ + __LINKER__ + 0)
-COSMOPOLITAN_C_START_
-
-struct utimbuf {
- int64_t actime; /* access time */
- int64_t modtime; /* modified time */
-};
-
-int utime(const char *path, const struct utimbuf *times) paramsnonnull((1));
-
-COSMOPOLITAN_C_END_
-#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
-#endif /* COSMOPOLITAN_LIBC_CALLS_UTIME_H_ */
diff --git a/libc/calls/copyfile.c b/libc/time/utimensat-nt.c
similarity index 56%
rename from libc/calls/copyfile.c
rename to libc/time/utimensat-nt.c
index 279eddb36..8a4f780a7 100644
--- a/libc/calls/copyfile.c
+++ b/libc/time/utimensat-nt.c
@@ -17,48 +17,70 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
-#include "libc/calls/struct/stat.h"
-#include "libc/dce.h"
+#include "libc/conv/conv.h"
+#include "libc/nt/createfile.h"
+#include "libc/nt/enum/creationdisposition.h"
#include "libc/nt/files.h"
+#include "libc/nt/runtime.h"
+#include "libc/nt/synchronization.h"
#include "libc/sysv/consts/at.h"
-#include "libc/sysv/consts/madv.h"
-#include "libc/sysv/consts/o.h"
+#include "libc/sysv/consts/utime.h"
+#include "libc/sysv/errfuns.h"
+#include "libc/time/time.h"
-int copyfile(const char *frompath, const char *topath, bool dontoverwrite) {
- if (IsWindows()) {
- char16_t frompath16[PATH_MAX], topath16[PATH_MAX];
- if (mkntpath(frompath, frompath16) == -1) return -1;
- if (mkntpath(topath, topath16) == -1) return -1;
- if (CopyFile(frompath16, topath16, dontoverwrite)) {
- return 0;
+textwindows int utimensat$nt(int dirfd, const char *path,
+ const struct timespec ts[2], int flags) {
+ int i, rc;
+ int64_t fh;
+ bool closeme;
+ uint16_t path16[PATH_MAX];
+ struct NtFileTime ft[2], *ftp[2];
+ if (flags) return einval();
+ if (path) {
+ if (dirfd == AT_FDCWD) {
+ if (mkntpath(path, path16) == -1) return -1;
+ if ((fh = CreateFile(path16, kNtFileWriteAttributes, kNtFileShareRead,
+ NULL, kNtOpenExisting, kNtFileAttributeNormal, 0)) !=
+ -1) {
+ closeme = true;
+ } else {
+ return winerr();
+ }
} else {
- return winerr();
+ return einval();
+ }
+ } else if (isfdindex(dirfd)) {
+ fh = g_fds.p[dirfd].handle;
+ closeme = false;
+ } else {
+ return ebadf();
+ }
+ if (!ts || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW) {
+ GetSystemTimeAsFileTime(ft);
+ }
+ if (ts) {
+ for (i = 0; i < 2; ++i) {
+ if (ts[i].tv_nsec == UTIME_NOW) {
+ ftp[i] = ft;
+ } else if (ts[i].tv_nsec == UTIME_OMIT) {
+ ftp[i] = NULL;
+ } else {
+ ft[i] = timespectofiletime(ts[i]);
+ ftp[i] = &ft[i];
+ }
}
} else {
- struct stat st;
- ssize_t transferred;
- int rc, fromfd, tofd;
- int64_t inoffset, outoffset;
- rc = -1;
- if ((fromfd = openat$sysv(AT_FDCWD, frompath, O_RDONLY, 0)) != -1) {
- if (fstat$sysv(fromfd, &st) != -1 &&
- (tofd = openat$sysv(AT_FDCWD, topath,
- O_WRONLY | O_CREAT | (dontoverwrite ? O_EXCL : 0),
- st.st_mode & 0777)) != -1) {
- inoffset = 0;
- outoffset = 0;
- while (st.st_size && (transferred = copy_file_range(
- fromfd, &inoffset, tofd, &outoffset,
- st.st_size, 0)) != -1) {
- st.st_size -= transferred;
- }
- if (!st.st_size) rc = 0;
- rc |= close$sysv(tofd);
- }
- rc |= close$sysv(fromfd);
- }
- return rc;
+ ftp[0] = ft;
+ ftp[1] = ft;
}
+ if (SetFileTime(fh, NULL, ftp[0], ftp[1])) {
+ rc = 0;
+ } else {
+ rc = winerr();
+ }
+ if (closeme) {
+ CloseHandle(fh);
+ }
+ return rc;
}
diff --git a/libc/time/utimensat-sysv.c b/libc/time/utimensat-sysv.c
new file mode 100644
index 000000000..0bb504341
--- /dev/null
+++ b/libc/time/utimensat-sysv.c
@@ -0,0 +1,30 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/internal.h"
+#include "libc/time/time.h"
+
+int utimensat$sysv(int dirfd, const char *path, const struct timespec ts[2],
+ int flags) {
+ if (!IsXnu()) {
+ return __utimensat$sysv(dirfd, path, ts, flags);
+ } else {
+ return utimensat$xnu(dirfd, path, ts, flags);
+ }
+}
diff --git a/test/dsp/tty/rgb2xterm256_test.c b/libc/time/utimensat-xnu.c
similarity index 65%
rename from test/dsp/tty/rgb2xterm256_test.c
rename to libc/time/utimensat-xnu.c
index d5ff1ab34..0e29d3d5c 100644
--- a/test/dsp/tty/rgb2xterm256_test.c
+++ b/libc/time/utimensat-xnu.c
@@ -17,31 +17,47 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "dsp/tty/rgb2xterm256.h"
-#include "libc/testlib/ezbench.h"
-#include "libc/testlib/testlib.h"
+#include "libc/calls/internal.h"
+#include "libc/nexgen32e/nexgen32e.h"
+#include "libc/sysv/consts/at.h"
+#include "libc/sysv/consts/utime.h"
+#include "libc/sysv/errfuns.h"
+#include "libc/time/time.h"
-TEST(rgb2xterm256, test) {
- EXPECT_EQ(196, rgb2xterm256v2(0xff, 0x00, 0x00)); /* red */
- EXPECT_EQ(46, rgb2xterm256v2(0x00, 0xff, 0x00)); /* green */
- EXPECT_EQ(21, rgb2xterm256v2(0x00, 0x00, 0xff)); /* blue */
- EXPECT_EQ(226, rgb2xterm256v2(0xff, 0xff, 0x00)); /* yellow */
- EXPECT_EQ(208, rgb2xterm256v2(0xff, 0x80, 0x00)); /* orange */
-}
-
-TEST(rgb2xterm256, testRedBlack) {
- EXPECT_EQ(16, rgb2xterm256v2(0, 0, 0));
- EXPECT_EQ(16, rgb2xterm256v2(12, 0, 0));
- EXPECT_EQ(232, rgb2xterm256v2(13, 0, 0));
- EXPECT_EQ(233, rgb2xterm256v2(39, 0, 0));
- EXPECT_EQ(233, rgb2xterm256v2(40, 0, 0));
- EXPECT_EQ(52, rgb2xterm256v2(53, 0, 0));
- EXPECT_EQ(88, rgb2xterm256v2(115, 0, 0));
- EXPECT_EQ(88, rgb2xterm256v2(116, 0, 0));
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-BENCH(rgb2xterm256v2, bench) {
- EZBENCH(donothing, rgb2xterm256v2(0xff, 0x80, 0x00));
+int utimensat$xnu(int dirfd, const char *path, const struct timespec ts[2],
+ int flags) {
+ int i;
+ struct timeval now, tv[2];
+ if (flags) return einval();
+ if (!ts || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW) {
+ gettimeofday(&now, NULL);
+ }
+ if (ts) {
+ for (i = 0; i < 2; ++i) {
+ if (ts[i].tv_nsec == UTIME_NOW) {
+ tv[i] = now;
+ } else if (ts[i].tv_nsec == UTIME_OMIT) {
+ return einval();
+ } else {
+ tv[i].tv_sec = ts[i].tv_sec;
+ tv[i].tv_usec = div1000int64(ts[i].tv_nsec);
+ }
+ }
+ } else {
+ tv[0] = now;
+ tv[1] = now;
+ }
+ if (path) {
+ if (dirfd == AT_FDCWD) {
+ return utimes$sysv(path, tv);
+ } else {
+ return enosys();
+ }
+ } else {
+ if (dirfd != AT_FDCWD) {
+ return futimes$sysv(dirfd, tv);
+ } else {
+ return einval();
+ }
+ }
}
diff --git a/libc/time/utimensat.c b/libc/time/utimensat.c
new file mode 100644
index 000000000..e24e82031
--- /dev/null
+++ b/libc/time/utimensat.c
@@ -0,0 +1,37 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/calls.h"
+#include "libc/calls/internal.h"
+
+/**
+ * Sets atime/mtime on file, the modern way.
+ *
+ * @param ts is atime/mtime, or null for current time
+ * @param flags can have AT_SYMLINK_NOFOLLOW
+ * @note no rhel5 support
+ */
+int utimensat(int dirfd, const char *path,
+ const struct timespec ts[hasatleast 2], int flags) {
+ if (!IsWindows()) {
+ return utimensat$sysv(dirfd, path, ts, flags);
+ } else {
+ return utimensat$nt(dirfd, path, ts, flags);
+ }
+}
diff --git a/libc/time/utimes.c b/libc/time/utimes.c
index cbb264e8c..2ca13b9d2 100644
--- a/libc/time/utimes.c
+++ b/libc/time/utimes.c
@@ -17,57 +17,25 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/calls/internal.h"
-#include "libc/calls/struct/timeval.h"
-#include "libc/conv/conv.h"
-#include "libc/dce.h"
-#include "libc/nt/createfile.h"
-#include "libc/nt/enum/accessmask.h"
-#include "libc/nt/files.h"
-#include "libc/nt/runtime.h"
-#include "libc/nt/struct/filetime.h"
-#include "libc/runtime/runtime.h"
-#include "libc/str/str.h"
-#include "libc/sysv/errfuns.h"
+#include "libc/sysv/consts/at.h"
#include "libc/time/time.h"
-static int utimes$nt(const char *path, const struct timeval times[2]) {
- int rc;
- int64_t fh;
- struct timeval tv;
- struct NtFileTime accessed;
- struct NtFileTime modified;
- uint16_t path16[PATH_MAX];
- if (mkntpath(path, path16) == -1) return -1;
- if (times) {
- accessed = timevaltofiletime(×[0]);
- modified = timevaltofiletime(×[1]);
- } else {
- gettimeofday(&tv, NULL);
- accessed = timevaltofiletime(&tv);
- modified = timevaltofiletime(&tv);
- }
- if ((fh = CreateFile(path16, kNtGenericWrite, kNtFileShareRead, NULL,
- kNtOpenExisting, kNtFileAttributeNormal, 0)) != -1 &&
- SetFileTime(fh, NULL, &accessed, &modified)) {
- rc = 0;
- } else {
- rc = winerr();
- }
- CloseHandle(fh);
- return rc;
-}
-
/**
* Changes last accessed/modified times on file.
*
* @param times is access/modified and NULL means now
* @return 0 on success or -1 w/ errno
+ * @see stat()
*/
-int utimes(const char *path, const struct timeval times[hasatleast 2]) {
- if (!IsWindows()) {
- return utimes$sysv(path, times);
+int utimes(const char *path, const struct timeval tv[hasatleast 2]) {
+ struct timespec ts[2];
+ if (tv) {
+ ts[0].tv_sec = tv[0].tv_sec;
+ ts[0].tv_nsec = tv[0].tv_usec * 1000;
+ ts[1].tv_sec = tv[1].tv_sec;
+ ts[1].tv_nsec = tv[1].tv_usec * 1000;
+ return utimensat(AT_FDCWD, path, ts, 0);
} else {
- return utimes$nt(path, times);
+ return utimensat(AT_FDCWD, path, NULL, 0);
}
}
diff --git a/libc/tinymath/c2rangr.S b/libc/tinymath/c2rangr.S
index 258b53beb..f32bd801d 100644
--- a/libc/tinymath/c2rangr.S
+++ b/libc/tinymath/c2rangr.S
@@ -38,9 +38,9 @@ c2rangr:push %rbp
ret
1: fldpi
fadd %st
- fxch %st(1)
+ fxch
2: fprem1
- fnstsw %ax
+ fnstsw
test $FPU_C2>>8,%ah
jnz 2b
fstp %st(1)
diff --git a/libc/tinymath/cbrt.S b/libc/tinymath/cbrt.S
index dc07a57ec..1ed64a05e 100644
--- a/libc/tinymath/cbrt.S
+++ b/libc/tinymath/cbrt.S
@@ -1,4 +1,4 @@
-/*-*- mode:asm; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney │
@@ -27,3 +27,4 @@ tinymath_cbrt:
jmp __cbrt
.endfn tinymath_cbrt,globl
.alias tinymath_cbrt,cbrt
+ .source __FILE__
diff --git a/libc/tinymath/cbrtf.S b/libc/tinymath/cbrtf.S
index 58eb2dae9..cdf9857e7 100644
--- a/libc/tinymath/cbrtf.S
+++ b/libc/tinymath/cbrtf.S
@@ -1,4 +1,4 @@
-/*-*- mode:asm; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney │
@@ -33,3 +33,4 @@ tinymath_cbrtf:
ret
.endfn tinymath_cbrtf,globl
.alias tinymath_cbrtf,cbrtf
+ .source __FILE__
diff --git a/libc/tinymath/cbrtl.S b/libc/tinymath/cbrtl.S
index d25345005..0b1684522 100644
--- a/libc/tinymath/cbrtl.S
+++ b/libc/tinymath/cbrtl.S
@@ -1,4 +1,4 @@
-/*-*- mode:asm; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney │
@@ -37,3 +37,4 @@ tinymath_cbrtl:
ret
.endfn tinymath_cbrtl,globl
.alias tinymath_cbrtl,cbrtl
+ .source __FILE__
diff --git a/libc/tinymath/copysignl.S b/libc/tinymath/copysignl.S
index db62d454b..44e36582e 100644
--- a/libc/tinymath/copysignl.S
+++ b/libc/tinymath/copysignl.S
@@ -26,7 +26,7 @@ tinymath_copysignl:
.profilable
fldt 32(%rbp)
fxam
- fnstsw %ax
+ fnstsw
fstp %st
fldt 16(%rbp)
testb $2,%ah
diff --git a/libc/tinymath/cprojl.S b/libc/tinymath/cprojl.S
index cc47ba604..dbbba04f7 100644
--- a/libc/tinymath/cprojl.S
+++ b/libc/tinymath/cprojl.S
@@ -20,39 +20,22 @@
#include "libc/macros.h"
.source __FILE__
+/ Projects into Rienmann sphere.
+/
+/ @param z is complex long double passed on stack
+/ @note needs sse3
tinymath_cprojl:
.profilable
sub $24,%rsp
fldt 32(%rsp)
- fnstcw 14(%rsp)
- movzwl 14(%rsp),%eax
- orb $12,%ah
- movw %ax,12(%rsp)
- fldcw 12(%rsp)
- fistpq (%rsp)
- fldcw 14(%rsp)
- movq (%rsp),%rsi
+ fisttpq 8(%rsp)
fldt 48(%rsp)
+ movq 8(%rsp),%rsi
mov %rsi,%rax
- fldcw 12(%rsp)
- fistpq (%rsp)
- fldcw 14(%rsp)
- movq (%rsp),%rcx
+ fisttpq 8(%rsp)
+ mov 8(%rsp),%rcx
add $24,%rsp
mov %rcx,%rdx
ret
.endfn tinymath_cprojl,globl
.alias tinymath_cprojl,cprojl
-
-/ TODO(jart):
-/ sub $24,%rsp
-/ fldt 32(%rsp)
-/ fisttpq 8(%rsp)
-/ fldt 48(%rsp)
-/ movq 8(%rsp),%rsi
-/ mov %rsi,%rax
-/ fisttpq 8(%rsp)
-/ movq 8(%rsp),%rcx
-/ add $24,%rsp
-/ mov %rcx,%rdx
-/ ret
diff --git a/libc/tinymath/exp2.S b/libc/tinymath/exp2.S
index a7f39f8b8..a939eff76 100644
--- a/libc/tinymath/exp2.S
+++ b/libc/tinymath/exp2.S
@@ -24,6 +24,8 @@
/
/ @param 𝑥 is a double passed in the lower quadword of %xmm0
/ @return result in lower quadword of %xmm0
-exp2: ezlea exp2l,ax
+tinymath_exp2:
+ ezlea tinymath_exp2l,ax
jmp _d2ld2
- .endfn exp2,globl
+ .endfn tinymath_exp2,globl
+ .alias tinymath_exp2,exp2
diff --git a/libc/tinymath/exp2f.S b/libc/tinymath/exp2f.S
index 0b6997ae8..b7e0a8a60 100644
--- a/libc/tinymath/exp2f.S
+++ b/libc/tinymath/exp2f.S
@@ -20,6 +20,12 @@
#include "libc/macros.h"
.source __FILE__
-exp2f: ezlea exp2f,ax
+/ Returns 2^𝑥.
+/
+/ @param 𝑥 is a float passed in the lower quarter of %xmm0
+/ @return result in lower quarter of %xmm0
+tinymath_exp2f:
+ ezlea tinymath_exp2l,ax
jmp _f2ld2
- .endfn exp2f,globl
+ .endfn tinymath_exp2f,globl
+ .alias tinymath_exp2f,exp2f
diff --git a/libc/tinymath/exp2l.S b/libc/tinymath/exp2l.S
index 413876675..6ced34b58 100644
--- a/libc/tinymath/exp2l.S
+++ b/libc/tinymath/exp2l.S
@@ -20,8 +20,12 @@
#include "libc/macros.h"
.source __FILE__
-/ Returns 2^x.
-exp2l: push %rbp
+/ Returns 2^𝑥.
+/
+/ @param 𝑥 is an 80-bit long double passed on stack in 16-bytes
+/ @return result of exponentiation on FPU stack in %st
+tinymath_exp2l:
+ push %rbp
mov %rsp,%rbp
.profilable
fldt 16(%rbp)
@@ -35,7 +39,8 @@ exp2l: push %rbp
fstp %st(1)
pop %rbp
ret
- .endfn exp2l,globl
+ .endfn tinymath_exp2l,globl
+ .alias tinymath_exp2l,exp2l
.rodata.cst4
.Lone: .float 1.0
diff --git a/libc/tinymath/expm1.S b/libc/tinymath/expm1.S
index f4f4295e3..97fac375a 100644
--- a/libc/tinymath/expm1.S
+++ b/libc/tinymath/expm1.S
@@ -20,6 +20,12 @@
#include "libc/macros.h"
.source __FILE__
-expm1: ezlea expm1l,ax
+/ Returns 𝑒^x-1.
+/
+/ @param 𝑥 is double scalar in low half of %xmm0
+/ @return double scalar in low half of %xmm0
+tinymath_expm1:
+ ezlea tinymath_expm1l,ax
jmp _d2ld2
- .endfn expm1,globl
+ .endfn tinymath_expm1,globl
+ .alias tinymath_expm1,expm1
diff --git a/libc/tinymath/expm1f.S b/libc/tinymath/expm1f.S
index 35e5b9000..3cfef3c26 100644
--- a/libc/tinymath/expm1f.S
+++ b/libc/tinymath/expm1f.S
@@ -20,6 +20,12 @@
#include "libc/macros.h"
.source __FILE__
-expm1f: ezlea expm1l,ax
+/ Returns 𝑒^x-1.
+/
+/ @param 𝑥 is float scalar in low quarter of %xmm0
+/ @return float scalar in low quarter of %xmm0
+tinymath_expm1f:
+ ezlea tinymath_expm1l,ax
jmp _f2ld2
- .endfn expm1f,globl
+ .endfn tinymath_expm1f,globl
+ .alias tinymath_expm1f,expm1f
diff --git a/libc/tinymath/expm1l.S b/libc/tinymath/expm1l.S
index e15763cd2..3378c61d0 100644
--- a/libc/tinymath/expm1l.S
+++ b/libc/tinymath/expm1l.S
@@ -20,8 +20,12 @@
#include "libc/macros.h"
.source __FILE__
-/ Returns exp(𝑥) - 1.
-expm1l: push %rbp
+/ Returns 𝑒^x-1.
+/
+/ @param 𝑥 is an 80-bit long double passed on stack in 16-bytes
+/ @return result of exponentiation on FPU stack in %st
+tinymath_expm1l:
+ push %rbp
mov %rsp,%rbp
.profilable
fldt 16(%rbp)
@@ -41,7 +45,8 @@ expm1l: push %rbp
faddp %st,%st(1)
pop %rbp
ret
- .endfn expm1l,globl
+ .endfn tinymath_expm1l,globl
+ .alias tinymath_expm1l,expm1l
.rodata.cst4
.Lone: .float 1.0
diff --git a/libc/tinymath/f2ld2.S b/libc/tinymath/f2ld2.S
index 0d7c8cb7e..f7db0fd55 100644
--- a/libc/tinymath/f2ld2.S
+++ b/libc/tinymath/f2ld2.S
@@ -18,7 +18,6 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.source __FILE__
/ Thunks float(*fn)(float,float) -> long double fn.
/
@@ -41,3 +40,4 @@ _f2ld2: push %rbp
leave
ret
.endfn _f2ld2,globl,hidden
+ .source __FILE__
diff --git a/libc/tinymath/fmodl.S b/libc/tinymath/fmodl.S
index 98a6d07a2..dd48151d4 100644
--- a/libc/tinymath/fmodl.S
+++ b/libc/tinymath/fmodl.S
@@ -17,6 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "ape/lib/pc.h"
#include "libc/macros.h"
.source __FILE__
@@ -27,8 +28,8 @@ tinymath_fmodl:
fldt 32(%rbp)
fldt 16(%rbp)
1: fprem
- fnstsw %ax
- testb $4,%ah
+ fnstsw
+ test $FPU_C2>>8,%ah
jnz 1b
fstp %st(1)
pop %rbp
diff --git a/libc/tinymath/ilogb.S b/libc/tinymath/ilogb.S
index 9cb0a10ae..985874f04 100644
--- a/libc/tinymath/ilogb.S
+++ b/libc/tinymath/ilogb.S
@@ -18,10 +18,25 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.source __FILE__
+/ Returns log₂ₓ exponent part of double.
+/
+/ @param 𝑥 is double passed in %xmm0
+/ @return result in %eax
+/ @note needs sse3
tinymath_ilogb:
- ezlea tinymath_ilogbl,ax
- jmp _d2ld2
+ push %rbp
+ mov %rsp,%rbp
+ .profilable
+ push %rax
+ movsd %xmm0,(%rsp)
+ fldl (%rsp)
+ fxtract
+ fstp %st
+ fisttpl (%rsp)
+ mov (%rsp),%eax
+ leave
+ ret
.endfn tinymath_ilogb,globl
.alias tinymath_ilogb,ilogb
+ .source __FILE__
diff --git a/libc/tinymath/ilogbf.S b/libc/tinymath/ilogbf.S
index 36e2ccf12..9b92cddd6 100644
--- a/libc/tinymath/ilogbf.S
+++ b/libc/tinymath/ilogbf.S
@@ -18,10 +18,25 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.source __FILE__
+/ Returns log₂ₓ exponent part of float.
+/
+/ @param 𝑥 is float passed in %xmm0
+/ @return result in %eax
+/ @note needs sse3
tinymath_ilogbf:
- ezlea tinymath_ilogbl,ax
- jmp _f2ld2
+ push %rbp
+ mov %rsp,%rbp
+ .profilable
+ push %rax
+ movss %xmm0,(%rsp)
+ flds (%rsp)
+ fxtract
+ fstp %st
+ fisttpl (%rsp)
+ mov (%rsp),%eax
+ leave
+ ret
.endfn tinymath_ilogbf,globl
.alias tinymath_ilogbf,ilogbf
+ .source __FILE__
diff --git a/libc/tinymath/ilogbl.S b/libc/tinymath/ilogbl.S
index fbe36db6c..97535971f 100644
--- a/libc/tinymath/ilogbl.S
+++ b/libc/tinymath/ilogbl.S
@@ -18,38 +18,24 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.source __FILE__
+/ Returns log₂ₓ exponent part of long double.
+/
+/ @param 𝑥 is long double passed on stack
+/ @return result in %eax
+/ @note needs sse3
tinymath_ilogbl:
+ push %rbp
+ mov %rsp,%rbp
.profilable
- sub $24,%rsp
- fldt 32(%rsp)
- fnstcw 14(%rsp)
- movzwl 14(%rsp),%eax
- orb $12,%ah
- movw %ax,12(%rsp)
+ fldt 16(%rbp)
fxtract
fstp %st
- fldcw 12(%rsp)
- fistpl 8(%rsp)
- fldcw 14(%rsp)
- movl 8(%rsp),%eax
- add $24,%rsp
+ push %rax
+ fisttpl (%rsp)
+ mov (%rsp),%eax
+ leave
ret
.endfn tinymath_ilogbl,globl
.alias tinymath_ilogbl,ilogbl
-
-/*
- TODO(jart)
-.globl ilogbl
-.type ilogbl,@function
-ilogbl: sub $24,%rsp
- fldt 32(%rsp)
- fxtract
- fstp %st
- fisttpl 12(%rsp)
- movl 12(%rsp),%eax
- add $24,%rsp
- ret
-.size ilogbl,.-ilogbl
-*/
+ .source __FILE__
diff --git a/libc/tinymath/log1p.S b/libc/tinymath/log1p.S
index 6384cf167..cd67d530a 100644
--- a/libc/tinymath/log1p.S
+++ b/libc/tinymath/log1p.S
@@ -20,7 +20,12 @@
#include "libc/macros.h"
.source __FILE__
-log1p: push %rbp
+/ Returns log(𝟷+𝑥).
+/
+/ @param 𝑥 is double scalar in low half of %xmm0
+/ @return double scalar in low half of %xmm0
+tinymath_log1p:
+ push %rbp
mov %rsp,%rbp
.profilable
push %rax
@@ -48,12 +53,11 @@ log1p: push %rbp
fstpl (%rsp)
vmovsd (%rsp),%xmm0
jmp 0b
- .endfn log1p,globl
+ .endfn tinymath_log1p,globl
+ .alias tinymath_log1p,log1p
- .section .rodata.cst16,"aM",@progbits,16
- .align 16
+ .rodata.cst16
.LC16: .long 205731576
.long 2515933592
.long 16381
.long 0
- .previous
diff --git a/libc/tinymath/log1pf.S b/libc/tinymath/log1pf.S
index 768a341af..ee5c6bed9 100644
--- a/libc/tinymath/log1pf.S
+++ b/libc/tinymath/log1pf.S
@@ -20,7 +20,12 @@
#include "libc/macros.h"
.source __FILE__
-log1pf: push %rbp
+/ Returns log(𝟷+𝑥).
+/
+/ @param 𝑥 is float scalar in low quarter of %xmm0
+/ @return float scalar in low quarter of %xmm0
+tinymath_log1pf:
+ push %rbp
mov %rsp,%rbp
.profilable
push %rax
@@ -46,12 +51,11 @@ log1pf: push %rbp
fxch %st(1)
fyl2x
jmp 1b
- .endfn log1pf,globl
+ .endfn tinymath_log1pf,globl
+ .alias tinymath_log1pf,log1pf
- .section .rodata.cst16,"aM",@progbits,16
- .align 16
+ .rodata.cst16
.LC16: .long 205731576
.long 2515933592
.long 16381
.long 0
- .previous
diff --git a/libc/tinymath/log1pl.S b/libc/tinymath/log1pl.S
index 121119b0e..2eb8ba66f 100644
--- a/libc/tinymath/log1pl.S
+++ b/libc/tinymath/log1pl.S
@@ -20,7 +20,12 @@
#include "libc/macros.h"
.source __FILE__
-log1pl: push %rbp
+/ Returns log(𝟷+𝑥).
+/
+/ @param 𝑥 is an 80-bit long double passed on stack in 16-bytes
+/ @return result of exponentiation on FPU stack in %st
+tinymath_log1pl:
+ push %rbp
mov %rsp,%rbp
.profilable
fldt 16(%rbp)
@@ -42,12 +47,11 @@ log1pl: push %rbp
fxch %st(1)
fyl2x
jmp 0b
- .endfn log1pl,globl
+ .endfn tinymath_log1pl,globl
+ .alias tinymath_log1pl,log1pl
- .section .rodata.cst16,"aM",@progbits,16
- .align 16
+ .rodata.cst16
.LC16: .long 205731576
.long 2515933592
.long 16381
.long 0
- .previous
diff --git a/libc/tinymath/log2l.S b/libc/tinymath/log2l.S
index f4bfbd282..f739da611 100644
--- a/libc/tinymath/log2l.S
+++ b/libc/tinymath/log2l.S
@@ -24,6 +24,7 @@
/
/ @param 𝑥 is an 80-bit long double passed on stack in 16-bytes
/ @return result in %st
+/ @see ilogbl()
log2l: push %rbp
mov %rsp,%rbp
.profilable
diff --git a/libc/tinymath/logb.S b/libc/tinymath/logb.S
index ec7e805aa..e14ecdf7e 100644
--- a/libc/tinymath/logb.S
+++ b/libc/tinymath/logb.S
@@ -18,18 +18,14 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.source __FILE__
-logb: push %rbp
- mov %rsp,%rbp
- .profilable
- push %rax
- movsd %xmm0,(%rsp)
- fldl (%rsp)
- fxtract
- fstp %st
- fstpl (%rsp)
- movsd (%rsp),%xmm0
- leave
- ret
- .endfn logb,globl
+/ Returns log₂ₓ exponent part of double.
+/
+/ @param 𝑥 is double passed in %xmm0
+/ @return result in %xmm0
+tinymath_logb:
+ ezlea tinymath_logbl,ax
+ jmp _d2ld2
+ .endfn tinymath_logb,globl
+ .alias tinymath_logb,logb
+ .source __FILE__
diff --git a/libc/tinymath/logbf.S b/libc/tinymath/logbf.S
index d7b3d7dfd..8538dac19 100644
--- a/libc/tinymath/logbf.S
+++ b/libc/tinymath/logbf.S
@@ -18,18 +18,14 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.source __FILE__
-logbf: push %rbp
- mov %rsp,%rbp
- .profilable
- push %rax
- movss %xmm0,-4(%rbp)
- flds -4(%rbp)
- fxtract
- fstp %st
- fstps -4(%rbp)
- movss -4(%rbp),%xmm0
- leave
- ret
- .endfn logbf,globl
+/ Returns log₂ₓ exponent part of float.
+/
+/ @param 𝑥 is float passed in %xmm0
+/ @return result in %xmm0
+tinymath_logbf:
+ ezlea tinymath_logbl,ax
+ jmp _f2ld2
+ .endfn tinymath_logbf,globl
+ .alias tinymath_logbf,logbf
+ .source __FILE__
diff --git a/libc/tinymath/logbl.S b/libc/tinymath/logbl.S
index d860f46d2..07c4a64ba 100644
--- a/libc/tinymath/logbl.S
+++ b/libc/tinymath/logbl.S
@@ -18,9 +18,13 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
-.source __FILE__
-logbl: push %rbp
+/ Returns log₂ₓ exponent part of long double.
+/
+/ @param 𝑥 is long double passed on stack
+/ @return result in %st0
+tinymath_logbl:
+ push %rbp
mov %rsp,%rbp
.profilable
fldt 16(%rbp)
@@ -28,4 +32,6 @@ logbl: push %rbp
fstp %st
pop %rbp
ret
- .endfn logbl,globl
+ .endfn tinymath_logbl,globl
+ .alias tinymath_logbl,logbl
+ .source __FILE__
diff --git a/libc/tinymath/lroundl.S b/libc/tinymath/lroundl.S
index d174fec6a..baa2d0bfb 100644
--- a/libc/tinymath/lroundl.S
+++ b/libc/tinymath/lroundl.S
@@ -34,7 +34,7 @@ tinymath_lroundl:
or $0b00000100,%dh # →-∞
mov %dx,-4(%rbp)
fxam
- fnstsw %ax
+ fnstsw
fabs
test $FPU_C1>>8,%ah
fadds .Lhalf(%rip)
diff --git a/libc/tinymath/remainderl.S b/libc/tinymath/remainderl.S
index 99b608688..942fac1d4 100644
--- a/libc/tinymath/remainderl.S
+++ b/libc/tinymath/remainderl.S
@@ -28,9 +28,9 @@ tinymath_remainderl:
fldt 32(%rbp)
fldt 16(%rbp)
1: fprem1
- fnstsw %ax
+ fnstsw
test $FPU_C2>>8,%ah
- jne 1b
+ jnz 1b
fstp %st(1)
pop %rbp
ret
diff --git a/libc/tinymath/roundl.S b/libc/tinymath/roundl.S
index 8f87f931a..4f4413da7 100644
--- a/libc/tinymath/roundl.S
+++ b/libc/tinymath/roundl.S
@@ -19,8 +19,11 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "ape/lib/pc.h"
#include "libc/macros.h"
-.source __FILE__
+/ Rounds to nearest integer, away from zero.
+/
+/ @param 𝑥 is an 80-bit long double passed on stack in 16-bytes
+/ @return result of exponentiation on FPU stack in %st
tinymath_roundl:
push %rbp
mov %rsp,%rbp
@@ -30,10 +33,10 @@ tinymath_roundl:
fnstcw -8(%rbp)
movzwl -8(%rbp),%edx
and $0b11110011,%dh # RC (Rounding Control)
- or $0b00000100,%dh # →-∞
+ or $0b00000100,%dh # →-∞ a.k.a. floor()
mov %dx,-4(%rbp)
- fxam
- fnstsw %ax
+ fxam # C1 is set to sign bit
+ fnstsw
fabs
test $FPU_C1>>8,%ah
fadds .Lhalf(%rip)
@@ -46,6 +49,7 @@ tinymath_roundl:
ret
.endfn tinymath_roundl,globl
.alias tinymath_roundl,roundl
+ .source __FILE__
.rodata.cst4
.Lhalf: .float .5
diff --git a/libc/tinymath/scalbnf.S b/libc/tinymath/scalbnf.S
index a7c4f6494..6c7a7bb17 100644
--- a/libc/tinymath/scalbnf.S
+++ b/libc/tinymath/scalbnf.S
@@ -20,16 +20,21 @@
#include "libc/macros.h"
.source __FILE__
-tinymath_scalbnl:
+tinymath_scalbnf:
+ push %rbp
+ mov %rsp,%rbp
.profilable
- sub $24,%rsp
- fldt 32(%rsp)
- movl %edi,12(%rsp)
- fildl 12(%rsp)
+ push %rax
+ movss %xmm0,-4(%rbp)
+ flds -4(%rbp)
+ movl %edi,-4(%rbp)
+ fildl -4(%rbp)
fxch %st(1)
- add $24,%rsp
fscale
fstp %st(1)
+ fstps -4(%rbp)
+ movss -4(%rbp),%xmm0
+ leave
ret
- .endfn tinymath_scalbnl,globl
- .alias tinymath_scalbnl,scalbnl
+ .endfn tinymath_scalbnf,globl
+ .alias tinymath_scalbnf,scalbnf
diff --git a/libc/tinymath/scalbnl.S b/libc/tinymath/scalbnl.S
index 6c7a7bb17..8e97cc5ff 100644
--- a/libc/tinymath/scalbnl.S
+++ b/libc/tinymath/scalbnl.S
@@ -20,21 +20,22 @@
#include "libc/macros.h"
.source __FILE__
-tinymath_scalbnf:
+/ Returns 𝑥 × 𝑟ʸ where 𝑟 is radix of hardware architecture.
+/
+/ @param 𝑥 is long double passed on stack
+/ @param 𝑦 is exponent via %edi
+/ @see FLT_RADIX
+tinymath_scalbnl:
push %rbp
mov %rsp,%rbp
.profilable
- push %rax
- movss %xmm0,-4(%rbp)
- flds -4(%rbp)
- movl %edi,-4(%rbp)
- fildl -4(%rbp)
- fxch %st(1)
+ fldt 16(%rsp)
+ push %rdi
+ fildl (%rsp)
+ fxch
fscale
fstp %st(1)
- fstps -4(%rbp)
- movss -4(%rbp),%xmm0
leave
ret
- .endfn tinymath_scalbnf,globl
- .alias tinymath_scalbnf,scalbnf
+ .endfn tinymath_scalbnl,globl
+ .alias tinymath_scalbnl,scalbnl
diff --git a/libc/tinymath/sincosl.S b/libc/tinymath/sincosl.S
index 96f47be9f..78a97c42e 100644
--- a/libc/tinymath/sincosl.S
+++ b/libc/tinymath/sincosl.S
@@ -17,6 +17,7 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "ape/lib/pc.h"
#include "libc/macros.h"
.source __FILE__
@@ -31,10 +32,22 @@ tinymath_sincosl:
mov %rsp,%rbp
.profilable
fldt 16(%rbp)
- fsincos
+0: fsincos
+ fstsw
+ test $FPU_C2>>8,%ah
+ jnz 1f
fstpt (%rsi)
fstpt (%rdi)
pop %rbp
ret
+1: fldpi
+ fadd %st
+ fxch
+2: fprem1
+ fnstsw
+ test $FPU_C2>>8,%ah
+ jnz 2b
+ fstp %st(1)
+ jmp 0b
.endfn tinymath_sincosl,globl
.alias tinymath_sincosl,sincosl
diff --git a/libc/tinymath/truncl.S b/libc/tinymath/truncl.S
index a50d93cbc..3966629ab 100644
--- a/libc/tinymath/truncl.S
+++ b/libc/tinymath/truncl.S
@@ -26,7 +26,7 @@ tinymath_truncl:
fldt 32(%rsp)
fnstcw 14(%rsp)
movzwl 14(%rsp),%eax
- or $12,%ah
+ or $0b1100,%ah # round to zero
mov %ax,12(%rsp)
fldcw 12(%rsp)
frndint
diff --git a/libc/unicode/kcombiningchars.S b/libc/unicode/kcombiningchars.S
index cf5b1eee1..b05c5e58b 100644
--- a/libc/unicode/kcombiningchars.S
+++ b/libc/unicode/kcombiningchars.S
@@ -36,11 +36,11 @@ kCombiningChars:
.init.start 400,_init_kCombiningChars
push %rsi
- mov $1257,%edx
+ mov $1203,%edx
call lz4cpy
mov %rax,%rdi
pop %rsi
- add $1264,%rsi
+ add $1208,%rsi
.init.end 400,_init_kCombiningChars
/ The data below is sparse, as evidenced by:
@@ -74,112 +74,105 @@ kCombiningCharsCtor:
/ o/libc/str/CombiningChars.bin.lz4
.initro 400,_init_kCombiningChars
kCombiningCharsLz4:
- .byte 0x16,0x00,0x01,0x00,0x24,0x40,0x01,0x0a #▬ ☺ $@☺◙
- .byte 0x00,0x4f,0x81,0x10,0x01,0x00,0x01,0x00 # Oü►☺ ☺
- .byte 0x2c,0x7b,0x3c,0x00,0xfc,0xff,0xe0,0xaf #,{< ⁿλα»
- .byte 0xff,0x01,0x00,0x3f,0x20,0x00,0x30,0x38 #λ☺ ? 08
- .byte 0x00,0x0c,0x2f,0xf8,0x03,0x5a,0x00,0x0d # ♀/°♥Z ♪
- .byte 0x10,0xfe,0x49,0x00,0x23,0xbf,0xb6,0x0e #►■I .┐╢♫
- .byte 0x00,0x42,0x3f,0x00,0xff,0x17,0x39,0x00 # B? λ↨9
- .byte 0x48,0xff,0xff,0x00,0x00,0xb7,0x00,0xf7 #Hλλ ╖ ≈
- .byte 0x01,0xc0,0xbf,0x9f,0x3d,0x00,0x00,0x00 #☺└┐ƒ=
- .byte 0x80,0x02,0x00,0x00,0x00,0xff,0xff,0xff #Ç☻ λλλ
- .byte 0x07,0x1a,0x00,0x13,0xff,0x28,0x00,0x91 #•→ ‼λ( æ
- .byte 0xf8,0x0f,0x20,0x00,0x00,0xc0,0xfb,0xef #°☼ └√∩
- .byte 0x3e,0x0e,0x00,0x1b,0x0e,0x8a,0x00,0x26 #>♫ ←♫è &
- .byte 0xff,0xff,0x37,0x00,0xa2,0x14,0xfe,0x21 #λλ7 ó¶■!
- .byte 0xfe,0x00,0x0c,0x00,0x00,0x00,0x02,0x10 #■ ♀ ☻►
- .byte 0x00,0x40,0x10,0x1e,0x20,0x00,0x10,0x00 # @►▲ ►
- .byte 0x23,0x40,0x06,0x10,0x00,0x20,0x86,0x39 #.@♠► å9
- .byte 0x1a,0x00,0x24,0x23,0x00,0x10,0x00,0x21 #→ $. ► !
- .byte 0xbe,0x21,0x20,0x00,0x13,0xfc,0x30,0x00 #╛! ‼ⁿ0
- .byte 0x41,0x90,0x1e,0x20,0x40,0x40,0x00,0x13 #AÉ▲ @@ ‼
- .byte 0x04,0x5e,0x00,0x22,0x01,0x20,0x08,0x00 #♦^ .☺ ◘
- .byte 0x13,0x11,0x93,0x00,0x38,0xc1,0x3d,0x60 #‼◄ô 8┴=`
- .byte 0x60,0x00,0x31,0x90,0x40,0x30,0x40,0x00 #` 1É@0@
- .byte 0x13,0x00,0x0f,0x01,0x13,0x18,0x70,0x00 #‼ ☼☺‼↑p
- .byte 0x06,0x9f,0x00,0x27,0x04,0x5c,0x0d,0x00 #♠ƒ '♦\♪
- .byte 0x48,0xf2,0x07,0x80,0x7f,0x1d,0x00,0x45 #H≥•Ç⌂↔ E
- .byte 0xf2,0x1f,0x00,0x3f,0x0d,0x00,0x43,0x03 #≥▼ ?♪ C♥
- .byte 0x00,0x00,0xa0,0x57,0x00,0x50,0xfe,0x7f # áW P■⌂
- .byte 0xdf,0xe0,0xff,0x41,0x01,0x28,0x1f,0x40 #▀αλA☺(▼@
- .byte 0x2f,0x00,0xff,0x00,0xe0,0xfd,0x66,0x00 #/ λ α²f
- .byte 0x00,0x00,0xc3,0x01,0x00,0x1e,0x00,0x64 # ├☺ ▲ d
- .byte 0x20,0x00,0x20,0x7a,0x01,0x05,0x1f,0xff # z☺♣▼λ
- .byte 0x01,0x00,0x00,0x0f,0x13,0x02,0x18,0x2f #☺ ☼‼☻↑/
- .byte 0xe0,0x00,0x01,0x00,0x62,0x13,0x1c,0x04 #α ☺ b‼∟♦
- .byte 0x00,0x26,0x0c,0x00,0x42,0x01,0x52,0xb0 # &♀ B☺R░
- .byte 0x3f,0x40,0xfe,0x0f,0xe8,0x00,0x1a,0x78 #?@■☼Φ →x
- .byte 0x2e,0x00,0x26,0x60,0x00,0x85,0x01,0x04 #. &` à☺♦
- .byte 0x14,0x00,0x4f,0x87,0x01,0x04,0x0e,0x60 #¶ Oç☺♦♫`
- .byte 0x00,0x07,0x23,0x80,0x09,0x3f,0x03,0x53 # •.Ç○?♥S
- .byte 0x7f,0xe5,0x1f,0xf8,0x9f,0x2a,0x01,0x05 #⌂σ▼°ƒ*☺♣
- .byte 0x8e,0x01,0x11,0x0f,0x06,0x00,0x32,0xd0 #Ä☺◄☼♠ 2╨
- .byte 0x17,0x04,0x70,0x02,0x01,0xd0,0x01,0x23 #↨♦p☻☺╨☺.
- .byte 0x3c,0x3b,0x32,0x00,0x13,0xa3,0xde,0x01 #<;2 ‼ú▐☺
- .byte 0x2f,0xf0,0xcf,0x58,0x00,0x00,0x6f,0xf7 #/≡╧X o≈
- .byte 0xff,0xfd,0x21,0x10,0x03,0x8c,0x01,0x0c #λ²!►♥î☺♀
- .byte 0x1f,0xfb,0x15,0x01,0x24,0x40,0xa0,0x03 #▼√§☺$@á♥
- .byte 0xe0,0x00,0x02,0x00,0x72,0x60,0x00,0xf8 #α ☻ r` °
- .byte 0x00,0x00,0x00,0x7c,0x15,0x00,0x2c,0xdf # |§ ,▀
- .byte 0xff,0x62,0x00,0x2f,0x01,0x00,0x01,0x00 #λb /☺ ☺
- .byte 0xff,0x6b,0x1d,0x80,0xff,0x01,0x1c,0x80 #λk↔Çλ☺∟Ç
- .byte 0xa2,0x01,0x0f,0x68,0x00,0x32,0x19,0x3c #ó☺☼h 2↓<
- .byte 0x0e,0x00,0x2f,0x1e,0x00,0x01,0x00,0xff #♫ /▲ ☺ λ
+ .byte 0x1f,0x00,0x01,0x00,0x4c,0x19,0xff,0x01 #▼ ☺ L↓λ☺
+ .byte 0x00,0x0f,0x30,0x00,0x0f,0x2f,0xf8,0x03 # ☼0 ☼/°♥
+ .byte 0x22,0x00,0x0d,0x10,0xfe,0x49,0x00,0x23 #. ♪►■I .
+ .byte 0xbf,0xb6,0x0e,0x00,0x42,0x3f,0x00,0xff #┐╢♫ B⁇ λ
+ .byte 0x17,0x39,0x00,0x00,0x5e,0x00,0x17,0x01 #↨9 ^ ↨☺
+ .byte 0x28,0x00,0x92,0xc0,0xbf,0x9f,0x3d,0x00 #( Æ└┐ƒ=
+ .byte 0x00,0x00,0x80,0x02,0x86,0x00,0x17,0x07 # Ç☻å ↨•
+ .byte 0x1a,0x00,0x13,0xff,0x28,0x00,0x91,0xf8 #→ ‼λ( æ°
+ .byte 0x0f,0x20,0x00,0x00,0xc0,0xfb,0xef,0x3e #☼ └√∩>
+ .byte 0x0e,0x00,0x1b,0x0e,0x8a,0x00,0x26,0xff #♫ ←♫è &λ
+ .byte 0xff,0x37,0x00,0xa2,0x14,0xfe,0x21,0xfe #λ7 ó¶■!■
+ .byte 0x00,0x0c,0x00,0x00,0x00,0x02,0x10,0x00 # ♀ ☻►
+ .byte 0x40,0x10,0x1e,0x20,0x00,0x10,0x00,0x23 #@►▲ ► .
+ .byte 0x40,0x06,0x10,0x00,0x20,0x86,0x39,0x1a #@♠► å9→
+ .byte 0x00,0x24,0x23,0x00,0x10,0x00,0x21,0xbe # $. ► !╛
+ .byte 0x21,0x20,0x00,0x13,0xfc,0x30,0x00,0x41 #! ‼ⁿ0 A
+ .byte 0x90,0x1e,0x20,0x40,0x40,0x00,0x13,0x04 #É▲ @@ ‼♦
+ .byte 0x5e,0x00,0x22,0x01,0x20,0x08,0x00,0x13 #^ .☺ ◘ ‼
+ .byte 0x11,0x93,0x00,0x38,0xc1,0x3d,0x60,0x60 #◄ô 8┴=``
+ .byte 0x00,0x31,0x90,0x40,0x30,0x40,0x00,0x13 # 1É@0@ ‼
+ .byte 0x00,0x0f,0x01,0x13,0x18,0x70,0x00,0x06 # ☼☺‼↑p ♠
+ .byte 0x9f,0x00,0x27,0x04,0x5c,0x0d,0x00,0x48 #ƒ ‘♦\♪ H
+ .byte 0xf2,0x07,0x80,0x7f,0x1d,0x00,0x45,0xf2 #≥•Ç⌂↔ E≥
+ .byte 0x1f,0x00,0x3f,0x0d,0x00,0x43,0x03,0x00 #▼ ⁇♪ C♥
+ .byte 0x00,0xa0,0x57,0x00,0x50,0xfe,0x7f,0xdf # áW P■⌂▀
+ .byte 0xe0,0xff,0x41,0x01,0x28,0x1f,0x40,0x2f #αλA☺(▼@/
+ .byte 0x00,0xff,0x00,0xe0,0xfd,0x66,0x00,0x00 # λ α²f
+ .byte 0x00,0xc3,0x01,0x00,0x1e,0x00,0x64,0x20 # ├☺ ▲ d
+ .byte 0x00,0x20,0xcc,0x01,0x0b,0x0f,0xd2,0x01 # ╠☺♂☼╥☺
+ .byte 0x1d,0x06,0x66,0x00,0x1f,0x00,0x01,0x00 #↔♠f ▼ ☺
+ .byte 0x62,0x13,0x1c,0x04,0x00,0x26,0x0c,0x00 #b‼∟♦ &♀
+ .byte 0x42,0x01,0x52,0xb0,0x3f,0x40,0xfe,0x0f #B☺R░⁇@■☼
+ .byte 0xe8,0x00,0x1a,0x78,0x2e,0x00,0x26,0x60 #Φ →x. &`
+ .byte 0x00,0x85,0x01,0x04,0x14,0x00,0x4f,0x87 # à☺♦¶ Oç
+ .byte 0x01,0x04,0x0e,0x60,0x00,0x07,0x22,0x80 #☺♦♫` •.Ç
+ .byte 0x09,0x08,0x00,0x63,0x40,0x7f,0xe5,0x1f #○◘ c@⌂σ▼
+ .byte 0xf8,0x9f,0x2a,0x01,0x05,0x8e,0x01,0x11 #°ƒ*☺♣Ä☺◄
+ .byte 0x0f,0x06,0x00,0x32,0xd0,0x17,0x04,0x70 #☼♠ 2╨↨♦p
+ .byte 0x02,0x01,0xd0,0x01,0x23,0x3c,0x3b,0x32 #☻☺╨☺.<;2
+ .byte 0x00,0x13,0xa3,0xde,0x01,0x2f,0xf0,0xcf # ‼ú▐☺/≡╧
+ .byte 0x58,0x00,0x00,0x6f,0xf7,0xff,0xfd,0x21 #X o≈λ²!
+ .byte 0x10,0x03,0x8c,0x01,0x0c,0x1f,0xfb,0x1f #►♥î☺♀▼√▼
+ .byte 0x01,0x2e,0x52,0xf8,0x00,0x00,0x00,0x7c #☺.R° |
+ .byte 0x0b,0x00,0x2c,0xdf,0xff,0x62,0x00,0x2f #♂ ,▀λb /
+ .byte 0x01,0x00,0x01,0x00,0xff,0x6b,0x1d,0x80 #☺ ☺ λk↔Ç
+ .byte 0xff,0x01,0x1c,0x80,0xa2,0x01,0x0f,0x68 #λ☺∟Çó☺☼h
+ .byte 0x00,0x32,0x19,0x3c,0x0e,0x00,0x2f,0x06 # 2↓<♫ /♠
+ .byte 0x00,0x01,0x00,0xff,0xff,0xff,0xff,0xff # ☺ λλλλλ
.byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ
- .byte 0xff,0xff,0xff,0xff,0xff,0xb3,0x20,0x80 #λλλλλ│ Ç
- .byte 0xf7,0xf6,0x12,0x27,0xc0,0x00,0xfb,0x12 #≈÷↕'└ √↕
- .byte 0x58,0xff,0xff,0x7f,0x00,0x03,0x24,0x00 #Xλλ⌂ ♥$
- .byte 0x1a,0x06,0x33,0x00,0x23,0x44,0x08,0xf4 #→♠3 .D◘⌠
- .byte 0x11,0x0b,0x4b,0x00,0x11,0x30,0x60,0x0f #◄♂K ◄0`☼
- .byte 0x11,0x03,0x70,0x0f,0x62,0xc0,0x3f,0x00 #◄♥p☼b└?
- .byte 0x00,0x80,0xff,0x46,0x00,0x02,0x10,0x14 # ÇλF ☻►¶
- .byte 0x20,0xc8,0x33,0x06,0x00,0x05,0x29,0x13 # ╚3♠ ♣)‼
- .byte 0x52,0x7e,0x66,0x00,0x08,0x10,0xf8,0x13 #R~f ◘►°‼
- .byte 0x02,0x11,0x00,0x21,0x9d,0xc1,0x43,0x12 #☻◄ !¥┴C↕
- .byte 0x19,0x30,0x66,0x13,0x1c,0x08,0x64,0x00 #↓0f‼∟◘d
- .byte 0x2f,0x20,0x21,0x96,0x0a,0xff,0xff,0xff #/ !û◙λλλ
- .byte 0xff,0xff,0xff,0xff,0xff,0xff,0xdb,0x1e #λλλλλλ█▲
- .byte 0x40,0x13,0x00,0x3f,0xfc,0xff,0x03,0x5d #@‼ ?ⁿλ♥]
- .byte 0x00,0x34,0x00,0x02,0x1a,0x0f,0x06,0x1a # 4 ☻→☼♠→
- .byte 0x08,0x1d,0x80,0xdc,0x1f,0x0a,0x91,0x0a #◘↔Ç▄▼◙æ◙
- .byte 0x1f,0x0e,0x7f,0x00,0x2c,0x1f,0x20,0x1d #▼♫⌂ ,▼ ↔
- .byte 0x00,0x09,0x0e,0x74,0x00,0x2f,0xc0,0x07 # ○♫t /└•
- .byte 0xdd,0x01,0xbd,0x22,0x6e,0xf0,0x23,0x1e #▌☺╜.n≡.▲
- .byte 0x0f,0x1c,0x00,0x01,0x1f,0x60,0x64,0x00 #☼∟ ☺▼`d
- .byte 0x34,0x1f,0xf0,0x44,0x00,0x30,0x05,0xf4 #4▼≡D 0♣⌠
- .byte 0x20,0x0b,0x18,0x00,0x1a,0x02,0xb1,0x1e # ♂↑ →☻▒▲
- .byte 0x03,0x72,0x1c,0x24,0x78,0x26,0xda,0x01 #♥r∟$x&┌☺
- .byte 0x00,0xf0,0x0c,0x35,0x80,0xef,0x1f,0x32 # ≡♀5Ç∩▼2
- .byte 0x02,0x02,0x20,0x00,0x29,0xc0,0x7f,0x26 #☻☻ )└⌂&
- .byte 0x1c,0x3f,0x80,0xd3,0x40,0x7c,0x02,0x01 #∟?Ç╙@|☻☺
- .byte 0x26,0xf8,0x07,0xc0,0x20,0x00,0x7e,0x00 #&°•└ ~
- .byte 0x3f,0xc0,0x1f,0x1f,0xc7,0x02,0x06,0x19 #?└▼▼╟☻♠↓
- .byte 0x5c,0x28,0x03,0x3f,0xf8,0x85,0x0d,0xb1 #\(♥?°à♪▒
- .byte 0x1c,0x0b,0x22,0xb0,0x01,0xa3,0x0d,0x04 #∟♂.░☺ú♪♦
- .byte 0x30,0x00,0x19,0xa7,0xde,0x00,0x29,0x28 #0 ↓º▐ )(
- .byte 0xbf,0x78,0x20,0x2f,0xbc,0x0f,0x38,0x0e #┐x /╝☼8♫
- .byte 0x0d,0x2f,0xff,0x06,0x96,0x01,0x20,0x10 #♪/λ♠û☺ ►
- .byte 0x0c,0x74,0x00,0x11,0xfe,0xd2,0x02,0x52 #♀t ◄■╥☻R
- .byte 0xf8,0x79,0x80,0x00,0x7e,0x4c,0x03,0x2f #°yÇ ~L♥/
- .byte 0xfc,0x7f,0xdb,0x03,0x20,0x28,0x7f,0xbf #ⁿ⌂█♥ (⌂┐
- .byte 0x1c,0x04,0x3b,0xff,0xfc,0x6d,0x20,0x00 #∟♦;λⁿm
- .byte 0x26,0x7e,0xb4,0x21,0x00,0x1f,0xa3,0x58 #&~┤! ▼úX
- .byte 0x00,0x18,0x1f,0x18,0x23,0x07,0xff,0xff # ↑▼↑.•λλ
- .byte 0x96,0x2f,0xff,0x01,0xfb,0x0d,0xff,0xff #û/λ☺√♪λλ
- .byte 0xff,0xff,0xff,0xff,0xc9,0x04,0xf0,0x0a #λλλλ╔♦≡◙
- .byte 0x1f,0x7f,0x1c,0x19,0x70,0x04,0x08,0x00 #▼⌂∟↓p♦◘
- .byte 0x1f,0x07,0x30,0x18,0xff,0xff,0xff,0xff #▼•0↑λλλλ
- .byte 0xff,0xff,0xff,0xff,0xff,0x96,0x2f,0x60 #λλλλλû/`
- .byte 0x0f,0x5f,0x25,0xff,0xff,0x87,0x5d,0x03 #☼_%λλç]♥
- .byte 0xf8,0xff,0xe7,0x0f,0x30,0x34,0x05,0x66 #°λτ☼04♣f
- .byte 0x37,0x0f,0xba,0x14,0xe2,0x01,0x01,0x00 #7☼║¶Γ☺☺
- .byte 0x12,0x7f,0x2d,0x3a,0x20,0x1f,0x20,0x01 #↕⌂-: ▼ ☺
- .byte 0x26,0x3f,0xf8,0xfe,0xff,0xc0,0x00,0x97 #&?°■λ└ ù
- .byte 0x5f,0x7f,0xff,0xff,0xf9,0xdb,0x13,0x0e #_⌂λλ∙█‼♫
- .byte 0x0e,0x1f,0x7f,0xb9,0x1a,0x24,0x0f,0xda #♫▼⌂╣→$☼┌
- .byte 0x01,0xa9,0x0a,0xf4,0x00,0x1f,0xf0,0x37 #☺⌐◙⌠ ▼≡7
- .byte 0x0f,0xff,0x44,0x2f,0xf8,0x00,0x01,0x00 #☼λD/° ☺
+ .byte 0xff,0xb3,0x20,0x80,0xf7,0xf6,0x12,0x28 #λ│ Ç≈÷↕(
+ .byte 0xc0,0x00,0xfb,0x12,0x0f,0x33,0x00,0x0c #└ √↕☼3 ♀
+ .byte 0x23,0x44,0x08,0xf4,0x11,0x0b,0x18,0x00 #.D◘⌠◄♂↑
+ .byte 0x11,0x30,0x60,0x0f,0x11,0x03,0x70,0x0f #◄0`☼◄♥p☼
+ .byte 0x62,0xc0,0x3f,0x00,0x00,0x80,0xff,0x4c #b└⁇ ÇλL
+ .byte 0x00,0x02,0x10,0x14,0x20,0xc8,0x33,0x06 # ☻►¶ ╚3♠
+ .byte 0x00,0x05,0x29,0x13,0x52,0x7e,0x66,0x00 # ♣)‼R~f
+ .byte 0x08,0x10,0xf8,0x13,0x02,0x11,0x00,0x21 #◘►°‼☻◄ !
+ .byte 0x9d,0xc1,0x43,0x12,0x2f,0x30,0x40,0x7c #¥┴C↕/0@|
+ .byte 0x00,0x0a,0x2f,0x20,0x21,0x96,0x0a,0xff # ◙/ !û◙λ
+ .byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ
+ .byte 0xdb,0x1f,0x40,0x60,0x1f,0x4b,0x2f,0x00 #█▼@`▼K/
+ .byte 0x00,0x06,0x1a,0x08,0x1f,0x80,0x3f,0x00 # ♠→◘▼Ç⁇
+ .byte 0x0c,0x1f,0x0e,0x7f,0x00,0x2c,0x1f,0x20 #♀▼♫⌂ ,▼
+ .byte 0x1d,0x00,0x09,0x0e,0x3e,0x1c,0x2f,0xc0 #↔ ○♫>∟/└
+ .byte 0x07,0xdd,0x01,0xbd,0x22,0x6e,0xf0,0x23 #•▌☺╜.n≡.
+ .byte 0x1e,0x0f,0x1c,0x00,0x01,0x1f,0x60,0x64 #▲☼∟ ☺▼`d
+ .byte 0x00,0x34,0x1f,0xf0,0x44,0x00,0x30,0x05 # 4▼≡D 0♣
+ .byte 0xf4,0x20,0x0b,0x18,0x00,0x1a,0x02,0xb1 #⌠ ♂↑ →☻▒
+ .byte 0x1e,0x03,0x72,0x1c,0x24,0x78,0x26,0xda #▲♥r∟$x&┌
+ .byte 0x01,0x00,0xf0,0x0c,0x33,0x80,0xef,0x1f #☺ ≡♀3Ç∩▼
+ .byte 0x2e,0x00,0x13,0x08,0x52,0x0d,0x48,0xc0 #. ‼◘R♪H└
+ .byte 0x7f,0x00,0x1e,0x66,0x02,0x1f,0xd3,0xe4 #⌂ ▲f☻▼╙Σ
+ .byte 0x02,0x01,0x36,0x80,0xf8,0x07,0xc0,0x20 #☻☺6Ç°•└
+ .byte 0x00,0x7e,0x00,0x3f,0xc0,0x1f,0x1f,0xc7 # ~ ⁇└▼▼╟
+ .byte 0x02,0x06,0x19,0x5c,0x28,0x03,0x3f,0xf8 #☻♠↓\(♥⁇°
+ .byte 0x85,0x0d,0xb1,0x1c,0x0b,0x22,0xb0,0x01 #à♪▒∟♂.░☺
+ .byte 0xa3,0x0d,0x04,0x30,0x00,0x19,0xa7,0xde #ú♪♦0 ↓º▐
+ .byte 0x00,0x29,0x28,0xbf,0x78,0x20,0x2f,0xbc # )(┐x /╝
+ .byte 0x0f,0x38,0x0e,0x0d,0x1f,0xff,0xf4,0x1c #☼8♫♪▼λ⌠∟
+ .byte 0x20,0x20,0xf0,0x0c,0x74,0x00,0x11,0xfe # ≡♀t ◄■
+ .byte 0xd2,0x02,0x52,0xf8,0x79,0x80,0x00,0x7e #╥☻R°yÇ ~
+ .byte 0x4c,0x03,0x3f,0xfc,0x7f,0x03,0x4c,0x00 #L♥⁇ⁿ⌂♥L
+ .byte 0x1f,0x17,0x7f,0xb1,0x00,0x5b,0xfc,0xff #▼↨⌂▒ [ⁿλ
+ .byte 0xff,0xfc,0x6d,0x20,0x00,0x26,0x7e,0xb4 #λⁿm &~┤
+ .byte 0x21,0x00,0x1f,0xa3,0x58,0x00,0x18,0x1f #! ▼úX ↑▼
+ .byte 0x18,0x23,0x07,0xff,0xff,0x96,0x2f,0xff #↑.•λλû/λ
+ .byte 0x01,0xfb,0x0d,0xff,0xff,0xff,0xff,0xff #☺√♪λλλλλ
+ .byte 0xff,0xc9,0x04,0xf0,0x0a,0x1f,0x7f,0x1c #λ╔♦≡◙▼⌂∟
+ .byte 0x19,0x70,0x04,0x08,0x00,0x1f,0x07,0x30 #↓p♦◘ ▼•0
+ .byte 0x18,0xff,0xff,0xff,0xff,0xff,0xff,0xff #↑λλλλλλλ
+ .byte 0xff,0xff,0x96,0x2f,0x60,0x0f,0x5f,0x25 #λλû/`☼_%
+ .byte 0xff,0xff,0x87,0x5d,0x03,0xf8,0xff,0xe7 #λλç]♥°λτ
+ .byte 0x0f,0x30,0x34,0x05,0x66,0x37,0x0f,0xba #☼04♣f7☼║
+ .byte 0x14,0xe2,0x01,0x01,0x00,0x12,0x7f,0x2d #¶Γ☺☺ ↕⌂-
+ .byte 0x3a,0x20,0x1f,0x20,0x01,0x26,0x3f,0xf8 #: ▼ ☺&⁇°
+ .byte 0xfe,0xff,0xc0,0x00,0x97,0x5f,0x7f,0xff #■λ└ ù_⌂λ
+ .byte 0xff,0xf9,0xdb,0x13,0x0e,0x0e,0x1f,0x7f #λ∙█‼♫♫▼⌂
+ .byte 0xb9,0x1a,0x24,0x0f,0xda,0x01,0xa9,0x0a #╣→$☼┌☺⌐◙
+ .byte 0xf4,0x00,0x3f,0xf0,0x07,0x00,0x01,0x00 #⌠ ⁇≡• ☺
.byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ
.byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ
.byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ
@@ -228,9 +221,9 @@ kCombiningCharsLz4:
.byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ
.byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ
.byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ
- .byte 0xff,0xff,0xee,0x57,0x02,0x00,0x00,0x00 #λλεW☻
- .byte 0xff,0x01,0x00,0x0c,0x20,0x00,0x1f,0xff #λ☺ ♀ ▼λ
- .byte 0x01,0x00,0x07,0x50,0xff,0xff,0xff,0x00 #☺ •Pλλλ
- .byte 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+ .byte 0xff,0xff,0xff,0xff,0x46,0x57,0x02,0x00 #λλλλFW☻
+ .byte 0x00,0x00,0xff,0x01,0x00,0x0c,0x20,0x00 # λ☺ ♀
+ .byte 0x1f,0xff,0x01,0x00,0x07,0x50,0xff,0xff #▼λ☺ •Pλλ
+ .byte 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00
.endobj kCombiningCharsLz4,globl,hidden
.previous
diff --git a/libc/x/xstrmul.c b/libc/x/xstrmul.c
index a41eb80a1..651ccc55b 100644
--- a/libc/x/xstrmul.c
+++ b/libc/x/xstrmul.c
@@ -17,7 +17,6 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/conv/sizemultiply.h"
#include "libc/log/check.h"
#include "libc/str/str.h"
#include "libc/x/x.h"
diff --git a/libc/zipos/find.c b/libc/zipos/find.c
index 5b581861c..146282aa3 100644
--- a/libc/zipos/find.c
+++ b/libc/zipos/find.c
@@ -25,15 +25,18 @@
#include "libc/zip.h"
#include "libc/zipos/zipos.h"
-ssize_t __zipos_find(const struct ZiposUri *name) {
+ssize_t __zipos_find(struct Zipos *zipos, const struct ZiposUri *name) {
size_t i, cf;
- assert(ZIP_CDIR_MAGIC(__zip_end) == kZipCdirHdrMagic);
- for (i = 0, cf = ZIP_CDIR_OFFSET(__zip_end); i < ZIP_CDIR_RECORDS(__zip_end);
- ++i, cf += ZIP_CFILE_HDRSIZE(&_base[0] + cf)) {
- assert(ZIP_CFILE_MAGIC(&_base[0] + cf) == kZipCfileHdrMagic);
- if (name->len == ZIP_CFILE_NAMESIZE(&_base[0] + cf) &&
- memcmp(name->path, ZIP_CFILE_NAME(&_base[0] + cf), name->len) == 0) {
- return cf;
+ if ((zipos = __zipos_get())) {
+ assert(ZIP_CDIR_MAGIC(zipos->cdir) == kZipCdirHdrMagic);
+ for (i = 0, cf = ZIP_CDIR_OFFSET(zipos->cdir);
+ i < ZIP_CDIR_RECORDS(zipos->cdir);
+ ++i, cf += ZIP_CFILE_HDRSIZE(zipos->map + cf)) {
+ assert(ZIP_CFILE_MAGIC(zipos->map + cf) == kZipCfileHdrMagic);
+ if (name->len == ZIP_CFILE_NAMESIZE(zipos->map + cf) &&
+ memcmp(name->path, ZIP_CFILE_NAME(zipos->map + cf), name->len) == 0) {
+ return cf;
+ }
}
}
return -1;
diff --git a/libc/zipos/fstat.c b/libc/zipos/fstat.c
index d33e435aa..98312d1dd 100644
--- a/libc/zipos/fstat.c
+++ b/libc/zipos/fstat.c
@@ -26,5 +26,5 @@
* @asyncsignalsafe
*/
int __zipos_fstat(const struct ZiposHandle *h, struct stat *st) {
- return __zipos_stat_impl(h->cfile, st);
+ return __zipos_stat_impl(__zipos_get(), h->cfile, st);
}
diff --git a/libc/zipos/get.c b/libc/zipos/get.c
new file mode 100644
index 000000000..6df01710f
--- /dev/null
+++ b/libc/zipos/get.c
@@ -0,0 +1,65 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/calls/calls.h"
+#include "libc/calls/struct/stat.h"
+#include "libc/limits.h"
+#include "libc/runtime/runtime.h"
+#include "libc/sysv/consts/auxv.h"
+#include "libc/sysv/consts/map.h"
+#include "libc/sysv/consts/o.h"
+#include "libc/sysv/consts/prot.h"
+#include "libc/zip.h"
+#include "libc/zipos/zipos.h"
+
+/**
+ * Returns pointer to zip central directory of current executable.
+ */
+struct Zipos *__zipos_get(void) {
+ static struct Zipos zipos;
+ static bool once;
+ const char *exe;
+ size_t mapsize;
+ uint8_t *cdir;
+ void *map;
+ int fd;
+ if (!once) {
+ if (ZIP_CDIR_MAGIC(__zip_end) == kZipCdirHdrMagic) {
+ zipos.map = _base;
+ zipos.cdir = __zip_end;
+ } else {
+ exe = (const char *)getauxval(AT_EXECFN);
+ if ((fd = open(exe, O_RDONLY)) != -1) {
+ if ((mapsize = getfiledescriptorsize(fd)) != SIZE_MAX &&
+ (map = mmap(NULL, mapsize, PROT_READ, MAP_SHARED, fd, 0)) !=
+ MAP_FAILED) {
+ if ((cdir = zipfindcentraldir(map, mapsize))) {
+ zipos.map = map;
+ zipos.cdir = cdir;
+ } else {
+ munmap(map, mapsize);
+ }
+ }
+ close(fd);
+ }
+ }
+ once = true;
+ }
+ return zipos.cdir ? &zipos : NULL;
+}
diff --git a/libc/zipos/open.c b/libc/zipos/open.c
index 8e5d89bda..0cb29c803 100644
--- a/libc/zipos/open.c
+++ b/libc/zipos/open.c
@@ -23,10 +23,10 @@
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/stat.h"
-#include "libc/conv/sizemultiply.h"
#include "libc/dce.h"
#include "libc/macros.h"
#include "libc/mem/mem.h"
+#include "libc/nexgen32e/crc32.h"
#include "libc/runtime/rbx.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
@@ -83,37 +83,39 @@ static int __zipos_inflate_tiny(struct ZiposHandle *h, uint8_t *data,
return undeflate(h->mem, h->size, data, size, &ds) != -1 ? 0 : eio();
}
-static int __zipos_load(size_t cf, unsigned flags, int mode) {
+static int __zipos_load(struct Zipos *zipos, size_t cf, unsigned flags,
+ int mode) {
int fd;
size_t lf;
struct ZiposHandle *h;
- lf = ZIP_CFILE_OFFSET(&_base[0] + cf);
- assert(ZIP_LFILE_MAGIC(&_base[0] + lf) == kZipLfileHdrMagic);
- assert(ZIP_LFILE_COMPRESSIONMETHOD(&_base[0] + lf) == kZipCompressionNone ||
- ZIP_LFILE_COMPRESSIONMETHOD(&_base[0] + lf) == kZipCompressionDeflate);
+ lf = ZIP_CFILE_OFFSET(zipos->map + cf);
+ assert(ZIP_LFILE_MAGIC(zipos->map + lf) == kZipLfileHdrMagic);
+ assert(ZIP_LFILE_COMPRESSIONMETHOD(zipos->map + lf) == kZipCompressionNone ||
+ ZIP_LFILE_COMPRESSIONMETHOD(zipos->map + lf) ==
+ kZipCompressionDeflate);
if ((fd = createfd()) == -1) return -1;
if (!(h = calloc(1, sizeof(*h)))) return -1;
h->cfile = cf;
- if ((h->size = ZIP_LFILE_UNCOMPRESSEDSIZE(&_base[0] + lf))) {
- if (ZIP_LFILE_COMPRESSIONMETHOD(&_base[0] + lf)) {
- assert(ZIP_LFILE_COMPRESSEDSIZE(&_base[0] + lf));
+ if ((h->size = ZIP_LFILE_UNCOMPRESSEDSIZE(zipos->map + lf))) {
+ if (ZIP_LFILE_COMPRESSIONMETHOD(zipos->map + lf)) {
+ assert(ZIP_LFILE_COMPRESSEDSIZE(zipos->map + lf));
h->mapsize = ROUNDUP(h->size + FRAMESIZE, FRAMESIZE);
if ((h->map = mapanon(h->mapsize)) != MAP_FAILED) {
h->mem = h->map + FRAMESIZE / 2;
if ((IsTiny() ? __zipos_inflate_tiny : __zipos_inflate_fast)(
- h, ZIP_LFILE_CONTENT(&_base[0] + lf),
- ZIP_LFILE_COMPRESSEDSIZE(&_base[0] + lf)) == -1) {
+ h, ZIP_LFILE_CONTENT(zipos->map + lf),
+ ZIP_LFILE_COMPRESSEDSIZE(zipos->map + lf)) == -1) {
fd = -1;
}
} else {
fd = -1;
}
} else {
- h->mem = ZIP_LFILE_CONTENT(&_base[0] + lf);
+ h->mem = ZIP_LFILE_CONTENT(zipos->map + lf);
}
}
if (!IsTiny() && fd != -1 &&
- crc32_z(0, h->mem, h->size) != ZIP_LFILE_CRC32(&_base[0] + lf)) {
+ crc32_z(0, h->mem, h->size) != ZIP_LFILE_CRC32(zipos->map + lf)) {
fd = eio();
}
if (fd != -1) {
@@ -136,12 +138,17 @@ int __zipos_open(const struct ZiposUri *name, unsigned flags, int mode) {
int fd;
ssize_t cf;
sigset_t oldmask;
+ struct Zipos *zipos;
assert((flags & O_ACCMODE) == O_RDONLY);
sigprocmask(SIG_BLOCK, &kSigsetFull, &oldmask);
- if ((cf = __zipos_find(name)) != -1) {
- fd = __zipos_load(cf, flags, mode);
+ if ((zipos = __zipos_get())) {
+ if ((cf = __zipos_find(zipos, name)) != -1) {
+ fd = __zipos_load(zipos, cf, flags, mode);
+ } else {
+ fd = enoent();
+ }
} else {
- fd = enoent();
+ fd = enoexec();
}
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return fd;
diff --git a/libc/zipos/stat-impl.c b/libc/zipos/stat-impl.c
index 9f0820852..db621cb86 100644
--- a/libc/zipos/stat-impl.c
+++ b/libc/zipos/stat-impl.c
@@ -22,17 +22,23 @@
#include "libc/calls/struct/stat.h"
#include "libc/runtime/rbx.h"
#include "libc/str/str.h"
+#include "libc/sysv/errfuns.h"
#include "libc/zip.h"
#include "libc/zipos/zipos.h"
-int __zipos_stat_impl(size_t cf, struct stat *st) {
- memset(st, 0, sizeof(*st));
- if (ZIP_CFILE_FILEATTRCOMPAT(&_base[0] + cf) == kZipOsUnix) {
- st->st_mode = ZIP_CFILE_EXTERNALATTRIBUTES(&_base[0] + cf) >> 16;
+int __zipos_stat_impl(struct Zipos *zipos, size_t cf, struct stat *st) {
+ if (zipos && st) {
+ memset(st, 0, sizeof(*st));
+ if (ZIP_CFILE_FILEATTRCOMPAT(zipos->map + cf) == kZipOsUnix) {
+ st->st_mode = ZIP_CFILE_EXTERNALATTRIBUTES(zipos->map + cf) >> 16;
+ } else {
+ st->st_mode = 0100644;
+ }
+ st->st_size = ZIP_CFILE_UNCOMPRESSEDSIZE(zipos->map + cf);
+ st->st_blocks =
+ roundup(ZIP_CFILE_COMPRESSEDSIZE(zipos->map + cf), 512) / 512;
+ return 0;
} else {
- st->st_mode = 0100644;
+ return einval();
}
- st->st_size = ZIP_CFILE_UNCOMPRESSEDSIZE(&_base[0] + cf);
- st->st_blocks = roundup(ZIP_CFILE_COMPRESSEDSIZE(&_base[0] + cf), 512) / 512;
- return 0;
}
diff --git a/libc/zipos/stat.c b/libc/zipos/stat.c
index 8586dcba3..f27039d2e 100644
--- a/libc/zipos/stat.c
+++ b/libc/zipos/stat.c
@@ -28,9 +28,14 @@
*/
int __zipos_stat(const struct ZiposUri *name, struct stat *st) {
ssize_t cf;
- if ((cf = __zipos_find(name)) != -1) {
- return __zipos_stat_impl(cf, st);
+ struct Zipos *zipos;
+ if ((zipos = __zipos_get())) {
+ if ((cf = __zipos_find(zipos, name)) != -1) {
+ return __zipos_stat_impl(zipos, cf, st);
+ } else {
+ return enoent();
+ }
} else {
- return enoent();
+ return enoexec();
}
}
diff --git a/libc/zipos/zipcentraldir.S b/libc/zipos/zipcentraldir.S
deleted file mode 100644
index 335a603b1..000000000
--- a/libc/zipos/zipcentraldir.S
+++ /dev/null
@@ -1,58 +0,0 @@
-/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
-│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "ape/relocations.h"
-#include "libc/zip.h"
-#include "libc/macros.h"
-
-/ ZIP Central Directory.
- .section .piro.data.sort.zip.3,"a",@progbits
- .hidden __zip_start
- .globl __zip_start
- .type __zip_start,@object
- .align kZipCdirAlign
-__zip_start:
- .previous/*
- ...
- decentralized content
- ...
- */.section .piro.data.sort.zip.5,"a",@progbits
- .align kZipCdirAlign
-__zip_end:
- .long kZipCdirHdrMagic # magic
- .short 0 # disk
- .short 0 # starting disk
- .short v_zip_records # records on disk
- .short v_zip_records # records
- .long v_zip_cdirsize # size of central directory
- .long RVA(__zip_start) # central directory offset
- .short v_zip_commentsize # comment size
- .endobj __zip_end,globl,hidden
- .weak v_zip_records
- .weak v_zip_cdirsize
- .weak v_zip_commentsize
- .previous
-
- .yoink __zipos_close
- .yoink __zipos_fstat
- .yoink __zipos_open
- .yoink __zipos_parseuri
- .yoink __zipos_read
- .yoink __zipos_stat
- .source __FILE__
diff --git a/libc/zipos/zipos.S b/libc/zipos/zipos.S
new file mode 100644
index 000000000..567c23162
--- /dev/null
+++ b/libc/zipos/zipos.S
@@ -0,0 +1,29 @@
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
+│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/macros.h"
+
+ .yoink __zip_start
+ .yoink __zip_end
+ .yoink __zipos_close
+ .yoink __zipos_fstat
+ .yoink __zipos_open
+ .yoink __zipos_parseuri
+ .yoink __zipos_read
+ .yoink __zipos_stat
diff --git a/libc/zipos/zipos.h b/libc/zipos/zipos.h
index 6ca3661f4..cb3872671 100644
--- a/libc/zipos/zipos.h
+++ b/libc/zipos/zipos.h
@@ -6,6 +6,11 @@ COSMOPOLITAN_C_START_
struct stat;
struct iovec;
+struct Zipos {
+ uint8_t *map;
+ uint8_t *cdir;
+};
+
struct ZiposUri {
const char *path;
size_t len;
@@ -22,13 +27,14 @@ struct ZiposHandle {
extern const char kZiposSchemePrefix[4];
+struct Zipos *__zipos_get(void);
ssize_t __zipos_parseuri(const char *, struct ZiposUri *);
-ssize_t __zipos_find(const struct ZiposUri *);
+ssize_t __zipos_find(struct Zipos *, const struct ZiposUri *);
int __zipos_close(struct ZiposHandle *);
int __zipos_open(const struct ZiposUri *, unsigned, int);
int __zipos_stat(const struct ZiposUri *, struct stat *);
int __zipos_fstat(const struct ZiposHandle *, struct stat *);
-int __zipos_stat_impl(size_t, struct stat *);
+int __zipos_stat_impl(struct Zipos *, size_t, struct stat *);
ssize_t __zipos_read(struct ZiposHandle *, const struct iovec *, size_t,
ssize_t);
ssize_t __zipos_write(struct ZiposHandle *, const struct iovec *, size_t,
diff --git a/libc/zipos/zipos.mk b/libc/zipos/zipos.mk
index 496324181..63e473901 100644
--- a/libc/zipos/zipos.mk
+++ b/libc/zipos/zipos.mk
@@ -4,13 +4,17 @@
PKGS += LIBC_ZIPOS
LIBC_ZIPOS_ARTIFACTS += LIBC_ZIPOS_A
-LIBC_ZIPOS = $(LIBC_ZIPOS_A_DEPS) $(LIBC_ZIPOS_A)
LIBC_ZIPOS_A = o/$(MODE)/libc/zipos/zipos.a
LIBC_ZIPOS_A_FILES := $(wildcard libc/zipos/*)
LIBC_ZIPOS_A_HDRS = $(filter %.h,$(LIBC_ZIPOS_A_FILES))
LIBC_ZIPOS_A_SRCS_S = $(filter %.S,$(LIBC_ZIPOS_A_FILES))
LIBC_ZIPOS_A_SRCS_C = $(filter %.c,$(LIBC_ZIPOS_A_FILES))
+LIBC_ZIPOS = \
+ $(LIBC_ZIPOS_A_DEPS) \
+ $(LIBC_ZIPOS_A) \
+ o/$(MODE)/libc/zipos/zipos.o
+
LIBC_ZIPOS_A_SRCS = \
$(LIBC_ZIPOS_A_SRCS_S) \
$(LIBC_ZIPOS_A_SRCS_C)
diff --git a/test/ape/lib/test.mk b/test/ape/lib/test.mk
index bc56a3cd7..b633a5927 100644
--- a/test/ape/lib/test.mk
+++ b/test/ape/lib/test.mk
@@ -5,12 +5,14 @@ PKGS += TEST_APE_LIB
TEST_APE_LIB_SRCS := $(wildcard test/ape/lib/*.c)
TEST_APE_LIB_SRCS_TEST = $(filter %_test.c,$(TEST_APE_LIB_SRCS))
-TEST_APE_LIB_COMS = $(TEST_APE_LIB_OBJS:%.o=%.com)
TEST_APE_LIB_OBJS = \
$(TEST_APE_LIB_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_APE_LIB_SRCS:%.c=o/$(MODE)/%.o)
+TEST_APE_LIB_COMS = \
+ $(TEST_APE_LIB_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_APE_LIB_BINS = \
$(TEST_APE_LIB_COMS) \
$(TEST_APE_LIB_COMS:%=%.dbg)
diff --git a/test/dsp/core/test.mk b/test/dsp/core/test.mk
index cd4c95762..35a469cf1 100644
--- a/test/dsp/core/test.mk
+++ b/test/dsp/core/test.mk
@@ -5,13 +5,15 @@ PKGS += TEST_DSP_CORE
TEST_DSP_CORE_SRCS := $(wildcard test/dsp/core/*.c)
TEST_DSP_CORE_SRCS_TEST = $(filter %_test.c,$(TEST_DSP_CORE_SRCS))
-TEST_DSP_CORE_COMS = $(TEST_DSP_CORE_OBJS:%.o=%.com)
TEST_DSP_CORE_BINS = $(TEST_DSP_CORE_COMS) $(TEST_DSP_CORE_COMS:%=%.dbg)
TEST_DSP_CORE_OBJS = \
$(TEST_DSP_CORE_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_DSP_CORE_SRCS:%.c=o/$(MODE)/%.o)
+TEST_DSP_CORE_COMS = \
+ $(TEST_DSP_CORE_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_DSP_CORE_TESTS = \
$(TEST_DSP_CORE_SRCS_TEST:%.c=o/$(MODE)/%.com.ok)
diff --git a/test/dsp/scale/magikarp_test.c b/test/dsp/scale/magikarp_test.c
index 05b2cd432..39ca33db4 100644
--- a/test/dsp/scale/magikarp_test.c
+++ b/test/dsp/scale/magikarp_test.c
@@ -67,7 +67,7 @@ TEST(magikarp, testConvolve2) {
cDecimate2xUint8x8(256,
tgc(tunbing(u" ☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼"
u" !“#$%&‘()*+,-./0123456789:;<=>⁇"
- u"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[╲]^_"
+ u"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
u"`abcdefghijklmnopqrstuvwxyz{|}~⌂"
u"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥€ƒ"
u"áíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐"
diff --git a/test/dsp/scale/test.mk b/test/dsp/scale/test.mk
index be459fd23..966b4170a 100644
--- a/test/dsp/scale/test.mk
+++ b/test/dsp/scale/test.mk
@@ -5,13 +5,15 @@ PKGS += TEST_DSP_SCALE
TEST_DSP_SCALE_SRCS := $(wildcard test/dsp/scale/*.c)
TEST_DSP_SCALE_SRCS_TEST = $(filter %_test.c,$(TEST_DSP_SCALE_SRCS))
-TEST_DSP_SCALE_COMS = $(TEST_DSP_SCALE_OBJS:%.o=%.com)
TEST_DSP_SCALE_BINS = $(TEST_DSP_SCALE_COMS) $(TEST_DSP_SCALE_COMS:%=%.dbg)
TEST_DSP_SCALE_OBJS = \
$(TEST_DSP_SCALE_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_DSP_SCALE_SRCS:%.c=o/$(MODE)/%.o)
+TEST_DSP_SCALE_COMS = \
+ $(TEST_DSP_SCALE_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_DSP_SCALE_TESTS = \
$(TEST_DSP_SCALE_SRCS_TEST:%.c=o/$(MODE)/%.com.ok)
diff --git a/test/dsp/tty/rgb2ansi_test.c b/test/dsp/tty/rgb2ansi_test.c
index c98b63282..714805e7e 100644
--- a/test/dsp/tty/rgb2ansi_test.c
+++ b/test/dsp/tty/rgb2ansi_test.c
@@ -23,7 +23,7 @@
struct TtyRgb res;
TEST(rgb2ansi, testDesaturatedPurple_isQuantizedBetterThanEuclideanDistance) {
- ttyquantinit(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode);
+ ttyquantsetup(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode);
/*
* the challenge to the xterm256 palette is that it was likely
diff --git a/test/dsp/tty/test.mk b/test/dsp/tty/test.mk
index 4481046ff..1717b9be2 100644
--- a/test/dsp/tty/test.mk
+++ b/test/dsp/tty/test.mk
@@ -5,13 +5,15 @@ PKGS += TEST_DSP_TTY
TEST_DSP_TTY_SRCS := $(wildcard test/dsp/tty/*.c)
TEST_DSP_TTY_SRCS_TEST = $(filter %_test.c,$(TEST_DSP_TTY_SRCS))
-TEST_DSP_TTY_COMS = $(TEST_DSP_TTY_OBJS:%.o=%.com)
TEST_DSP_TTY_BINS = $(TEST_DSP_TTY_COMS) $(TEST_DSP_TTY_COMS:%=%.dbg)
TEST_DSP_TTY_OBJS = \
$(TEST_DSP_TTY_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_DSP_TTY_SRCS:%.c=o/$(MODE)/%.o)
+TEST_DSP_TTY_COMS = \
+ $(TEST_DSP_TTY_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_DSP_TTY_TESTS = \
$(TEST_DSP_TTY_SRCS_TEST:%.c=o/$(MODE)/%.com.ok)
diff --git a/test/dsp/tty/ttyraster_test.c b/test/dsp/tty/ttyraster_test.c
index 371a08781..4e19df17f 100644
--- a/test/dsp/tty/ttyraster_test.c
+++ b/test/dsp/tty/ttyraster_test.c
@@ -32,7 +32,7 @@ static const struct TtyRgb kRed = {0xff, 0, 0, 196};
char vtbuf[128];
void ttyraster_true_setup(void) {
- ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode);
+ ttyquantsetup(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode);
}
void ttyraster2x2_true(void) {
@@ -51,7 +51,7 @@ TEST(ttyraster, testCorner) {
}
TEST(ttyraster, testFullBlock_favorsSpace) {
- ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode);
+ ttyquantsetup(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode);
ttyraster(vtbuf,
(struct TtyRgb *)(unsigned[2][2]){
{DARKRED, DARKRED},
@@ -62,7 +62,7 @@ TEST(ttyraster, testFullBlock_favorsSpace) {
}
TEST(ttyraster, testFullBlock_favorsUnicodeWhenCurrenttFgMatchesButNotBg) {
- ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode);
+ ttyquantsetup(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode);
ttyraster(vtbuf,
(struct TtyRgb *)(unsigned[2][4]){
{DARKRED, GRAY1, GRAY1, GRAY1},
@@ -73,7 +73,7 @@ TEST(ttyraster, testFullBlock_favorsUnicodeWhenCurrenttFgMatchesButNotBg) {
}
TEST(ttyraster, testFullBlock_forcesSwitchBackToSpaceForRuns) {
- ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode);
+ ttyquantsetup(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode);
ttyraster(vtbuf,
(struct TtyRgb *)(unsigned[2][8]){
{DARKRED, GRAY1, GRAY1, GRAY1, GRAY1, GRAY1, GRAY1, GRAY1},
@@ -86,7 +86,7 @@ TEST(ttyraster, testFullBlock_forcesSwitchBackToSpaceForRuns) {
////////////////////////////////////////////////////////////////////////////////
TEST(ttyraster_cp437, testSide) {
- ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksCp437);
+ ttyquantsetup(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksCp437);
ttyraster(vtbuf,
(const struct TtyRgb *)(unsigned[2][2]){
{DARKRED, GRAY1},
@@ -99,7 +99,7 @@ TEST(ttyraster_cp437, testSide) {
////////////////////////////////////////////////////////////////////////////////
void ttyraster_xterm256_setup(void) {
- ttyquantinit(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode);
+ ttyquantsetup(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode);
}
void ttyraster2x2_xterm256(void) {
diff --git a/test/libc/alg/tarjan_test.c b/test/libc/alg/tarjan_test.c
index 7e252ba62..cb967dbe4 100644
--- a/test/libc/alg/tarjan_test.c
+++ b/test/libc/alg/tarjan_test.c
@@ -24,23 +24,22 @@
STATIC_YOINK("realloc");
TEST(tarjan, empty_doesNothing) {
- uint32_t sorted_vertices[1] = {-1u};
- uint32_t edges[][2] = {{0, 0}};
- uint32_t vertex_count = 0;
- uint32_t edge_count = 0;
+ int sorted_vertices[1] = {-1};
+ int edges[][2] = {{0, 0}};
+ int vertex_count = 0;
+ int edge_count = 0;
tarjan(vertex_count, (void *)edges, edge_count, sorted_vertices, NULL, NULL);
- ASSERT_EQ(-1u, sorted_vertices[0]);
+ ASSERT_EQ(-1, sorted_vertices[0]);
}
TEST(tarjan, topologicalSort_noCycles) {
enum VertexIndex { A = 0, B = 1, C = 2, D = 3 };
const char *const vertices[] = {[A] = "A", [B] = "B", [C] = "C", [D] = "D"};
- uint32_t edges[][2] = {
- {A /* depends on → */, B /* which must come before A */},
- {A /* depends on → */, C /* which must come before A */},
- {A /* depends on → */, D /* which must come before A */},
- {B /* depends on → */, C /* which must come before B */},
- {B /* depends on → */, D /* which must come before B */}};
+ int edges[][2] = {{A /* depends on → */, B /* which must come before A */},
+ {A /* depends on → */, C /* which must come before A */},
+ {A /* depends on → */, D /* which must come before A */},
+ {B /* depends on → */, C /* which must come before B */},
+ {B /* depends on → */, D /* which must come before B */}};
/*
$ tsort <nameservers.i);
- EXPECT_EQ(AF_INET, rv->nameservers.p[0].sin_family);
- EXPECT_EQ(DNS_PORT, ntohs(rv->nameservers.p[0].sin_port));
- EXPECT_STREQ("127.0.0.1", FormatIp(&rv->nameservers.p[0]));
+ ASSERT_EQ(0, parseresolvconf(rv, f));
+ ASSERT_EQ(0, rv->nameservers.i);
freeresolvconf(&rv);
fclose(f);
}
diff --git a/test/libc/dns/test.mk b/test/libc/dns/test.mk
index cf7936a15..399b83f5d 100644
--- a/test/libc/dns/test.mk
+++ b/test/libc/dns/test.mk
@@ -5,12 +5,14 @@ PKGS += TEST_LIBC_DNS
TEST_LIBC_DNS_SRCS := $(wildcard test/libc/dns/*.c)
TEST_LIBC_DNS_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_DNS_SRCS))
-TEST_LIBC_DNS_COMS = $(TEST_LIBC_DNS_OBJS:%.o=%.com)
TEST_LIBC_DNS_OBJS = \
$(TEST_LIBC_DNS_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_DNS_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_DNS_COMS = \
+ $(TEST_LIBC_DNS_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_DNS_BINS = \
$(TEST_LIBC_DNS_COMS) \
$(TEST_LIBC_DNS_COMS:%=%.dbg)
diff --git a/test/libc/fmt/palandprintf_test.c b/test/libc/fmt/palandprintf_test.c
index b051dc27d..2a39050ea 100644
--- a/test/libc/fmt/palandprintf_test.c
+++ b/test/libc/fmt/palandprintf_test.c
@@ -27,12 +27,15 @@
#include "libc/bits/progn.h"
#include "libc/bits/pushpop.h"
#include "libc/bits/safemacros.h"
+#include "libc/conv/itoa.h"
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
+#include "libc/limits.h"
#include "libc/math.h"
#include "libc/mem/mem.h"
#include "libc/runtime/gc.h"
#include "libc/str/str.h"
+#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
@@ -479,10 +482,8 @@ TEST(sprintf, test_types) {
TEST(sprintf, testOverflow_truncationNotSaturation) {
errno = 0;
EXPECT_STREQ("13398", Format("%hu", 0x123456UL));
- EXPECT_EQ(ERANGE, errno);
errno = 0;
EXPECT_STREQ("Test16 65535", Format("%s%hhi %hu", "Test", 10000, 0xFFFFFFFF));
- EXPECT_EQ(ERANGE, errno);
}
TEST(sprintf, test_pointer) {
@@ -598,6 +599,10 @@ TEST(xasprintf, test) {
free(pp);
}
+TEST(xasprintf, nullPointer) {
+ ASSERT_STREQ("000000000000", gc(xasprintf("%p", NULL)));
+}
+
TEST(xasprintf, pointer_doesntShowNonCanonicalZeroes) {
ASSERT_STREQ("100000000010", gc(xasprintf("%p", 0x0000100000000010)));
ASSERT_STREQ("0x100000000010", gc(xasprintf("%#p", 0x0000100000000010)));
@@ -610,6 +615,19 @@ TEST(xasprintf, nonCanonicalPointer_discardsHighBits_ratherThanSaturate) {
ASSERT_STREQ("0x7fffffffffff", gc(xasprintf("%#p", 0x7fffffffffff)));
}
+TEST(xasprintf, hugeNtoa) {
+ ASSERT_STREQ(
+ "0b1111111111111111111111111111111111111111111111111111111111111111111111"
+ "1111111111111111111111111111111111111111111111111111111111",
+ gc(xasprintf("%#jb", UINT128_MAX)));
+}
+
+TEST(xasprintf, twosBane) {
+ ASSERT_STREQ("-2147483648", gc(xasprintf("%d", 0x80000000)));
+ ASSERT_STREQ("-9223372036854775808",
+ gc(xasprintf("%ld", 0x8000000000000000)));
+}
+
TEST(snprintf, testFixedWidthString_wontOverrunInput) {
const int N = 2;
char *buf = tmalloc(N + 1);
@@ -651,3 +669,17 @@ TEST(snprintf, testFixedWidthStringIsNull_wontLeakMemory) {
EXPECT_BINEQ(u"NULL ", buf);
tfree(buf);
}
+
+TEST(snprintf, twosBaneWithTypePromotion) {
+ int16_t x = 0x8000;
+ EXPECT_STREQ("-32768", Format("%hd", x));
+}
+
+BENCH(palandprintf, bench) {
+ EZBENCH2("snprintf %x", donothing, Format("%x", VEIL("r", INT_MIN)));
+ EZBENCH2("snprintf %d", donothing, Format("%d", VEIL("r", INT_MIN)));
+ EZBENCH2("snprintf %s", donothing, Format("%s", VEIL("r", "hi (╯°□°)╯")));
+ EZBENCH2("snprintf %hs", donothing, Format("%hs", VEIL("r", u"hi (╯°□°)╯")));
+ EZBENCH2("snprintf %ls", donothing, Format("%ls", VEIL("r", L"hi (╯°□°)╯")));
+ EZBENCH2("int64toarray", donothing, int64toarray_radix10(-3, buffer));
+}
diff --git a/test/libc/fmt/sprintf_s.inc b/test/libc/fmt/sprintf_s.inc
index 31e17a197..f815d0e3d 100644
--- a/test/libc/fmt/sprintf_s.inc
+++ b/test/libc/fmt/sprintf_s.inc
@@ -40,10 +40,10 @@ TEST(SUITE(sprintf), testCharacterCounting) {
TEST(SUITE(snprintf), testTableFlip) {
EXPECT_STREQ("Table flip ", Format("%-20ls", L"Table flip"));
- EXPECT_STREQ("(╯°□°)╯︵ ┻━┻ ", Format("%-20ls", L"(╯°□°)╯︵ ┻━┻"));
- EXPECT_STREQ("(╯°□°)╯︵ ┻━┻ ", Format("%-20hs", u"(╯°□°)╯︵ ┻━┻"));
+ EXPECT_STREQ("(╯°□°)╯︵L┻━┻ ", Format("%-20ls", L"(╯°□°)╯︵L┻━┻"));
+ EXPECT_STREQ("(╯°□°)╯︵u┻━┻ ", Format("%-20hs", u"(╯°□°)╯︵u┻━┻"));
EXPECT_STREQ("ちゃぶ台返し ", Format("%-20ls", L"ちゃぶ台返し"));
- EXPECT_STREQ(" (╯°□°)╯︵ ┻━┻", Format("%20ls", L"(╯°□°)╯︵ ┻━┻"));
+ EXPECT_STREQ(" (╯°□°)╯︵L┻━┻", Format("%20ls", L"(╯°□°)╯︵L┻━┻"));
EXPECT_STREQ(" ちゃぶ台返し", Format("%20ls", L"ちゃぶ台返し"));
}
diff --git a/test/libc/fmt/strerror_test.c b/test/libc/fmt/strerror_test.c
index f79e05ac8..6b87c7f3c 100644
--- a/test/libc/fmt/strerror_test.c
+++ b/test/libc/fmt/strerror_test.c
@@ -30,6 +30,11 @@
* libc/sysv/kErrnoNames.S needs updating.
*/
+TEST(strerror, e2big) {
+ if (IsTiny()) return;
+ EXPECT_STARTSWITH("E2BIG", strerror(E2BIG));
+}
+
TEST(strerror, enosys) {
if (IsTiny()) return;
EXPECT_STARTSWITH("ENOSYS", strerror(ENOSYS));
diff --git a/test/libc/fmt/test.mk b/test/libc/fmt/test.mk
index 4a4220816..98c6bc946 100644
--- a/test/libc/fmt/test.mk
+++ b/test/libc/fmt/test.mk
@@ -5,7 +5,6 @@ PKGS += TEST_LIBC_FMT
TEST_LIBC_FMT_SRCS := $(wildcard test/libc/fmt/*.c)
TEST_LIBC_FMT_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_FMT_SRCS))
-TEST_LIBC_FMT_COMS = $(TEST_LIBC_FMT_OBJS:%.o=%.com)
TEST_LIBC_FMT_BINS = $(TEST_LIBC_FMT_COMS) $(TEST_LIBC_FMT_COMS:%=%.dbg)
TEST_LIBC_FMT_TESTS = $(TEST_LIBC_FMT_SRCS_TEST:%.c=o/$(MODE)/%.com.ok)
@@ -13,11 +12,15 @@ TEST_LIBC_FMT_OBJS = \
$(TEST_LIBC_FMT_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_FMT_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_FMT_COMS = \
+ $(TEST_LIBC_FMT_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_FMT_CHECKS = \
$(TEST_LIBC_FMT_SRCS_TEST:%.c=o/$(MODE)/%.com.runs)
TEST_LIBC_FMT_DIRECTDEPS = \
LIBC_CALLS_HEFTY \
+ LIBC_CONV \
LIBC_FMT \
LIBC_MEM \
LIBC_NEXGEN32E \
@@ -45,9 +48,7 @@ o/$(MODE)/test/libc/fmt/%.com.dbg: \
$(APE)
@$(APELINK)
-$(TEST_LIBC_FMT_OBJS): \
- $(BUILD_FILES) \
- test/libc/fmt/test.mk
+$(TEST_LIBC_FMT_OBJS): test/libc/fmt/test.mk
.PHONY: o/$(MODE)/test/libc/fmt
o/$(MODE)/test/libc/fmt: \
diff --git a/test/libc/intrin/intrin_test.c b/test/libc/intrin/intrin_test.c
new file mode 100644
index 000000000..b4b90b363
--- /dev/null
+++ b/test/libc/intrin/intrin_test.c
@@ -0,0 +1,2108 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/bits/progn.h"
+#include "libc/intrin/pabsb.h"
+#include "libc/intrin/pabsd.h"
+#include "libc/intrin/pabsw.h"
+#include "libc/intrin/packssdw.h"
+#include "libc/intrin/packsswb.h"
+#include "libc/intrin/packusdw.h"
+#include "libc/intrin/packuswb.h"
+#include "libc/intrin/paddb.h"
+#include "libc/intrin/paddd.h"
+#include "libc/intrin/paddq.h"
+#include "libc/intrin/paddsb.h"
+#include "libc/intrin/paddsw.h"
+#include "libc/intrin/paddusb.h"
+#include "libc/intrin/paddusw.h"
+#include "libc/intrin/paddw.h"
+#include "libc/intrin/pand.h"
+#include "libc/intrin/pandn.h"
+#include "libc/intrin/pavgb.h"
+#include "libc/intrin/pavgw.h"
+#include "libc/intrin/pcmpeqb.h"
+#include "libc/intrin/pcmpeqd.h"
+#include "libc/intrin/pcmpeqw.h"
+#include "libc/intrin/pcmpgtb.h"
+#include "libc/intrin/pcmpgtd.h"
+#include "libc/intrin/pcmpgtw.h"
+#include "libc/intrin/phaddd.h"
+#include "libc/intrin/phaddsw.h"
+#include "libc/intrin/phaddw.h"
+#include "libc/intrin/phsubd.h"
+#include "libc/intrin/phsubsw.h"
+#include "libc/intrin/phsubw.h"
+#include "libc/intrin/pmaddubsw.h"
+#include "libc/intrin/pmaddwd.h"
+#include "libc/intrin/pmaxsw.h"
+#include "libc/intrin/pmaxub.h"
+#include "libc/intrin/pminsw.h"
+#include "libc/intrin/pminub.h"
+#include "libc/intrin/pmulhrsw.h"
+#include "libc/intrin/pmulhuw.h"
+#include "libc/intrin/pmulhw.h"
+#include "libc/intrin/pmulld.h"
+#include "libc/intrin/pmullw.h"
+#include "libc/intrin/pmuludq.h"
+#include "libc/intrin/por.h"
+#include "libc/intrin/psadbw.h"
+#include "libc/intrin/pshufb.h"
+#include "libc/intrin/pshufd.h"
+#include "libc/intrin/pshufhw.h"
+#include "libc/intrin/pshuflw.h"
+#include "libc/intrin/pshufw.h"
+#include "libc/intrin/psignb.h"
+#include "libc/intrin/psignd.h"
+#include "libc/intrin/psignw.h"
+#include "libc/intrin/pslld.h"
+#include "libc/intrin/pslldq.h"
+#include "libc/intrin/psllq.h"
+#include "libc/intrin/psllw.h"
+#include "libc/intrin/psrad.h"
+#include "libc/intrin/psraw.h"
+#include "libc/intrin/psrld.h"
+#include "libc/intrin/psrldq.h"
+#include "libc/intrin/psrlq.h"
+#include "libc/intrin/psrlw.h"
+#include "libc/intrin/psubb.h"
+#include "libc/intrin/psubq.h"
+#include "libc/intrin/psubsb.h"
+#include "libc/intrin/psubsw.h"
+#include "libc/intrin/psubusb.h"
+#include "libc/intrin/psubusw.h"
+#include "libc/intrin/psubw.h"
+#include "libc/intrin/punpckhbw.h"
+#include "libc/intrin/punpckhdq.h"
+#include "libc/intrin/punpckhqdq.h"
+#include "libc/intrin/punpckhwd.h"
+#include "libc/intrin/punpcklbw.h"
+#include "libc/intrin/punpckldq.h"
+#include "libc/intrin/punpcklqdq.h"
+#include "libc/intrin/punpcklwd.h"
+#include "libc/intrin/pxor.h"
+#include "libc/intrin/shufpd.h"
+#include "libc/intrin/shufps.h"
+#include "libc/limits.h"
+#include "libc/log/check.h"
+#include "libc/nexgen32e/kcpuids.h"
+#include "libc/rand/lcg.h"
+#include "libc/rand/rand.h"
+#include "libc/runtime/gc.h"
+#include "libc/stdio/stdio.h"
+#include "libc/str/str.h"
+#include "libc/testlib/ezbench.h"
+#include "libc/testlib/testlib.h"
+#include "libc/x/x.h"
+#include "tool/viz/lib/formatstringtable-testlib.h"
+
+uint64_t g_rando = 1;
+
+forceinline uint64_t Rando(void) {
+ return KnuthLinearCongruentialGenerator(&g_rando) >> 32 << 32 |
+ KnuthLinearCongruentialGenerator(&g_rando) >> 32;
+}
+
+noinline void RngSet(void *mem, size_t size) {
+ uint64_t coin;
+ DCHECK(size % 8 == 0);
+ for (size >>= 3; size--;) {
+ coin = Rando();
+ memcpy((char *)mem + size * 8, &coin, 8);
+ }
+}
+
+FIXTURE(intrin, disableHardwareExtensions) {
+ memset((/*unconst*/ void *)kCpuids, 0, sizeof(kCpuids));
+}
+
+TEST(punpcklwd, test) {
+ uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8};
+ uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16};
+ uint16_t c[8];
+ punpcklwd(c, a, b);
+ ASSERT_EQ(1, c[0]);
+ ASSERT_EQ(9, c[1]);
+ ASSERT_EQ(2, c[2]);
+ ASSERT_EQ(10, c[3]);
+ ASSERT_EQ(3, c[4]);
+ ASSERT_EQ(11, c[5]);
+ ASSERT_EQ(4, c[6]);
+ ASSERT_EQ(12, c[7]);
+}
+
+TEST(punpcklwd, pure) {
+ uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8};
+ uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16};
+ uint16_t c[8];
+ punpcklwd(c, a, b);
+ ASSERT_EQ(1, c[0]);
+ ASSERT_EQ(9, c[1]);
+ ASSERT_EQ(2, c[2]);
+ ASSERT_EQ(10, c[3]);
+ ASSERT_EQ(3, c[4]);
+ ASSERT_EQ(11, c[5]);
+ ASSERT_EQ(4, c[6]);
+ ASSERT_EQ(12, c[7]);
+}
+
+TEST(punpcklwd, testAlias) {
+ uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8};
+ uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16};
+ punpcklwd(a, a, b);
+ ASSERT_EQ(1, a[0]);
+ ASSERT_EQ(9, a[1]);
+ ASSERT_EQ(2, a[2]);
+ ASSERT_EQ(10, a[3]);
+ ASSERT_EQ(3, a[4]);
+ ASSERT_EQ(11, a[5]);
+ ASSERT_EQ(4, a[6]);
+ ASSERT_EQ(12, a[7]);
+}
+
+TEST(punpcklwd, pureAlias) {
+ uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8};
+ uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16};
+ (punpcklwd)(a, a, b);
+ ASSERT_EQ(1, a[0]);
+ ASSERT_EQ(9, a[1]);
+ ASSERT_EQ(2, a[2]);
+ ASSERT_EQ(10, a[3]);
+ ASSERT_EQ(3, a[4]);
+ ASSERT_EQ(11, a[5]);
+ ASSERT_EQ(4, a[6]);
+ ASSERT_EQ(12, a[7]);
+}
+
+TEST(punpcklwd, testAlias2) {
+ uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8};
+ uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16};
+ punpcklwd(b, a, b);
+ ASSERT_EQ(1, b[0]);
+ ASSERT_EQ(9, b[1]);
+ ASSERT_EQ(2, b[2]);
+ ASSERT_EQ(10, b[3]);
+ ASSERT_EQ(3, b[4]);
+ ASSERT_EQ(11, b[5]);
+ ASSERT_EQ(4, b[6]);
+ ASSERT_EQ(12, b[7]);
+}
+
+TEST(punpcklwd, pureAlias2) {
+ uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8};
+ uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16};
+ (punpcklwd)(b, a, b);
+ ASSERT_EQ(1, b[0]);
+ ASSERT_EQ(9, b[1]);
+ ASSERT_EQ(2, b[2]);
+ ASSERT_EQ(10, b[3]);
+ ASSERT_EQ(3, b[4]);
+ ASSERT_EQ(11, b[5]);
+ ASSERT_EQ(4, b[6]);
+ ASSERT_EQ(12, b[7]);
+}
+
+TEST(punpcklqdq, test) {
+ uint64_t a[2] = {1, 2};
+ uint64_t b[2] = {3, 4};
+ uint64_t c[2];
+ punpcklqdq(c, a, b);
+ ASSERT_EQ(1, c[0]);
+ ASSERT_EQ(3, c[1]);
+}
+
+TEST(punpcklqdq, pure) {
+ uint64_t a[2] = {1, 2};
+ uint64_t b[2] = {3, 4};
+ uint64_t c[2];
+ (punpcklqdq)(c, a, b);
+ ASSERT_EQ(1, c[0]);
+ ASSERT_EQ(3, c[1]);
+}
+
+TEST(punpcklqdq, testAlias) {
+ uint64_t a[2] = {1, 2};
+ uint64_t b[2] = {3, 4};
+ punpcklqdq(a, a, b);
+ ASSERT_EQ(1, a[0]);
+ ASSERT_EQ(3, a[1]);
+}
+
+TEST(punpcklqdq, pureAlias) {
+ uint64_t a[2] = {1, 2};
+ uint64_t b[2] = {3, 4};
+ (punpcklqdq)(a, a, b);
+ ASSERT_EQ(1, a[0]);
+ ASSERT_EQ(3, a[1]);
+}
+
+TEST(punpckldq, test) {
+ uint32_t a[4] = {1, 2, 3, 4};
+ uint32_t b[4] = {5, 6, 7, 8};
+ uint32_t c[4];
+ punpckldq(c, a, b);
+ ASSERT_EQ(1, c[0]);
+ ASSERT_EQ(5, c[1]);
+ ASSERT_EQ(2, c[2]);
+ ASSERT_EQ(6, c[3]);
+}
+
+TEST(punpckldq, pure) {
+ uint32_t a[4] = {1, 2, 3, 4};
+ uint32_t b[4] = {5, 6, 7, 8};
+ uint32_t c[4];
+ punpckldq(c, a, b);
+ ASSERT_EQ(1, c[0]);
+ ASSERT_EQ(5, c[1]);
+ ASSERT_EQ(2, c[2]);
+ ASSERT_EQ(6, c[3]);
+}
+
+TEST(punpckldq, testAlias) {
+ uint32_t a[4] = {1, 2, 3, 4};
+ uint32_t b[4] = {5, 6, 7, 8};
+ punpckldq(a, a, b);
+ ASSERT_EQ(1, a[0]);
+ ASSERT_EQ(5, a[1]);
+ ASSERT_EQ(2, a[2]);
+ ASSERT_EQ(6, a[3]);
+}
+
+TEST(punpckldq, pureAlias) {
+ uint32_t a[4] = {1, 2, 3, 4};
+ uint32_t b[4] = {5, 6, 7, 8};
+ (punpckldq)(a, a, b);
+ ASSERT_EQ(1, a[0]);
+ ASSERT_EQ(5, a[1]);
+ ASSERT_EQ(2, a[2]);
+ ASSERT_EQ(6, a[3]);
+}
+
+TEST(punpckldq, testAlias2) {
+ uint32_t a[4] = {1, 2, 3, 4};
+ uint32_t b[4] = {5, 6, 7, 8};
+ punpckldq(b, a, b);
+ ASSERT_EQ(1, b[0]);
+ ASSERT_EQ(5, b[1]);
+ ASSERT_EQ(2, b[2]);
+ ASSERT_EQ(6, b[3]);
+}
+
+TEST(punpckldq, pureAlias2) {
+ uint32_t a[4] = {1, 2, 3, 4};
+ uint32_t b[4] = {5, 6, 7, 8};
+ (punpckldq)(b, a, b);
+ ASSERT_EQ(1, b[0]);
+ ASSERT_EQ(5, b[1]);
+ ASSERT_EQ(2, b[2]);
+ ASSERT_EQ(6, b[3]);
+}
+
+TEST(punpcklqdq, fuzz) {
+ int i, j;
+ uint64_t x[2], y[2], a[2], b[2];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ punpcklqdq(a, x, y);
+ (punpcklqdq)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(punpckldq, fuzz) {
+ int i, j;
+ uint32_t x[4], y[4], a[4], b[4];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ punpckldq(a, x, y);
+ (punpckldq)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(punpcklbw, fuzz) {
+ int i, j;
+ uint8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ punpcklbw(a, x, y);
+ (punpcklbw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(punpckhwd, test) {
+ uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8};
+ uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16};
+ uint16_t c[8];
+ punpckhwd(c, a, b);
+ ASSERT_EQ(5, c[0]);
+ ASSERT_EQ(13, c[1]);
+ ASSERT_EQ(6, c[2]);
+ ASSERT_EQ(14, c[3]);
+ ASSERT_EQ(7, c[4]);
+ ASSERT_EQ(15, c[5]);
+ ASSERT_EQ(8, c[6]);
+ ASSERT_EQ(16, c[7]);
+}
+
+TEST(punpckhwd, pure) {
+ uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8};
+ uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16};
+ uint16_t c[8];
+ punpckhwd(c, a, b);
+ ASSERT_EQ(5, c[0]);
+ ASSERT_EQ(13, c[1]);
+ ASSERT_EQ(6, c[2]);
+ ASSERT_EQ(14, c[3]);
+ ASSERT_EQ(7, c[4]);
+ ASSERT_EQ(15, c[5]);
+ ASSERT_EQ(8, c[6]);
+ ASSERT_EQ(16, c[7]);
+}
+
+TEST(punpckhwd, testAlias) {
+ uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8};
+ uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16};
+ punpckhwd(a, a, b);
+ ASSERT_EQ(5, a[0]);
+ ASSERT_EQ(13, a[1]);
+ ASSERT_EQ(6, a[2]);
+ ASSERT_EQ(14, a[3]);
+ ASSERT_EQ(7, a[4]);
+ ASSERT_EQ(15, a[5]);
+ ASSERT_EQ(8, a[6]);
+ ASSERT_EQ(16, a[7]);
+}
+
+TEST(punpckhwd, pureAlias) {
+ uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8};
+ uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16};
+ (punpckhwd)(a, a, b);
+ ASSERT_EQ(5, a[0]);
+ ASSERT_EQ(13, a[1]);
+ ASSERT_EQ(6, a[2]);
+ ASSERT_EQ(14, a[3]);
+ ASSERT_EQ(7, a[4]);
+ ASSERT_EQ(15, a[5]);
+ ASSERT_EQ(8, a[6]);
+ ASSERT_EQ(16, a[7]);
+}
+
+TEST(punpckhwd, testAlias2) {
+ uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8};
+ uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16};
+ punpckhwd(b, a, b);
+ ASSERT_EQ(5, b[0]);
+ ASSERT_EQ(13, b[1]);
+ ASSERT_EQ(6, b[2]);
+ ASSERT_EQ(14, b[3]);
+ ASSERT_EQ(7, b[4]);
+ ASSERT_EQ(15, b[5]);
+ ASSERT_EQ(8, b[6]);
+ ASSERT_EQ(16, b[7]);
+}
+
+TEST(punpckhwd, pureAlias2) {
+ uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8};
+ uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16};
+ (punpckhwd)(b, a, b);
+ ASSERT_EQ(5, b[0]);
+ ASSERT_EQ(13, b[1]);
+ ASSERT_EQ(6, b[2]);
+ ASSERT_EQ(14, b[3]);
+ ASSERT_EQ(7, b[4]);
+ ASSERT_EQ(15, b[5]);
+ ASSERT_EQ(8, b[6]);
+ ASSERT_EQ(16, b[7]);
+}
+
+TEST(punpckhqdq, test) {
+ uint64_t a[2] = {1, 2};
+ uint64_t b[2] = {3, 4};
+ uint64_t c[2];
+ punpckhqdq(c, a, b);
+ ASSERT_EQ(2, c[0]);
+ ASSERT_EQ(4, c[1]);
+}
+
+TEST(punpckhqdq, pure) {
+ uint64_t a[2] = {1, 2};
+ uint64_t b[2] = {3, 4};
+ uint64_t c[2];
+ (punpckhqdq)(c, a, b);
+ ASSERT_EQ(2, c[0]);
+ ASSERT_EQ(4, c[1]);
+}
+
+TEST(punpckhqdq, testAlias) {
+ uint64_t a[2] = {1, 2};
+ uint64_t b[2] = {3, 4};
+ punpckhqdq(a, a, b);
+ ASSERT_EQ(2, a[0]);
+ ASSERT_EQ(4, a[1]);
+}
+
+TEST(punpckhqdq, pureAlias) {
+ uint64_t a[2] = {1, 2};
+ uint64_t b[2] = {3, 4};
+ (punpckhqdq)(a, a, b);
+ ASSERT_EQ(2, a[0]);
+ ASSERT_EQ(4, a[1]);
+}
+
+TEST(punpckhdq, test) {
+ uint32_t a[4] = {1, 2, 3, 4};
+ uint32_t b[4] = {5, 6, 7, 8};
+ uint32_t c[4];
+ punpckhdq(c, a, b);
+ ASSERT_EQ(3, c[0]);
+ ASSERT_EQ(7, c[1]);
+ ASSERT_EQ(4, c[2]);
+ ASSERT_EQ(8, c[3]);
+}
+
+TEST(punpckhdq, pure) {
+ uint32_t a[4] = {1, 2, 3, 4};
+ uint32_t b[4] = {5, 6, 7, 8};
+ uint32_t c[4];
+ punpckhdq(c, a, b);
+ ASSERT_EQ(3, c[0]);
+ ASSERT_EQ(7, c[1]);
+ ASSERT_EQ(4, c[2]);
+ ASSERT_EQ(8, c[3]);
+}
+
+TEST(punpckhdq, testAlias) {
+ uint32_t a[4] = {1, 2, 3, 4};
+ uint32_t b[4] = {5, 6, 7, 8};
+ punpckhdq(a, a, b);
+ ASSERT_EQ(3, a[0]);
+ ASSERT_EQ(7, a[1]);
+ ASSERT_EQ(4, a[2]);
+ ASSERT_EQ(8, a[3]);
+}
+
+TEST(punpckhdq, pureAlias) {
+ uint32_t a[4] = {1, 2, 3, 4};
+ uint32_t b[4] = {5, 6, 7, 8};
+ (punpckhdq)(a, a, b);
+ ASSERT_EQ(3, a[0]);
+ ASSERT_EQ(7, a[1]);
+ ASSERT_EQ(4, a[2]);
+ ASSERT_EQ(8, a[3]);
+}
+
+TEST(punpckhdq, testAlias2) {
+ uint32_t a[4] = {1, 2, 3, 4};
+ uint32_t b[4] = {5, 6, 7, 8};
+ punpckhdq(b, a, b);
+ ASSERT_EQ(3, b[0]);
+ ASSERT_EQ(7, b[1]);
+ ASSERT_EQ(4, b[2]);
+ ASSERT_EQ(8, b[3]);
+}
+
+TEST(punpckhdq, pureAlias2) {
+ uint32_t a[4] = {1, 2, 3, 4};
+ uint32_t b[4] = {5, 6, 7, 8};
+ (punpckhdq)(b, a, b);
+ ASSERT_EQ(3, b[0]);
+ ASSERT_EQ(7, b[1]);
+ ASSERT_EQ(4, b[2]);
+ ASSERT_EQ(8, b[3]);
+}
+
+TEST(punpckhwd, fuzz) {
+ int i, j;
+ uint16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ punpckhwd(a, x, y);
+ (punpckhwd)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(punpckhqdq, fuzz) {
+ int i, j;
+ uint64_t x[2], y[2], a[2], b[2];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ punpckhqdq(a, x, y);
+ (punpckhqdq)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(punpckhdq, fuzz) {
+ int i, j;
+ uint32_t x[4], y[4], a[4], b[4];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ punpckhdq(a, x, y);
+ (punpckhdq)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(punpckhbw, fuzz) {
+ int i, j;
+ uint8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ punpckhbw(a, x, y);
+ (punpckhbw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psubq, fuzz) {
+ int i, j;
+ int64_t x[2], y[2], a[2], b[2];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ psubq(a, x, y);
+ (psubq)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psrawv, testSmallShift) {
+ int16_t A[8] = {-1, -2, SHRT_MIN, 2};
+ uint64_t B[2] = {1};
+ psrawv(A, A, B);
+ ASSERT_EQ(-1, A[0]);
+ ASSERT_EQ(-1, A[1]);
+ ASSERT_EQ(-16384, A[2]);
+ ASSERT_EQ(1, A[3]);
+ ASSERT_EQ(0, A[4]);
+}
+
+TEST(psraw, testSmallShift) {
+ int16_t A[8] = {-1, -2, SHRT_MIN, 2};
+ psraw(A, A, 1);
+ ASSERT_EQ(-1, A[0]);
+ ASSERT_EQ(-1, A[1]);
+ ASSERT_EQ(-16384, A[2]);
+ ASSERT_EQ(1, A[3]);
+ ASSERT_EQ(0, A[4]);
+}
+
+TEST(psraw, pureSmallShift) {
+ int16_t A[8] = {-1, -2, SHRT_MIN, 2};
+ (psraw)(A, A, 1);
+ ASSERT_EQ(-1, A[0]);
+ ASSERT_EQ(-1, A[1]);
+ ASSERT_EQ(-16384, A[2]);
+ ASSERT_EQ(1, A[3]);
+ ASSERT_EQ(0, A[4]);
+}
+
+TEST(psraw, testBigShift_saturatesCount) {
+ int16_t A[8] = {-1, -2, SHRT_MIN, 2};
+ psraw(A, A, 77);
+ ASSERT_EQ(-1, A[0]);
+ ASSERT_EQ(-1, A[1]);
+ ASSERT_EQ(-1, A[2]);
+ ASSERT_EQ(0, A[3]);
+ ASSERT_EQ(0, A[4]);
+}
+
+TEST(psraw, pureBigShift_saturatesCount) {
+ int16_t A[8] = {-1, -2, SHRT_MIN, 2};
+ (psraw)(A, A, 77);
+ ASSERT_EQ(-1, A[0]);
+ ASSERT_EQ(-1, A[1]);
+ ASSERT_EQ(-1, A[2]);
+ ASSERT_EQ(0, A[3]);
+ ASSERT_EQ(0, A[4]);
+}
+
+TEST(psradv, testSmallShift) {
+ int32_t A[8] = {-1, -2, INT32_MIN, 2};
+ uint64_t B[2] = {1};
+ psradv(A, A, B);
+ ASSERT_EQ(-1, A[0]);
+ ASSERT_EQ(-1, A[1]);
+ ASSERT_EQ(-1073741824, A[2]);
+ ASSERT_EQ(1, A[3]);
+ ASSERT_EQ(0, A[4]);
+}
+
+TEST(psradv, test) {
+ int i, j;
+ int32_t x[4], a[4], b[4];
+ uint64_t y[2];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ for (j = 0; j < 2; ++j) {
+ y[j] = Rando() % 64;
+ }
+ psradv(a, x, y);
+ (psradv)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psrad, testSmallShift) {
+ int32_t A[4] = {-1, -2, INT32_MIN, 2};
+ psrad(A, A, 1);
+ ASSERT_EQ(-1, A[0]);
+ ASSERT_EQ(-1, A[1]);
+ ASSERT_EQ(-1073741824, A[2]);
+ ASSERT_EQ(1, A[3]);
+}
+
+TEST(psrad, pureSmallShift) {
+ int32_t A[4] = {-1, -2, INT32_MIN, 2};
+ (psrad)(A, A, 1);
+ ASSERT_EQ(-1, A[0]);
+ ASSERT_EQ(-1, A[1]);
+ ASSERT_EQ(-1073741824, A[2]);
+ ASSERT_EQ(1, A[3]);
+}
+
+TEST(psrad, testBigShift_saturatesCount) {
+ int32_t A[4] = {-1, -2, INT32_MIN, 2};
+ psrad(A, A, 77);
+ ASSERT_EQ(-1, A[0]);
+ ASSERT_EQ(-1, A[1]);
+ ASSERT_EQ(-1, A[2]);
+ ASSERT_EQ(0, A[3]);
+}
+
+TEST(psrad, pureBigShift_saturatesCount) {
+ int32_t A[4] = {-1, -2, INT32_MIN, 2};
+ (psrad)(A, A, 77);
+ ASSERT_EQ(-1, A[0]);
+ ASSERT_EQ(-1, A[1]);
+ ASSERT_EQ(-1, A[2]);
+ ASSERT_EQ(0, A[3]);
+}
+
+TEST(psllwv, test) {
+ int i, j;
+ uint16_t x[8], a[8], b[8];
+ uint64_t y[2];
+ for (i = 0; i < 32; ++i) {
+ RngSet(x, sizeof(x));
+ for (j = 0; j < 2; ++j) {
+ y[j] = Rando() % 300;
+ }
+ psllwv(a, x, y);
+ (psllwv)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psllw, testSmallShift) {
+ uint16_t A[8] = {0, 1, 0xffff, 2};
+ psllw(A, A, 1);
+ ASSERT_EQ(0, A[0]);
+ ASSERT_EQ(2, A[1]);
+ ASSERT_EQ(0xfffe, A[2]);
+ ASSERT_EQ(4, A[3]);
+}
+
+TEST(psllwv, testSmallShift) {
+ uint16_t A[8] = {0, 1, 0xffff, 2};
+ uint64_t B[2] = {1};
+ psllwv(A, A, B);
+ ASSERT_EQ(0, A[0]);
+ ASSERT_EQ(2, A[1]);
+ ASSERT_EQ(0xfffe, A[2]);
+ ASSERT_EQ(4, A[3]);
+}
+
+TEST(pslldv, test) {
+ int i, j;
+ uint32_t x[4], a[4], b[4];
+ uint64_t y[2];
+ for (i = 0; i < 32; ++i) {
+ RngSet(x, sizeof(x));
+ for (j = 0; j < 2; ++j) {
+ y[j] = Rando() % 300;
+ }
+ pslldv(a, x, y);
+ (pslldv)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pslld, testSmallShift) {
+ uint32_t A[8] = {0, 1, 0xffffffff, 2};
+ pslld(A, A, 1);
+ ASSERT_EQ(0, A[0]);
+ ASSERT_EQ(2, A[1]);
+ ASSERT_EQ(0xfffffffe, A[2]);
+ ASSERT_EQ(4, A[3]);
+}
+
+TEST(pslldv, testSmallShift) {
+ uint32_t A[8] = {0, 1, 0xffffffff, 2};
+ uint64_t B[2] = {1};
+ pslldv(A, A, B);
+ ASSERT_EQ(0, A[0]);
+ ASSERT_EQ(2, A[1]);
+ ASSERT_EQ(0xfffffffe, A[2]);
+ ASSERT_EQ(4, A[3]);
+}
+
+TEST(pmulhuw, test) {
+ uint16_t x[8] = {0, 0xffff, 0x0000, 0x0001, 0x8000};
+ uint16_t y[8] = {0, 0xffff, 0xffff, 0xffff, 0x8000};
+ uint16_t z[8];
+ pmulhuw(z, x, y);
+ ASSERT_EQ(0x0000 /*0000*/, z[0]);
+ ASSERT_EQ(0xfffe /*0001*/, z[1]);
+ ASSERT_EQ(0x0000 /*0000*/, z[2]);
+ ASSERT_EQ(0x0000 /*ffff*/, z[3]);
+ ASSERT_EQ(0x4000 /*0000*/, z[4]);
+}
+
+TEST(pmulhuw, pure) {
+ uint16_t x[8] = {0, 0xffff, 0x0000, 0x0001, 0x8000};
+ uint16_t y[8] = {0, 0xffff, 0xffff, 0xffff, 0x8000};
+ uint16_t z[8];
+ (pmulhuw)(z, x, y);
+ ASSERT_EQ(0x0000 /*0000*/, z[0]);
+ ASSERT_EQ(0xfffe /*0001*/, z[1]);
+ ASSERT_EQ(0x0000 /*0000*/, z[2]);
+ ASSERT_EQ(0x0000 /*ffff*/, z[3]);
+ ASSERT_EQ(0x4000 /*0000*/, z[4]);
+}
+
+TEST(pmulhuw, fuzz) {
+ int i, j;
+ uint16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pmulhuw(a, x, y);
+ (pmulhuw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pmulhuw(a, (void *)a, y);
+ (pmulhuw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pmulhw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 1000; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pmulhw(a, x, y);
+ (pmulhw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pmulhw(a, (void *)a, y);
+ (pmulhw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pmullw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 1000; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pmullw(a, x, y);
+ (pmullw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pmullw(a, (void *)a, y);
+ (pmullw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pmulld, fuzz) {
+ int i, j;
+ int32_t x[4], y[4], a[4], b[4];
+ for (i = 0; i < 1000; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pmulld(a, x, y);
+ (pmulld)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pmulld(a, (void *)a, y);
+ (pmulld)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pmuludq, fuzz) {
+ int i, j;
+ uint32_t x[4], y[4];
+ uint64_t a[2], b[2];
+ for (i = 0; i < 1000; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pmuludq(a, x, y);
+ (pmuludq)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pmuludq(a, (void *)a, y);
+ (pmuludq)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pmaddwd, fuzz) {
+ int i, j;
+ int16_t x[8], y[8];
+ int32_t a[4], b[4];
+ for (i = 0; i < 1000; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pmaddwd(a, x, y);
+ (pmaddwd)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pmaddwd(a, (void *)a, y);
+ (pmaddwd)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(phaddw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8];
+ int16_t a[8], b[8];
+ for (i = 0; i < 1000; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ phaddw(a, x, y);
+ (phaddw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ phaddw(a, (void *)a, y);
+ (phaddw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(phaddd, fuzz) {
+ int i, j;
+ int32_t x[4], y[4];
+ int32_t a[4], b[4];
+ for (i = 0; i < 1000; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ phaddd(a, x, y);
+ (phaddd)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ phaddd(a, (void *)a, y);
+ (phaddd)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(phsubw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8];
+ int16_t a[8], b[8];
+ for (i = 0; i < 1000; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ phsubw(a, x, y);
+ (phsubw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ phsubw(a, (void *)a, y);
+ (phsubw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(phsubd, fuzz) {
+ int i, j;
+ int32_t x[4], y[4];
+ int32_t a[4], b[4];
+ for (i = 0; i < 1000; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ phsubd(a, x, y);
+ (phsubd)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ phsubd(a, (void *)a, y);
+ (phsubd)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(phaddsw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8];
+ int16_t a[8], b[8];
+ for (i = 0; i < 1000; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ phaddsw(a, x, y);
+ (phaddsw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ phaddsw(a, (void *)a, y);
+ (phaddsw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(phsubsw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8];
+ int16_t a[8], b[8];
+ for (i = 0; i < 1000; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ phsubsw(a, x, y);
+ (phsubsw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ phsubsw(a, (void *)a, y);
+ (phsubsw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(phaddw, testOverflow_wrapsAround) {
+ short M[2][8] = {
+ {0x7fff, 0, 0x7fff, 1, 13004, -30425, 20777, -16389},
+ {-28040, 13318, -1336, -24798, -13876, 3599, -7346, -23575},
+ };
+ phaddw(M[0], M[0], M[1]);
+ EXPECT_SHRTMATRIXEQ(2, 8, M, "\n\
+ 32767 -32768 -17421 4388 -14722 -26134 -10277 -30921\n\
+-28040 13318 -1336 -24798 -13876 3599 -7346 -23575");
+}
+
+TEST(phaddw, testAliasing_isOk) {
+ short M[1][8] = {
+ {0, 1, 2, 3, 4, 5, 6, 7},
+ };
+ phaddw(M[0], M[0], M[0]);
+ EXPECT_SHRTMATRIXEQ(1, 8, M, "\n\
+ 1 5 9 13 1 5 9 13");
+}
+
+TEST(phaddsw, testOverflow_saturates) {
+ short M[2][8] = {
+ {0x7fff, 0, 0x7fff, 1, 0x7fff, 0x7fff, 20777, -16389},
+ {-28040, 13318, -1336, -24798, -13876, 3599, -7346, -23575},
+ };
+ phaddsw(M[0], M[0], M[1]);
+ EXPECT_SHRTMATRIXEQ(2, 8, M, "\n\
+ 32767 32767 32767 4388 -14722 -26134 -10277 -30921\n\
+-28040 13318 -1336 -24798 -13876 3599 -7346 -23575");
+}
+
+TEST(phaddsw, testAliasing_isOk) {
+ short M[1][8] = {{0, 1, 2, 3, 4, 5, 6, 7}};
+ phaddsw(M[0], M[0], M[0]);
+ EXPECT_SHRTMATRIXEQ(1, 8, M, "\n\
+ 1 5 9 13 1 5 9 13");
+}
+
+TEST(pcmpgtb, test) {
+ int i, j;
+ int8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ static int count;
+ pcmpgtb(a, x, y);
+ (pcmpgtb)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pcmpeqb, test) {
+ int i, j;
+ uint8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pcmpeqb(a, x, y);
+ (pcmpeqb)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pcmpeqd, test) {
+ int i, j;
+ int32_t x[4], y[4], a[4], b[4];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pcmpeqd(a, x, y);
+ (pcmpeqd)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pcmpgtd, test) {
+ int i, j;
+ int32_t x[4], y[4], a[4], b[4];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pcmpgtd(a, x, y);
+ (pcmpgtd)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pcmpeqw, test) {
+ int i, j;
+ int16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pcmpeqw(a, x, y);
+ (pcmpeqw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pcmpgtw, test) {
+ int i, j;
+ int16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pcmpgtw(a, x, y);
+ (pcmpgtw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(por, fuzz) {
+ int i, j;
+ uint64_t x[2], y[2], a[2], b[2];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 2; ++j) x[j] = Rando();
+ for (j = 0; j < 2; ++j) y[j] = Rando();
+ por(a, x, y);
+ (por)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ por(a, (void *)a, y);
+ (por)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pxor, fuzz) {
+ int i, j;
+ uint64_t x[2], y[2], a[2], b[2];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 2; ++j) x[j] = Rando();
+ for (j = 0; j < 2; ++j) y[j] = Rando();
+ pxor(a, x, y);
+ (pxor)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pxor(a, (void *)a, y);
+ (pxor)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pand, fuzz) {
+ int i, j;
+ uint64_t x[2], y[2], a[2], b[2];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 2; ++j) x[j] = Rando();
+ for (j = 0; j < 2; ++j) y[j] = Rando();
+ pand(a, x, y);
+ (pand)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pand(a, (void *)a, y);
+ (pand)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pandn, fuzz) {
+ int i, j;
+ uint64_t x[2], y[2], a[2], b[2];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 2; ++j) x[j] = Rando();
+ for (j = 0; j < 2; ++j) y[j] = Rando();
+ pandn(a, x, y);
+ (pandn)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pandn(a, (void *)a, y);
+ (pandn)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(paddq, fuzz) {
+ int i, j;
+ int64_t x[2], y[2], a[2], b[2];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 2; ++j) x[j] = Rando();
+ for (j = 0; j < 2; ++j) y[j] = Rando();
+ paddq(a, x, y);
+ (paddq)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ paddq(a, (void *)a, y);
+ (paddq)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pavgb, fuzz) {
+ int i, j;
+ uint8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pavgb(a, x, y);
+ (pavgb)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pavgb(a, (void *)a, y);
+ (pavgb)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pavgw, fuzz) {
+ int i, j;
+ uint16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pavgw(a, x, y);
+ (pavgw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pavgw(a, (void *)a, y);
+ (pavgw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(punpcklwd, fuzz) {
+ int i, j;
+ uint16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ punpcklwd(a, x, y);
+ (punpcklwd)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ punpcklwd(a, a, y);
+ (punpcklwd)(b, b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ punpcklwd(a, y, a);
+ (punpcklwd)(b, y, b);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pminub, fuzz) {
+ int i, j;
+ uint8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pminub(a, x, y);
+ (pminub)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pminub(a, (void *)a, y);
+ (pminub)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pminsw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pminsw(a, x, y);
+ (pminsw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pminsw(a, (void *)a, y);
+ (pminsw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pmaxub, fuzz) {
+ int i, j;
+ uint8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pmaxub(a, x, y);
+ (pmaxub)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pmaxub(a, (void *)a, y);
+ (pmaxub)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pmaxsw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pmaxsw(a, x, y);
+ (pmaxsw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pmaxsw(a, (void *)a, y);
+ (pmaxsw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(paddw, test) {
+ int16_t A[8] = {7};
+ int16_t B[8] = {11};
+ int16_t C[8];
+ paddw(C, A, B);
+ ASSERT_EQ(18, C[0]);
+}
+
+TEST(paddw, testOverflow_wrapsAround) {
+ int16_t A[8] = {SHRT_MAX, SHRT_MIN};
+ int16_t B[8] = {1, -1};
+ paddw(A, A, B);
+ ASSERT_EQ(SHRT_MIN, A[0]);
+ ASSERT_EQ(SHRT_MAX, A[1]);
+}
+
+TEST(paddw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ paddw(a, x, y);
+ (paddw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ paddw(a, (void *)a, y);
+ (paddw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(paddsw, test) {
+ int16_t A[8] = {7};
+ int16_t B[8] = {11};
+ int16_t C[8];
+ paddsw(C, A, B);
+ ASSERT_EQ(18, C[0]);
+}
+
+TEST(paddsw, testOverflow_saturates) {
+ int16_t A[8] = {SHRT_MAX, SHRT_MIN};
+ int16_t B[8] = {1, -1};
+ paddsw(A, A, B);
+ ASSERT_EQ(SHRT_MAX, A[0]);
+ ASSERT_EQ(SHRT_MIN, A[1]);
+}
+
+TEST(paddusw, fuzz) {
+ int i, j;
+ uint16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ paddusw(a, x, y);
+ (paddusw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ paddusw(a, (void *)a, y);
+ (paddusw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psubb, fuzz) {
+ int i, j;
+ int8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ psubb(a, x, y);
+ (psubb)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psubb(a, (void *)a, y);
+ (psubb)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psubw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ psubw(a, x, y);
+ (psubw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psubw(a, (void *)a, y);
+ (psubw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psubusw, fuzz) {
+ int i, j;
+ uint16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ psubusw(a, x, y);
+ (psubusw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psubusw(a, (void *)a, y);
+ (psubusw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(paddusb, fuzz) {
+ int i, j;
+ uint8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ paddusb(a, x, y);
+ (paddusb)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ paddusb(a, (void *)a, y);
+ (paddusb)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psubusb, fuzz) {
+ int i, j;
+ uint8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ psubusb(a, x, y);
+ (psubusb)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psubusb(a, (void *)a, y);
+ (psubusb)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pabsb, fuzz) {
+ int i, j;
+ int8_t x[16];
+ uint8_t a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ pabsb(a, x);
+ (pabsb)(b, x);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pabsw, fuzz) {
+ int i, j;
+ int16_t x[8];
+ uint16_t a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ pabsw(a, x);
+ (pabsw)(b, x);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pabsd, fuzz) {
+ int i, j;
+ int32_t x[4];
+ uint32_t a[4], b[4];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ pabsd(a, x);
+ (pabsd)(b, x);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psignb, fuzz) {
+ int i, j;
+ int8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ psignb(a, x, y);
+ (psignb)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psignb(a, (void *)a, y);
+ (psignb)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psignw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ psignw(a, x, y);
+ (psignw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psignw(a, (void *)a, y);
+ (psignw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psignd, fuzz) {
+ int i, j;
+ int32_t x[4], y[4], a[4], b[4];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ psignd(a, x, y);
+ (psignd)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psignd(a, (void *)a, y);
+ (psignd)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(paddb, fuzz) {
+ int i, j;
+ int8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ paddb(a, x, y);
+ (paddb)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ paddb(a, (void *)a, y);
+ (paddb)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(paddsb, fuzz) {
+ int i, j;
+ int8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ paddsb(a, x, y);
+ (paddsb)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ paddsb(a, (void *)a, y);
+ (paddsb)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(paddsw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ paddsw(a, x, y);
+ (paddsw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ paddsw(a, (void *)a, y);
+ (paddsw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psubsb, fuzz) {
+ int i, j;
+ int8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ psubsb(a, x, y);
+ (psubsb)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psubsb(a, (void *)a, y);
+ (psubsb)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psubsw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ psubsw(a, x, y);
+ (psubsw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psubsw(a, (void *)a, y);
+ (psubsw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(paddd, fuzz) {
+ int i, j;
+ int32_t x[4], y[4], a[4], b[4];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ paddd(a, x, y);
+ (paddd)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ paddd(a, (void *)a, y);
+ (paddd)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pshufb, fuzz) {
+ int i, j;
+ uint8_t x[16], y[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pshufb(a, x, y);
+ (pshufb)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pshufb(a, (void *)a, y);
+ (pshufb)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pshufd, fuzz) {
+ int i, j;
+ int32_t x[4], a[4], b[4];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 4; ++j) x[j] = Rando();
+#define T(IMM) \
+ pshufd(a, x, IMM); \
+ (pshufd)(b, x, IMM); \
+ ASSERT_EQ(0, memcmp(a, b, 16)); \
+ pshufd(a, (void *)a, IMM); \
+ (pshufd)(b, (void *)b, IMM); \
+ ASSERT_EQ(0, memcmp(a, b, 16))
+ T(0b00000011);
+ T(0b00000110);
+ T(0b00001100);
+ T(0b00011000);
+ T(0b00110000);
+ T(0b01100000);
+ T(0b11000000);
+ T(0b10000000);
+#undef T
+ }
+}
+
+TEST(pshuflw, fuzz) {
+ int i, j;
+ int16_t x[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 8; ++j) x[j] = Rando();
+#define T(IMM) \
+ pshuflw(a, x, IMM); \
+ (pshuflw)(b, x, IMM); \
+ ASSERT_EQ(0, memcmp(a, b, 16)); \
+ pshuflw(a, (void *)a, IMM); \
+ (pshuflw)(b, (void *)b, IMM); \
+ ASSERT_EQ(0, memcmp(a, b, 16))
+ T(0b00000011);
+ T(0b00000110);
+ T(0b00001100);
+ T(0b00011000);
+ T(0b00110000);
+ T(0b01100000);
+ T(0b11000000);
+ T(0b10000000);
+#undef T
+ }
+}
+
+TEST(pshufhw, fuzz) {
+ int i, j;
+ int16_t x[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 8; ++j) x[j] = Rando();
+#define T(IMM) \
+ pshufhw(a, x, IMM); \
+ (pshufhw)(b, x, IMM); \
+ ASSERT_EQ(0, memcmp(a, b, 16)); \
+ pshufhw(a, (void *)a, IMM); \
+ (pshufhw)(b, (void *)b, IMM); \
+ ASSERT_EQ(0, memcmp(a, b, 16))
+ T(0b00000011);
+ T(0b00000110);
+ T(0b00001100);
+ T(0b00011000);
+ T(0b00110000);
+ T(0b01100000);
+ T(0b11000000);
+ T(0b10000000);
+#undef T
+ }
+}
+
+TEST(shufps, fuzz) {
+ int i, j;
+ float x[4], a[4], b[4];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 4; ++j) x[j] = Rando() % INT_MAX;
+#define T(IMM) \
+ shufps(a, x, IMM); \
+ (shufps)(b, x, IMM); \
+ ASSERT_EQ(0, memcmp(a, b, 16)); \
+ shufps(a, (void *)a, IMM); \
+ (shufps)(b, (void *)b, IMM); \
+ ASSERT_EQ(0, memcmp(a, b, 16))
+ T(0b00000011);
+ T(0b00000110);
+ T(0b00001100);
+ T(0b00011000);
+ T(0b00110000);
+ T(0b01100000);
+ T(0b11000000);
+ T(0b10000000);
+#undef T
+ }
+}
+
+TEST(shufpd, fuzz) {
+ int i, j;
+ double x[2], a[2], b[2];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 2; ++j) x[j] = Rando() % INT_MAX;
+#define T(IMM) \
+ shufpd(a, x, IMM); \
+ (shufpd)(b, x, IMM); \
+ ASSERT_EQ(0, memcmp(a, b, 16)); \
+ shufpd(a, (void *)a, IMM); \
+ (shufpd)(b, (void *)b, IMM); \
+ ASSERT_EQ(0, memcmp(a, b, 16))
+ T(0b00000000);
+ T(0b00000001);
+ T(0b00000010);
+ T(0b00000011);
+#undef T
+ }
+}
+
+TEST(packuswb, test) {
+ const short S[8] = {0, 128, -128, 255, SHRT_MAX, SHRT_MIN, 0, 0};
+ unsigned char B[16] = {0};
+ packuswb(B, S, S);
+ ASSERT_EQ(0, B[0]);
+ ASSERT_EQ(128, B[1]);
+ ASSERT_EQ(0, B[2]);
+ ASSERT_EQ(255, B[3]);
+ ASSERT_EQ(255, B[4]);
+ ASSERT_EQ(0, B[5]);
+ ASSERT_EQ(0, B[6]);
+ ASSERT_EQ(0, B[7]);
+ ASSERT_EQ(0, B[8]);
+ ASSERT_EQ(128, B[9]);
+ ASSERT_EQ(0, B[10]);
+ ASSERT_EQ(255, B[11]);
+ ASSERT_EQ(255, B[12]);
+ ASSERT_EQ(0, B[13]);
+ ASSERT_EQ(0, B[14]);
+ ASSERT_EQ(0, B[15]);
+}
+
+TEST(packsswb, test) {
+ const short S[8] = {0, 128, -128, 255, SHRT_MAX, SHRT_MIN, 0, 0};
+ signed char B[16] = {0};
+ packsswb(B, S, S);
+ ASSERT_EQ(0, B[0]);
+ ASSERT_EQ(127, B[1]);
+ ASSERT_EQ(-128, B[2]);
+ ASSERT_EQ(127, B[3]);
+ ASSERT_EQ(127, B[4]);
+ ASSERT_EQ(-128, B[5]);
+ ASSERT_EQ(0, B[6]);
+ ASSERT_EQ(0, B[7]);
+ ASSERT_EQ(0, B[8]);
+ ASSERT_EQ(127, B[9]);
+ ASSERT_EQ(-128, B[10]);
+ ASSERT_EQ(127, B[11]);
+ ASSERT_EQ(127, B[12]);
+ ASSERT_EQ(-128, B[13]);
+ ASSERT_EQ(0, B[14]);
+ ASSERT_EQ(0, B[15]);
+}
+
+TEST(packssdw, testAlias) {
+ int i, j;
+ union {
+ int16_t out[8];
+ int32_t in1[4];
+ } u;
+ int16_t a[8], b[8];
+ int32_t x[4], y[4];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ memcpy(u.in1, x, sizeof(x));
+ packssdw(u.out, u.in1, y);
+ memcpy(a, u.out, sizeof(u.out));
+ memcpy(u.in1, x, sizeof(x));
+ (packssdw)(u.out, u.in1, y);
+ memcpy(b, u.out, sizeof(u.out));
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(packusdw, test) {
+ int i, j;
+ int32_t x[4], y[4];
+ uint16_t a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ packusdw(a, x, y);
+ (packusdw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(packuswb, fuzz) {
+ int i, j;
+ int16_t x[8], y[8];
+ uint8_t a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ packuswb(a, x, y);
+ (packuswb)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ packuswb(a, x, x);
+ (packuswb)(b, x, x);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(packssdw, test) {
+ int i, j;
+ int32_t x[4], y[4];
+ int16_t a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ packssdw(a, x, y);
+ (packssdw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psllwv, fuzz) {
+ int i, j;
+ uint64_t y[2];
+ uint16_t x[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 8; ++j) x[j] = Rando();
+ for (j = 0; j < 2; ++j) y[j] = Rando() % 64;
+ psllwv(a, x, y);
+ (psllwv)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psllwv(a, (void *)a, y);
+ (psllwv)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pslldv, fuzz) {
+ int i, j;
+ uint64_t y[2];
+ uint32_t x[4], a[4], b[4];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 4; ++j) x[j] = Rando();
+ for (j = 0; j < 2; ++j) y[j] = Rando() % 64;
+ pslldv(a, x, y);
+ (pslldv)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pslldv(a, (void *)a, y);
+ (pslldv)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psllqv, fuzz) {
+ int i, j;
+ uint64_t y[2];
+ uint64_t x[2], a[2], b[2];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 2; ++j) x[j] = Rando();
+ for (j = 0; j < 2; ++j) y[j] = Rando() % 64;
+ psllqv(a, x, y);
+ (psllqv)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psllqv(a, (void *)a, y);
+ (psllqv)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psrlwv, fuzz) {
+ int i, j;
+ uint64_t y[2];
+ uint16_t x[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 8; ++j) x[j] = Rando();
+ for (j = 0; j < 2; ++j) y[j] = Rando() % 64;
+ psrlwv(a, x, y);
+ (psrlwv)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psrlwv(a, (void *)a, y);
+ (psrlwv)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psrldv, fuzz) {
+ int i, j;
+ uint64_t y[2];
+ uint32_t x[4], a[4], b[4];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 4; ++j) x[j] = Rando();
+ for (j = 0; j < 2; ++j) y[j] = Rando() % 64;
+ psrldv(a, x, y);
+ (psrldv)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psrldv(a, (void *)a, y);
+ (psrldv)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psrlqv, fuzz) {
+ int i, j;
+ uint64_t y[2];
+ uint64_t x[2], a[2], b[2];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 2; ++j) x[j] = Rando();
+ for (j = 0; j < 2; ++j) y[j] = Rando() % 64;
+ psrlqv(a, x, y);
+ (psrlqv)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psrlqv(a, (void *)a, y);
+ (psrlqv)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psrawv, fuzz) {
+ int i, j;
+ uint64_t y[2];
+ int16_t x[8], a[8], b[8];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 8; ++j) x[j] = Rando();
+ for (j = 0; j < 2; ++j) y[j] = Rando() % 64;
+ psrawv(a, x, y);
+ (psrawv)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psrawv(a, (void *)a, y);
+ (psrawv)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psradv, fuzz) {
+ int i, j;
+ uint64_t y[2];
+ int32_t x[4], a[4], b[4];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 4; ++j) x[j] = Rando();
+ for (j = 0; j < 2; ++j) y[j] = Rando() % 64;
+ psradv(a, x, y);
+ (psradv)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ psradv(a, (void *)a, y);
+ (psradv)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(psrldq, fuzz) {
+ int i, n;
+ uint8_t x[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ memset(a, -1, sizeof(a));
+ memset(b, -1, sizeof(b));
+ RngSet(x, sizeof(x));
+ n = Rando() % 20;
+ psrldq(a, x, n);
+ (psrldq)(b, x, n);
+ ASSERT_EQ(0, memcmp(a, b, 16), "%d\n\t%`#.16s\n\t%`#.16s\n\t%`#.16s", n, x,
+ a, b);
+ n = Rando() % 20;
+ psrldq(a, a, n);
+ (psrldq)(b, b, n);
+ ASSERT_EQ(0, memcmp(a, b, 16), "%d\n\t%`#.16s\n\t%`#.16s\n\t%`#.16s", n, x,
+ a, b);
+ }
+}
+
+TEST(pslldq, fuzz) {
+ int i, n;
+ uint8_t x[16], a[16], b[16];
+ for (i = 0; i < 100; ++i) {
+ memset(a, -1, sizeof(a));
+ memset(b, -1, sizeof(b));
+ RngSet(x, sizeof(x));
+ n = Rando() % 20;
+ pslldq(a, x, n);
+ (pslldq)(b, x, n);
+ ASSERT_EQ(0, memcmp(a, b, 16), "%d\n\t%`#.16s\n\t%`#.16s\n\t%`#.16s", n, x,
+ a, b);
+ n = Rando() % 20;
+ pslldq(a, a, n);
+ (pslldq)(b, b, n);
+ ASSERT_EQ(0, memcmp(a, b, 16), "%d\n\t%`#.16s\n\t%`#.16s\n\t%`#.16s", n, x,
+ a, b);
+ }
+}
+
+TEST(psadbw, test) {
+ int i, j;
+ uint64_t a[2], b[2];
+ uint8_t x[16], y[16];
+ for (i = 0; i < 100; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ psadbw(a, x, y);
+ (psadbw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pmulhrsw, fuzz) {
+ int i, j;
+ int16_t x[8], y[8], a[8], b[8];
+ for (i = 0; i < 1000; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pmulhrsw(a, x, y);
+ (pmulhrsw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pmulhrsw(a, (void *)a, y);
+ (pmulhrsw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pmaddubsw, fuzz) {
+ int i, j;
+ int8_t y[16];
+ uint8_t x[16];
+ int16_t a[8], b[8];
+ for (i = 0; i < 1000; ++i) {
+ RngSet(x, sizeof(x));
+ RngSet(y, sizeof(y));
+ pmaddubsw(a, x, y);
+ (pmaddubsw)(b, x, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ pmaddubsw(a, (void *)a, y);
+ (pmaddubsw)(b, (void *)b, y);
+ ASSERT_EQ(0, memcmp(a, b, 16));
+ }
+}
+
+TEST(pshufw, fuzz) {
+ int i, j;
+ uint8_t y;
+ int16_t x[4], a[4], b[4];
+ for (i = 0; i < 100; ++i) {
+ for (j = 0; j < 4; ++j) x[j] = Rando();
+ pshufw(a, x, 0b10111111);
+ (pshufw)(b, x, 0b10111111);
+ ASSERT_EQ(0, memcmp(a, b, 8));
+ pshufw(a, (void *)a, 0b10111111);
+ (pshufw)(b, (void *)b, 0b10111111);
+ ASSERT_EQ(0, memcmp(a, b, 8));
+ pshufw(a, x, 0b00001000);
+ (pshufw)(b, x, 0b00001000);
+ ASSERT_EQ(0, memcmp(a, b, 8));
+ pshufw(a, x, 0b00010001);
+ (pshufw)(b, x, 0b00010001);
+ ASSERT_EQ(0, memcmp(a, b, 8));
+ pshufw(a, x, 0b01110100);
+ (pshufw)(b, x, 0b01110100);
+ ASSERT_EQ(0, memcmp(a, b, 8));
+ pshufw(a, x, 0b01101101);
+ (pshufw)(b, x, 0b01101101);
+ ASSERT_EQ(0, memcmp(a, b, 8));
+ pshufw(a, x, 0b10011011);
+ (pshufw)(b, x, 0b10011011);
+ ASSERT_EQ(0, memcmp(a, b, 8));
+ pshufw(a, x, 0b10111000);
+ (pshufw)(b, x, 0b10111000);
+ ASSERT_EQ(0, memcmp(a, b, 8));
+ pshufw(a, x, 0b11000111);
+ (pshufw)(b, x, 0b11000111);
+ ASSERT_EQ(0, memcmp(a, b, 8));
+ }
+}
+
+TEST(pcmpeqw, test2) {
+ int16_t kNumbers16[] = {0, 1, 2, 123, 0xffff, 0xfffe, 0x8000, 0x8001, 0x8080};
+ int i, j, k;
+ int16_t a[8], b[8], x[8], y[8];
+ for (i = 0; i < ARRAYLEN(kNumbers16); ++i) {
+ for (j = 0; j < ARRAYLEN(kNumbers16); ++j) {
+ for (k = 0; k < 8; ++k) {
+ x[k] = kNumbers16[(i + k) % ARRAYLEN(kNumbers16)];
+ y[k] = kNumbers16[(j + k) % ARRAYLEN(kNumbers16)];
+ }
+ pcmpeqw(a, x, y);
+ (pcmpeqw)(b, x, y);
+ EXPECT_EQ(0, memcmp(a, b, 16));
+ }
+ }
+}
+
+BENCH(psrldq, bench) {
+ volatile uint8_t A[16];
+ volatile uint8_t B[16];
+ EZBENCH2("psrldq const 𝑖", donothing, PROGN(psrldq(A, B, 7)));
+ EZBENCH2("psrldq var 𝑖", donothing, PROGN(psrldq(A, B, VEIL("r", 7))));
+ EZBENCH2("psrldq ansi", donothing, PROGN((psrldq)(A, B, 7)));
+}
+
+BENCH(pslldq, bench) {
+ volatile uint8_t A[16];
+ volatile uint8_t B[16];
+ EZBENCH2("pslldq const 𝑖", donothing, PROGN(pslldq(A, B, 7)));
+ EZBENCH2("pslldq var 𝑖", donothing, PROGN(pslldq(A, B, VEIL("r", 7))));
+ EZBENCH2("pslldq ansi", donothing, PROGN((pslldq)(A, B, 7)));
+}
diff --git a/test/libc/intrin/palignr_test.c b/test/libc/intrin/palignr_test.c
index 7d2b92499..7374a76bb 100644
--- a/test/libc/intrin/palignr_test.c
+++ b/test/libc/intrin/palignr_test.c
@@ -17,71 +17,216 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/bits/progn.h"
#include "libc/intrin/palignr.h"
+#include "libc/rand/rand.h"
+#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
-const int A[4] = {1, 2, 3, 4};
-const int B[4] = {5, 6, 7, 8};
-
TEST(palignr, testLeftpad) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
int C[4] = {0};
palignr(C, B, A, 12);
- EXPECT_EQ(4, C[0]);
- EXPECT_EQ(5, C[1]);
- EXPECT_EQ(6, C[2]);
- EXPECT_EQ(7, C[3]);
+ ASSERT_EQ(4, C[0]);
+ ASSERT_EQ(5, C[1]);
+ ASSERT_EQ(6, C[2]);
+ ASSERT_EQ(7, C[3]);
+}
+
+TEST(palignr, testLeftpad_variableImmediate) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
+ int C[4] = {0};
+ palignr(C, B, A, VEIL("r", 12));
+ ASSERT_EQ(4, C[0]);
+ ASSERT_EQ(5, C[1]);
+ ASSERT_EQ(6, C[2]);
+ ASSERT_EQ(7, C[3]);
}
TEST(palignr, test0) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
int C[4];
palignr(C, B, A, 0);
- EXPECT_EQ(1, C[0]);
- EXPECT_EQ(2, C[1]);
- EXPECT_EQ(3, C[2]);
- EXPECT_EQ(4, C[3]);
+ ASSERT_EQ(1, C[0]);
+ ASSERT_EQ(2, C[1]);
+ ASSERT_EQ(3, C[2]);
+ ASSERT_EQ(4, C[3]);
}
TEST(palignr, test4) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
int C[4];
palignr(C, B, A, 4);
- EXPECT_EQ(2, C[0]);
- EXPECT_EQ(3, C[1]);
- EXPECT_EQ(4, C[2]);
- EXPECT_EQ(5, C[3]);
+ ASSERT_EQ(2, C[0]);
+ ASSERT_EQ(3, C[1]);
+ ASSERT_EQ(4, C[2]);
+ ASSERT_EQ(5, C[3]);
}
TEST(palignr, test12) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
int C[4];
palignr(C, B, A, 12);
- EXPECT_EQ(4, C[0]);
- EXPECT_EQ(5, C[1]);
- EXPECT_EQ(6, C[2]);
- EXPECT_EQ(7, C[3]);
+ ASSERT_EQ(4, C[0]);
+ ASSERT_EQ(5, C[1]);
+ ASSERT_EQ(6, C[2]);
+ ASSERT_EQ(7, C[3]);
}
TEST(palignr, test16) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
int C[4];
palignr(C, B, A, 16);
- EXPECT_EQ(5, C[0]);
- EXPECT_EQ(6, C[1]);
- EXPECT_EQ(7, C[2]);
- EXPECT_EQ(8, C[3]);
+ ASSERT_EQ(5, C[0]);
+ ASSERT_EQ(6, C[1]);
+ ASSERT_EQ(7, C[2]);
+ ASSERT_EQ(8, C[3]);
}
TEST(palignr, test20) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
int C[4] = {-1, -1, -1, -1};
palignr(C, B, A, 20);
- EXPECT_EQ(6, C[0]);
- EXPECT_EQ(7, C[1]);
- EXPECT_EQ(8, C[2]);
- EXPECT_EQ(0, C[3]);
+ ASSERT_EQ(6, C[0]);
+ ASSERT_EQ(7, C[1]);
+ ASSERT_EQ(8, C[2]);
+ ASSERT_EQ(0, C[3]);
}
-TEST(palignr, test32) {
+TEST(palignrc, testLeftpad) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
+ int C[4] = {0};
+ (palignr)(C, B, A, 12);
+ ASSERT_EQ(4, C[0]);
+ ASSERT_EQ(5, C[1]);
+ ASSERT_EQ(6, C[2]);
+ ASSERT_EQ(7, C[3]);
+}
+
+TEST(palignrc, testLeftpad_variableImmediate) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
+ int C[4] = {0};
+ (palignr)(C, B, A, VEIL("r", 12));
+ ASSERT_EQ(4, C[0]);
+ ASSERT_EQ(5, C[1]);
+ ASSERT_EQ(6, C[2]);
+ ASSERT_EQ(7, C[3]);
+}
+
+TEST(palignrc, test0) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
+ int C[4];
+ (palignr)(C, B, A, 0);
+ ASSERT_EQ(1, C[0]);
+ ASSERT_EQ(2, C[1]);
+ ASSERT_EQ(3, C[2]);
+ ASSERT_EQ(4, C[3]);
+}
+
+TEST(palignrc, test4) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
+ int C[4];
+ (palignr)(C, B, A, 4);
+ ASSERT_EQ(2, C[0]);
+ ASSERT_EQ(3, C[1]);
+ ASSERT_EQ(4, C[2]);
+ ASSERT_EQ(5, C[3]);
+}
+
+TEST(palignrc, test12) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
+ int C[4];
+ (palignr)(C, B, A, 12);
+ ASSERT_EQ(4, C[0]);
+ ASSERT_EQ(5, C[1]);
+ ASSERT_EQ(6, C[2]);
+ ASSERT_EQ(7, C[3]);
+}
+
+TEST(palignrc, test16) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
+ int C[4];
+ (palignr)(C, B, A, 16);
+ ASSERT_EQ(5, C[0]);
+ ASSERT_EQ(6, C[1]);
+ ASSERT_EQ(7, C[2]);
+ ASSERT_EQ(8, C[3]);
+}
+
+TEST(palignrc, test20) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
+ int C[4] = {-1, -1, -1, -1};
+ (palignr)(C, B, A, 20);
+ ASSERT_EQ(6, C[0]);
+ ASSERT_EQ(7, C[1]);
+ ASSERT_EQ(8, C[2]);
+ ASSERT_EQ(0, C[3]);
+}
+
+TEST(palignr, test32orHigher_clearsOutput) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
int C[4] = {-1, -1, -1, -1};
palignr(C, B, A, 32);
- EXPECT_EQ(0, C[0]);
- EXPECT_EQ(0, C[1]);
- EXPECT_EQ(0, C[2]);
- EXPECT_EQ(0, C[3]);
+ ASSERT_EQ(0, C[0]);
+ ASSERT_EQ(0, C[1]);
+ ASSERT_EQ(0, C[2]);
+ ASSERT_EQ(0, C[3]);
+ C[0] = 43;
+ palignr(C, B, A, 123);
+ ASSERT_EQ(0, C[0]);
+ ASSERT_EQ(0, C[1]);
+ ASSERT_EQ(0, C[2]);
+ ASSERT_EQ(0, C[3]);
+}
+
+TEST(palignrv, test32orHigher_clearsOutput) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
+ int C[4] = {-1, -1, -1, -1};
+ palignr(C, B, A, VEIL("r", 32));
+ ASSERT_EQ(0, C[0]);
+ ASSERT_EQ(0, C[1]);
+ ASSERT_EQ(0, C[2]);
+ ASSERT_EQ(0, C[3]);
+ C[0] = 43;
+ palignr(C, B, A, VEIL("r", 123));
+ ASSERT_EQ(0, C[0]);
+ ASSERT_EQ(0, C[1]);
+ ASSERT_EQ(0, C[2]);
+ ASSERT_EQ(0, C[3]);
+}
+
+TEST(palignrc, test32orHigher_clearsOutput) {
+ const int A[4] = {1, 2, 3, 4};
+ const int B[4] = {5, 6, 7, 8};
+ int C[4] = {-1, -1, -1, -1};
+ (palignr)(C, B, A, 32);
+ ASSERT_EQ(0, C[0]);
+ ASSERT_EQ(0, C[1]);
+ ASSERT_EQ(0, C[2]);
+ ASSERT_EQ(0, C[3]);
+}
+
+BENCH(palignr, bench) {
+ volatile __intrin_xmm_t A;
+ volatile __intrin_xmm_t B;
+ EZBENCH2("palignr const 𝑖", donothing, PROGN(palignr(&A, &A, &B, 7)));
+ EZBENCH2("palignr var 𝑖", donothing,
+ PROGN(palignr(&A, &A, &B, VEIL("r", 7))));
+ EZBENCH2("palignr ansi", donothing, PROGN((palignr)(&A, &A, &B, 7)));
}
diff --git a/test/libc/intrin/pshuf_test.c b/test/libc/intrin/pshuf_test.c
new file mode 100644
index 000000000..e1e361b11
--- /dev/null
+++ b/test/libc/intrin/pshuf_test.c
@@ -0,0 +1,162 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/intrin/pshufd.h"
+#include "libc/intrin/pshufhw.h"
+#include "libc/intrin/pshuflw.h"
+#include "libc/rand/rand.h"
+#include "libc/str/str.h"
+#include "libc/testlib/testlib.h"
+
+#define T(f, m) \
+ f(a, x, m); \
+ (f)(b, x, m); \
+ EXPECT_EQ(0, memcmp(a, b, 16))
+
+TEST(pshuflw, test) {
+ int i, j;
+ int16_t x[8], a[8], b[8];
+ for (i = 0; i < 10; ++i) {
+ for (j = 0; j < 8; ++j) x[j] = rand();
+ T(pshuflw, 0b00000000);
+ T(pshuflw, 0b00000001);
+ T(pshuflw, 0b00000011);
+ T(pshuflw, 0b00001100);
+ T(pshuflw, 0b00001101);
+ T(pshuflw, 0b00001111);
+ T(pshuflw, 0b00110000);
+ T(pshuflw, 0b00110001);
+ T(pshuflw, 0b00110011);
+ T(pshuflw, 0b00111100);
+ T(pshuflw, 0b00111101);
+ T(pshuflw, 0b00111111);
+ T(pshuflw, 0b01000000);
+ T(pshuflw, 0b01000001);
+ T(pshuflw, 0b01000011);
+ T(pshuflw, 0b01001100);
+ T(pshuflw, 0b01001101);
+ T(pshuflw, 0b01001111);
+ T(pshuflw, 0b01110000);
+ T(pshuflw, 0b01110001);
+ T(pshuflw, 0b01110011);
+ T(pshuflw, 0b01111100);
+ T(pshuflw, 0b01111101);
+ T(pshuflw, 0b01111111);
+ T(pshuflw, 0b11000000);
+ T(pshuflw, 0b11000001);
+ T(pshuflw, 0b11000011);
+ T(pshuflw, 0b11001100);
+ T(pshuflw, 0b11001101);
+ T(pshuflw, 0b11001111);
+ T(pshuflw, 0b11110000);
+ T(pshuflw, 0b11110001);
+ T(pshuflw, 0b11110011);
+ T(pshuflw, 0b11111100);
+ T(pshuflw, 0b11111101);
+ T(pshuflw, 0b11111111);
+ }
+}
+
+TEST(pshufhw, test) {
+ int i, j;
+ int16_t x[8], a[8], b[8];
+ for (i = 0; i < 10; ++i) {
+ for (j = 0; j < 8; ++j) x[j] = rand();
+ T(pshufhw, 0b00000000);
+ T(pshufhw, 0b00000001);
+ T(pshufhw, 0b00000011);
+ T(pshufhw, 0b00001100);
+ T(pshufhw, 0b00001101);
+ T(pshufhw, 0b00001111);
+ T(pshufhw, 0b00110000);
+ T(pshufhw, 0b00110001);
+ T(pshufhw, 0b00110011);
+ T(pshufhw, 0b00111100);
+ T(pshufhw, 0b00111101);
+ T(pshufhw, 0b00111111);
+ T(pshufhw, 0b01000000);
+ T(pshufhw, 0b01000001);
+ T(pshufhw, 0b01000011);
+ T(pshufhw, 0b01001100);
+ T(pshufhw, 0b01001101);
+ T(pshufhw, 0b01001111);
+ T(pshufhw, 0b01110000);
+ T(pshufhw, 0b01110001);
+ T(pshufhw, 0b01110011);
+ T(pshufhw, 0b01111100);
+ T(pshufhw, 0b01111101);
+ T(pshufhw, 0b01111111);
+ T(pshufhw, 0b11000000);
+ T(pshufhw, 0b11000001);
+ T(pshufhw, 0b11000011);
+ T(pshufhw, 0b11001100);
+ T(pshufhw, 0b11001101);
+ T(pshufhw, 0b11001111);
+ T(pshufhw, 0b11110000);
+ T(pshufhw, 0b11110001);
+ T(pshufhw, 0b11110011);
+ T(pshufhw, 0b11111100);
+ T(pshufhw, 0b11111101);
+ T(pshufhw, 0b11111111);
+ }
+}
+
+TEST(pshufd, test) {
+ int i, j;
+ int32_t x[4], a[4], b[4];
+ for (i = 0; i < 10; ++i) {
+ for (j = 0; j < 4; ++j) x[j] = rand();
+ T(pshufd, 0b00000000);
+ T(pshufd, 0b00000001);
+ T(pshufd, 0b00000011);
+ T(pshufd, 0b00001100);
+ T(pshufd, 0b00001101);
+ T(pshufd, 0b00001111);
+ T(pshufd, 0b00110000);
+ T(pshufd, 0b00110001);
+ T(pshufd, 0b00110011);
+ T(pshufd, 0b00111100);
+ T(pshufd, 0b00111101);
+ T(pshufd, 0b00111111);
+ T(pshufd, 0b01000000);
+ T(pshufd, 0b01000001);
+ T(pshufd, 0b01000011);
+ T(pshufd, 0b01001100);
+ T(pshufd, 0b01001101);
+ T(pshufd, 0b01001111);
+ T(pshufd, 0b01110000);
+ T(pshufd, 0b01110001);
+ T(pshufd, 0b01110011);
+ T(pshufd, 0b01111100);
+ T(pshufd, 0b01111101);
+ T(pshufd, 0b01111111);
+ T(pshufd, 0b11000000);
+ T(pshufd, 0b11000001);
+ T(pshufd, 0b11000011);
+ T(pshufd, 0b11001100);
+ T(pshufd, 0b11001101);
+ T(pshufd, 0b11001111);
+ T(pshufd, 0b11110000);
+ T(pshufd, 0b11110001);
+ T(pshufd, 0b11110011);
+ T(pshufd, 0b11111100);
+ T(pshufd, 0b11111101);
+ T(pshufd, 0b11111111);
+ }
+}
diff --git a/test/libc/intrin/test.mk b/test/libc/intrin/test.mk
index 3a714a8ce..aad716040 100644
--- a/test/libc/intrin/test.mk
+++ b/test/libc/intrin/test.mk
@@ -5,12 +5,14 @@ PKGS += TEST_LIBC_INTRIN
TEST_LIBC_INTRIN_SRCS := $(wildcard test/libc/intrin/*.c)
TEST_LIBC_INTRIN_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_INTRIN_SRCS))
-TEST_LIBC_INTRIN_COMS = $(TEST_LIBC_INTRIN_OBJS:%.o=%.com)
TEST_LIBC_INTRIN_OBJS = \
$(TEST_LIBC_INTRIN_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_INTRIN_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_INTRIN_COMS = \
+ $(TEST_LIBC_INTRIN_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_INTRIN_BINS = \
$(TEST_LIBC_INTRIN_COMS) \
$(TEST_LIBC_INTRIN_COMS:%=%.dbg)
@@ -22,12 +24,17 @@ TEST_LIBC_INTRIN_CHECKS = \
$(TEST_LIBC_INTRIN_SRCS_TEST:%.c=o/$(MODE)/%.com.runs)
TEST_LIBC_INTRIN_DIRECTDEPS = \
+ LIBC_FMT \
LIBC_INTRIN \
LIBC_NEXGEN32E \
+ LIBC_RAND \
+ LIBC_LOG \
LIBC_STUBS \
+ LIBC_TESTLIB \
LIBC_TINYMATH \
- TOOL_VIZ_LIB \
- LIBC_TESTLIB
+ LIBC_RUNTIME \
+ LIBC_X \
+ TOOL_VIZ_LIB
TEST_LIBC_INTRIN_DEPS := \
$(call uniq,$(foreach x,$(TEST_LIBC_INTRIN_DIRECTDEPS),$($(x))))
diff --git a/test/libc/math/ilogb_test.c b/test/libc/math/ilogb_test.c
new file mode 100644
index 000000000..20f2a6872
--- /dev/null
+++ b/test/libc/math/ilogb_test.c
@@ -0,0 +1,29 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/math.h"
+#include "libc/testlib/testlib.h"
+
+TEST(ilogb, test) {
+ EXPECT_EQ(0x7fffffff, ilogb(INFINITY));
+ EXPECT_EQ(0, ilogb(1));
+ EXPECT_EQ(1, ilogb(2));
+ EXPECT_EQ(2, ilogb(4));
+ EXPECT_EQ(63, ilogb(1e19));
+}
diff --git a/test/libc/math/test.mk b/test/libc/math/test.mk
index e40458ed9..56ee4e0d2 100644
--- a/test/libc/math/test.mk
+++ b/test/libc/math/test.mk
@@ -5,12 +5,14 @@ PKGS += TEST_LIBC_MATH
TEST_LIBC_MATH_SRCS := $(wildcard test/libc/math/*.c)
TEST_LIBC_MATH_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_MATH_SRCS))
-TEST_LIBC_MATH_COMS = $(TEST_LIBC_MATH_OBJS:%.o=%.com)
TEST_LIBC_MATH_OBJS = \
$(TEST_LIBC_MATH_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_MATH_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_MATH_COMS = \
+ $(TEST_LIBC_MATH_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_MATH_BINS = \
$(TEST_LIBC_MATH_COMS) \
$(TEST_LIBC_MATH_COMS:%=%.dbg)
@@ -23,7 +25,7 @@ TEST_LIBC_MATH_CHECKS = \
TEST_LIBC_MATH_DIRECTDEPS = \
LIBC_CALLS_HEFTY \
LIBC_FMT \
- LIBC_TINYMATH \
+ LIBC_MATH \
LIBC_MEM \
LIBC_RUNTIME \
LIBC_STUBS \
diff --git a/test/libc/mem/malloc_test.c b/test/libc/mem/malloc_test.c
index 6d2af58aa..b0e65e8bf 100644
--- a/test/libc/mem/malloc_test.c
+++ b/test/libc/mem/malloc_test.c
@@ -18,7 +18,59 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/bits.h"
+#include "libc/bits/safemacros.h"
+#include "libc/calls/calls.h"
+#include "libc/calls/struct/stat.h"
+#include "libc/macros.h"
#include "libc/mem/mem.h"
+#include "libc/rand/rand.h"
+#include "libc/runtime/memtrack.h"
+#include "libc/runtime/runtime.h"
+#include "libc/stdio/stdio.h"
+#include "libc/str/str.h"
+#include "libc/sysv/consts/map.h"
+#include "libc/sysv/consts/o.h"
+#include "libc/sysv/consts/prot.h"
#include "libc/testlib/testlib.h"
-TEST(malloc, test) { free(malloc(123)); }
+#define N 1024
+#define M 20
+
+TEST(malloc, test) {
+ static struct stat st;
+ static volatile int i, j, k, *A[4096], fds[M], *maps[M], mapsizes[M];
+ memset(fds, -1, sizeof(fds));
+ memset(maps, -1, sizeof(maps));
+ for (i = 0; i < N * M; ++i) {
+ j = rand() % ARRAYLEN(A);
+ if (A[j]) {
+ ASSERT_EQ(j, A[j][0]);
+ A[j] = realloc(A[j], max(sizeof(int), rand() % N));
+ ASSERT_NE(NULL, A[j]);
+ ASSERT_EQ(j, A[j][0]);
+ free(A[j]);
+ A[j] = NULL;
+ } else {
+ A[j] = malloc(max(sizeof(int), rand() % N));
+ ASSERT_NE(NULL, A[j]);
+ A[j][0] = j;
+ }
+ if (i % M == 0) {
+ k = rand() % M;
+ if (fds[k] == -1) {
+ ASSERT_NE(-1, (fds[k] = open(program_invocation_name, O_RDONLY)));
+ ASSERT_NE(-1, fstat(fds[k], &st));
+ ASSERT_NE(MAP_FAILED,
+ (maps[k] = mmap(NULL, (mapsizes[k] = st.st_size), PROT_READ,
+ MAP_SHARED, fds[k], 0)));
+ } else {
+ ASSERT_NE(-1, munmap(maps[k], mapsizes[k]));
+ ASSERT_NE(-1, close(fds[k]));
+ fds[k] = -1;
+ }
+ }
+ }
+ for (i = 0; i < ARRAYLEN(A); ++i) free(A[i]);
+ for (i = 0; i < ARRAYLEN(maps); ++i) munmap(maps[i], mapsizes[i]);
+ for (i = 0; i < ARRAYLEN(fds); ++i) close(fds[i]);
+}
diff --git a/test/libc/mem/test.mk b/test/libc/mem/test.mk
index 56dfe494a..342c1c22f 100644
--- a/test/libc/mem/test.mk
+++ b/test/libc/mem/test.mk
@@ -5,12 +5,14 @@ PKGS += TEST_LIBC_MEM
TEST_LIBC_MEM_SRCS := $(wildcard test/libc/mem/*.c)
TEST_LIBC_MEM_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_MEM_SRCS))
-TEST_LIBC_MEM_COMS = $(TEST_LIBC_MEM_OBJS:%.o=%.com)
TEST_LIBC_MEM_OBJS = \
$(TEST_LIBC_MEM_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_MEM_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_MEM_COMS = \
+ $(TEST_LIBC_MEM_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_MEM_BINS = \
$(TEST_LIBC_MEM_COMS) \
$(TEST_LIBC_MEM_COMS:%=%.dbg)
@@ -22,7 +24,14 @@ TEST_LIBC_MEM_CHECKS = \
TEST_LIBC_MEM_DIRECTDEPS = \
LIBC_MEM \
+ LIBC_CALLS \
LIBC_STUBS \
+ LIBC_NEXGEN32E \
+ LIBC_SYSV \
+ LIBC_FMT \
+ LIBC_RUNTIME \
+ LIBC_STDIO \
+ LIBC_RAND \
LIBC_TESTLIB
TEST_LIBC_MEM_DEPS := \
diff --git a/test/libc/nexgen32e/crc32_test.c b/test/libc/nexgen32e/crc32_test.c
index d4a9e1865..44374106a 100644
--- a/test/libc/nexgen32e/crc32_test.c
+++ b/test/libc/nexgen32e/crc32_test.c
@@ -18,25 +18,40 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/macros.h"
+#include "libc/nexgen32e/crc32.h"
#include "libc/nexgen32e/x86feature.h"
+#include "libc/str/knuthmultiplicativehash.h"
#include "libc/str/str.h"
+#include "libc/testlib/ezbench.h"
#include "libc/testlib/hyperion.h"
#include "libc/testlib/testlib.h"
-
-uint32_t crc32(uint32_t, const void *, int);
-uint32_t crc32$pclmul(uint32_t, const void *, size_t);
-uint32_t crc32$pclmul2(uint32_t, const void *, size_t);
+#include "third_party/zlib/zlib.h"
TEST(crc32, testBigText) {
size_t size;
+ void *hyperion;
size = kHyperionSize;
- EXPECT_EQ(0xe9ded8e6, crc32(0, kHyperion, size));
- EXPECT_EQ(0xe9ded8e6, crc32_z(0, kHyperion, size));
+ hyperion = kHyperion;
+ EXPECT_EQ(0xe9ded8e6, crc32(0, hyperion, size));
+ EXPECT_EQ(0xe9ded8e6, crc32_z(0, hyperion, size));
if (X86_HAVE(PCLMUL)) {
size = ROUNDDOWN(size, 64);
- EXPECT_EQ(0xc7adc04f, crc32(0, kHyperion, size));
- EXPECT_EQ(0xc7adc04f, crc32_z(0, kHyperion, size));
+ EXPECT_EQ(0xc7adc04f, crc32(0, hyperion, size));
+ EXPECT_EQ(0xc7adc04f, crc32_z(0, hyperion, size));
EXPECT_EQ(0xc7adc04f,
- 0xffffffffu ^ crc32$pclmul(0 ^ 0xffffffffu, kHyperion, size));
+ 0xffffffffu ^ crc32$pclmul(0 ^ 0xffffffffu, hyperion, size));
}
}
+
+#define TESTSTR "libc/calls/typedef/sighandler_t.h"
+
+BENCH(crc32c, bench) {
+ EZBENCH2("crc32c", donothing,
+ EXPROPRIATE(crc32c(0, VEIL("r", TESTSTR), sizeof(TESTSTR) - 1)));
+}
+
+BENCH(KnuthMultiplicativeHash32, bench) {
+ EZBENCH2("KMP", donothing,
+ EXPROPRIATE(KnuthMultiplicativeHash32(VEIL("r", TESTSTR),
+ sizeof(TESTSTR) - 1)));
+}
diff --git a/test/libc/nexgen32e/kcp437_test.c b/test/libc/nexgen32e/kcp437_test.c
new file mode 100644
index 000000000..2f8b2a274
--- /dev/null
+++ b/test/libc/nexgen32e/kcp437_test.c
@@ -0,0 +1,30 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/nexgen32e/nexgen32e.h"
+#include "libc/str/str.h"
+#include "libc/testlib/testlib.h"
+#include "libc/unicode/unicode.h"
+
+TEST(kcp437, test) {
+ long i;
+ for (i = 0; i < 256; ++i) {
+ EXPECT_EQ(1, wcwidth(kCp437[i]), "%lc", kCp437[i]);
+ }
+}
diff --git a/test/libc/nexgen32e/memeqmask_test.c b/test/libc/nexgen32e/memeqmask_test.c
index 74dacca0a..900fbbd82 100644
--- a/test/libc/nexgen32e/memeqmask_test.c
+++ b/test/libc/nexgen32e/memeqmask_test.c
@@ -20,7 +20,6 @@
#include "libc/bits/bits.h"
#include "libc/runtime/buffer.h"
#include "libc/runtime/gc.h"
-#include "libc/runtime/mappings.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/testlib/testlib.h"
diff --git a/test/libc/str/memmove_test.c b/test/libc/nexgen32e/memmove_test.c
similarity index 87%
rename from test/libc/str/memmove_test.c
rename to test/libc/nexgen32e/memmove_test.c
index 8c821f0f4..156ba766c 100644
--- a/test/libc/str/memmove_test.c
+++ b/test/libc/nexgen32e/memmove_test.c
@@ -25,12 +25,15 @@
#define N 256
#define S 7
+long i, j, n;
+char *b1, *b2;
+
TEST(memmove, overlapping) {
- for (size_t i = 0; i < N; i += S) {
- for (size_t j = 10; j < N; j += S) {
- char *b1 = tmalloc(N);
- char *b2 = tmalloc(N);
- size_t n = min(N - i, N - j);
+ for (i = 0; i < N; i += S) {
+ for (j = 10; j < N; j += S) {
+ b1 = tmalloc(N);
+ b2 = tmalloc(N);
+ n = min(N - i, N - j);
memcpy(b2, b1 + i, n);
ASSERT_EQ(b1 + j, memmove(b1 + j, b1 + i, n));
ASSERT_EQ(0, memcmp(b1 + j, b2, n));
@@ -41,11 +44,11 @@ TEST(memmove, overlapping) {
}
TEST(memmove, overlappingDirect) {
- for (size_t i = 0; i < N; i += S) {
- for (size_t j = 10; j < N; j += S) {
- char *b1 = tmalloc(N);
- char *b2 = tmalloc(N);
- size_t n = min(N - i, N - j);
+ for (i = 0; i < N; i += S) {
+ for (j = 10; j < N; j += S) {
+ b1 = tmalloc(N);
+ b2 = tmalloc(N);
+ n = min(N - i, N - j);
memcpy(b2, b1 + i, n);
ASSERT_EQ(b1 + j, (memmove)(b1 + j, b1 + i, n));
ASSERT_EQ(0, memcmp(b1 + j, b2, n));
diff --git a/test/libc/intrin/packuswb_test.c b/test/libc/nexgen32e/memset_test.c
similarity index 60%
rename from test/libc/intrin/packuswb_test.c
rename to test/libc/nexgen32e/memset_test.c
index bbd4eef93..99842f56a 100644
--- a/test/libc/intrin/packuswb_test.c
+++ b/test/libc/nexgen32e/memset_test.c
@@ -17,54 +17,55 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/intrin/packsswb.h"
-#include "libc/intrin/packuswb.h"
-#include "libc/limits.h"
-#include "libc/nexgen32e/kcpuids.h"
-#include "libc/str/str.h"
+#include "libc/nexgen32e/nexgen32e.h"
#include "libc/testlib/testlib.h"
-const short S[8] = {0, 128, -128, 255, SHRT_MAX, SHRT_MIN, 0, 0};
-
-TEST(packuswb, test) {
- unsigned char B[16] = {0};
- packuswb(B, S, S);
- EXPECT_EQ(0, B[0]);
- EXPECT_EQ(128, B[1]);
- EXPECT_EQ(0, B[2]);
- EXPECT_EQ(255, B[3]);
- EXPECT_EQ(255, B[4]);
- EXPECT_EQ(0, B[5]);
- EXPECT_EQ(0, B[6]);
- EXPECT_EQ(0, B[7]);
- EXPECT_EQ(0, B[8]);
- EXPECT_EQ(128, B[9]);
- EXPECT_EQ(0, B[10]);
- EXPECT_EQ(255, B[11]);
- EXPECT_EQ(255, B[12]);
- EXPECT_EQ(0, B[13]);
- EXPECT_EQ(0, B[14]);
- EXPECT_EQ(0, B[15]);
+TEST(memset, testMulTrick4) {
+ long i, j;
+ unsigned long x;
+ long di, si, dx, ax;
+ volatile uint8_t *b;
+ b = tgc(tmalloc(4));
+ for (i = 0; i < 255; ++i) {
+ for (j = -1; j < 1; ++j) {
+ x = j;
+ x &= ~0xff;
+ x |= i;
+ asm volatile("call\tmemset"
+ : "=D"(di), "=S"(si), "=d"(dx), "=a"(ax)
+ : "0"(b), "1"(x), "2"(4)
+ : "rcx", "memory", "cc");
+ ASSERT_EQ(x & 0xff, b[0]);
+ ASSERT_EQ(x & 0xff, b[1]);
+ ASSERT_EQ(x & 0xff, b[2]);
+ ASSERT_EQ(x & 0xff, b[3]);
+ }
+ }
}
-TEST(packsswb, test) {
- const short S[8] = {0, 128, -128, 255, SHRT_MAX, SHRT_MIN, 0, 0};
- signed char B[16] = {0};
- packsswb(B, S, S);
- EXPECT_EQ(0, B[0]);
- EXPECT_EQ(127, B[1]);
- EXPECT_EQ(-128, B[2]);
- EXPECT_EQ(127, B[3]);
- EXPECT_EQ(127, B[4]);
- EXPECT_EQ(-128, B[5]);
- EXPECT_EQ(0, B[6]);
- EXPECT_EQ(0, B[7]);
- EXPECT_EQ(0, B[8]);
- EXPECT_EQ(127, B[9]);
- EXPECT_EQ(-128, B[10]);
- EXPECT_EQ(127, B[11]);
- EXPECT_EQ(127, B[12]);
- EXPECT_EQ(-128, B[13]);
- EXPECT_EQ(0, B[14]);
- EXPECT_EQ(0, B[15]);
+TEST(memset, testMulTrick8) {
+ long i, j;
+ unsigned long x;
+ long di, si, dx, ax;
+ volatile uint8_t *b;
+ b = tgc(tmalloc(8));
+ for (i = 0; i < 255; ++i) {
+ for (j = -1; j < 1; ++j) {
+ x = j;
+ x &= ~0xff;
+ x |= i;
+ asm volatile("call\tmemset"
+ : "=D"(di), "=S"(si), "=d"(dx), "=a"(ax)
+ : "0"(b), "1"(x), "2"(8)
+ : "rcx", "memory", "cc");
+ ASSERT_EQ(x & 0xff, b[0]);
+ ASSERT_EQ(x & 0xff, b[1]);
+ ASSERT_EQ(x & 0xff, b[2]);
+ ASSERT_EQ(x & 0xff, b[3]);
+ ASSERT_EQ(x & 0xff, b[4]);
+ ASSERT_EQ(x & 0xff, b[5]);
+ ASSERT_EQ(x & 0xff, b[6]);
+ ASSERT_EQ(x & 0xff, b[7]);
+ }
+ }
}
diff --git a/test/libc/nexgen32e/sidiv_test.c b/test/libc/nexgen32e/sidiv_test.c
new file mode 100644
index 000000000..9ff32d664
--- /dev/null
+++ b/test/libc/nexgen32e/sidiv_test.c
@@ -0,0 +1,48 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/nexgen32e/nexgen32e.h"
+#include "libc/testlib/testlib.h"
+
+TEST(sidiv, smoke) {
+ EXPECT_EQ(13373133731337 / 10, div10int64(13373133731337));
+ EXPECT_EQ(13373133731337 / 100, div100int64(13373133731337));
+ EXPECT_EQ(13373133731337 / 1000, div1000int64(13373133731337));
+ EXPECT_EQ(13373133731337 / 10000, div10000int64(13373133731337));
+ EXPECT_EQ(13373133731337 / 1000000, div1000000int64(13373133731337));
+ EXPECT_EQ(13373133731337 / 1000000000, div1000000000int64(13373133731337));
+}
+
+TEST(sirem, smoke) {
+ EXPECT_EQ(13373133731337 % 10, rem10int64(13373133731337));
+ EXPECT_EQ(13373133731337 % 100, rem100int64(13373133731337));
+ EXPECT_EQ(13373133731337 % 1000, rem1000int64(13373133731337));
+ EXPECT_EQ(13373133731337 % 10000, rem10000int64(13373133731337));
+ EXPECT_EQ(13373133731337 % 1000000, rem1000000int64(13373133731337));
+ EXPECT_EQ(13373133731337 % 1000000000, rem1000000000int64(13373133731337));
+}
+
+TEST(rem, euclid) {
+ ASSERT_EQ(-2, rem10int64(-12));
+ ASSERT_EQ(-1, rem10int64(-1));
+ ASSERT_EQ(0, rem10int64(0));
+ ASSERT_EQ(1, rem10int64(1));
+ ASSERT_EQ(9, rem10int64(9));
+ ASSERT_EQ(1, rem10int64(11));
+}
diff --git a/test/libc/nexgen32e/strtolower_test.c b/test/libc/nexgen32e/strcaseconv_test.c
similarity index 74%
rename from test/libc/nexgen32e/strtolower_test.c
rename to test/libc/nexgen32e/strcaseconv_test.c
index 68a4b87c9..be014a553 100644
--- a/test/libc/nexgen32e/strtolower_test.c
+++ b/test/libc/nexgen32e/strcaseconv_test.c
@@ -24,25 +24,31 @@
#include "libc/testlib/testlib.h"
TEST(strtolower, testAligned) {
- char s[128] = "AZCDabcdABCDabcdABCDabcdABCDabcdABCDabcd";
- EXPECT_STREQ("azcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", strtolower(s));
+ EXPECT_STREQ("azcdabcdabcdabcd",
+ strtolower(tgc(tstrdup("AZCDabcdABCDabcd"))));
+ EXPECT_STREQ("azcdabcdabcdabcdabcdabcdabcdabcd",
+ strtolower(tgc(tstrdup("AZCDabcdABCDabcdABCDabcdABCDabcd"))));
}
TEST(strtolower, testUnaligned) {
- char s[128] = "AZCDabcdABCDabcdABCDabcdABCDabcdABCDabcd";
- strtolower(s + 1);
- EXPECT_STREQ("Azcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", s);
+ EXPECT_STREQ("1", strtolower(tgc(tstrdup("1"))));
+ EXPECT_STREQ(
+ "zcdabcdabcdabcdabcdabcdabcdabc",
+ strtolower((char *)tgc(tstrdup("AZCDabcdABCDabcdABCDabcdABCDabc")) + 1));
}
TEST(strtoupper, testAligned) {
- char s[128] = "AZCDabcdABCDabcdA0CDabcdABCDabcdABCDabcd";
- EXPECT_STREQ("AZCDABCDABCDABCDA0CDABCDABCDABCDABCDABCD", strtoupper(s));
+ EXPECT_STREQ("AZCDABCDABCDABCD",
+ strtoupper(tgc(tstrdup("AZCDabcdABCDabcd"))));
+ EXPECT_STREQ("AZCDABCDABCDABCDABCDABCDABCDABCD",
+ strtoupper(tgc(tstrdup("AZCDabcdABCDabcdABCDabcdABCDabcd"))));
}
TEST(strtoupper, testUnaligned) {
- char s[128] = "aZCDabcdABCDabcdABCDabcdABCDabcdABCDabcd";
- strtoupper(s + 1);
- EXPECT_STREQ("aZCDABCDABCDABCDABCDABCDABCDABCDABCDABCD", s);
+ EXPECT_STREQ("1", strtoupper(tgc(tstrdup("1"))));
+ EXPECT_STREQ(
+ "ZCDABCDABCDABCDABCDABCDABCDABC",
+ strtoupper((char *)tgc(tstrdup("AZCDabcdABCDabcdABCDabcdABCDabc")) + 1));
}
BENCH(strtolower, bench) {
diff --git a/test/libc/nexgen32e/test.mk b/test/libc/nexgen32e/test.mk
index 8cb7cebd4..4b8bbf68f 100644
--- a/test/libc/nexgen32e/test.mk
+++ b/test/libc/nexgen32e/test.mk
@@ -13,7 +13,7 @@ TEST_LIBC_NEXGEN32E_OBJS = \
$(TEST_LIBC_NEXGEN32E_SRCS:%.c=o/$(MODE)/%.o)
TEST_LIBC_NEXGEN32E_COMS = \
- $(TEST_LIBC_NEXGEN32E_OBJS:%.o=%.com)
+ $(TEST_LIBC_NEXGEN32E_SRCS:%.c=o/$(MODE)/%.com)
TEST_LIBC_NEXGEN32E_BINS = \
$(TEST_LIBC_NEXGEN32E_COMS) \
@@ -38,6 +38,7 @@ TEST_LIBC_NEXGEN32E_DIRECTDEPS = \
LIBC_RUNTIME \
LIBC_STUBS \
LIBC_STR \
+ LIBC_UNICODE \
LIBC_TESTLIB \
LIBC_X \
TOOL_VIZ_LIB
diff --git a/test/libc/rand/test.mk b/test/libc/rand/test.mk
index 6586ea6a9..188cbcf87 100644
--- a/test/libc/rand/test.mk
+++ b/test/libc/rand/test.mk
@@ -5,13 +5,15 @@ PKGS += TEST_LIBC_RAND
TEST_LIBC_RAND_SRCS := $(wildcard test/libc/rand/*.c)
TEST_LIBC_RAND_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_RAND_SRCS))
-TEST_LIBC_RAND_COMS = $(TEST_LIBC_RAND_OBJS:%.o=%.com)
TEST_LIBC_RAND_BINS = $(TEST_LIBC_RAND_COMS) $(TEST_LIBC_RAND_COMS:%=%.dbg)
TEST_LIBC_RAND_OBJS = \
$(TEST_LIBC_RAND_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_RAND_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_RAND_COMS = \
+ $(TEST_LIBC_RAND_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_RAND_TESTS = $(TEST_LIBC_RAND_SRCS_TEST:%.c=o/$(MODE)/%.com.ok)
TEST_LIBC_RAND_CHECKS = \
@@ -45,9 +47,7 @@ o/$(MODE)/test/libc/rand/%.com.dbg: \
$(APE)
@$(APELINK)
-$(TEST_LIBC_RAND_OBJS): \
- $(BUILD_FILES) \
- test/libc/rand/test.mk
+$(TEST_LIBC_RAND_OBJS): test/libc/rand/test.mk
$(TEST_LIBC_RAND_OBJS): \
DEFAULT_CCFLAGS += \
diff --git a/test/libc/runtime/arch_prctl_test.c b/test/libc/runtime/arch_prctl_test.c
index 8faece1cb..7b7972d8d 100644
--- a/test/libc/runtime/arch_prctl_test.c
+++ b/test/libc/runtime/arch_prctl_test.c
@@ -17,8 +17,9 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/dce.h"
+#include "libc/bits/segmentation.h"
#include "libc/calls/calls.h"
+#include "libc/dce.h"
#include "libc/testlib/testlib.h"
TEST(arch_prctl, fs) {
diff --git a/test/libc/runtime/balloc_test.c b/test/libc/runtime/balloc_test.c
deleted file mode 100644
index dee08c88d..000000000
--- a/test/libc/runtime/balloc_test.c
+++ /dev/null
@@ -1,105 +0,0 @@
-/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
-│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/bits/safemacros.h"
-#include "libc/calls/calls.h"
-#include "libc/calls/struct/sigaction.h"
-#include "libc/calls/ucontext.h"
-#include "libc/runtime/buffer.h"
-#include "libc/runtime/runtime.h"
-#include "libc/stdio/stdio.h"
-#include "libc/str/str.h"
-#include "libc/sysv/consts/fileno.h"
-#include "libc/sysv/consts/prot.h"
-#include "libc/sysv/consts/sa.h"
-#include "libc/sysv/consts/sig.h"
-#include "libc/testlib/testlib.h"
-#include "libc/x/x.h"
-#include "third_party/xed/x86.h"
-
-char *p;
-bool segfaulted_;
-struct GuardedBuffer b_;
-struct sigaction oldsegv_;
-struct XedDecodedInst xedd_;
-
-void RestrictPage(void *addr, unsigned flags) {
- addr = (void *)rounddown((intptr_t)addr, PAGESIZE);
- EXPECT_NE(-1, mprotect(addr, PAGESIZE, flags));
-}
-
-void OnSegLol(int sig, struct siginfo *si, struct ucontext *uc) {
- size_t i;
- uint8_t *rip;
- segfaulted_ = true;
- rip = (uint8_t *)uc->uc_mcontext.rip;
- RestrictPage(rip, PROT_READ | PROT_WRITE | PROT_EXEC);
- ASSERT_EQ(XED_ERROR_NONE,
- xed_instruction_length_decode(xed_decoded_inst_zero_set_mode(
- &xedd_, XED_MACHINE_MODE_LONG_64),
- rip, XED_MAX_INSTRUCTION_BYTES));
- for (i = 0; i < xedd_.decoded_length; ++i) rip[i] = 0x90; /* NOP */
- RestrictPage(rip, PROT_READ | PROT_EXEC);
-}
-
-void SetUp(void) {
- segfaulted_ = false;
- memset(&b_, 0, sizeof(b_));
- ASSERT_NE(-1, xsigaction(SIGSEGV, OnSegLol, SA_RESETHAND | SA_RESTART, 0,
- &oldsegv_));
-}
-
-void TearDown(void) {
- EXPECT_NE(-1, sigaction(SIGSEGV, &oldsegv_, NULL));
- bfree(&b_);
- EXPECT_EQ(NULL, b_.p);
-}
-
-TEST(balloc, createsGuardPage) {
- ASSERT_NE(NULL, (p = balloc(&b_, 1, 1)));
- EXPECT_EQ(p, b_.p);
- p[0] = '.';
- ASSERT_FALSE(segfaulted_);
- /* TODO(jart): fix me!!! */
- /* p[1 + __BIGGEST_ALIGNMENT__] = '!'; */
- /* EXPECT_TRUE(segfaulted_); */
-}
-
-TEST(balloc, aligned_roundsUp) {
- ASSERT_NE(NULL, (p = balloc(&b_, 128, 1)));
- EXPECT_EQ(0, (intptr_t)b_.p & 127);
- p[127] = '.';
- ASSERT_FALSE(segfaulted_);
- /* TODO(jart): fix me!!! */
- /* p[128 + __BIGGEST_ALIGNMENT__] = '!'; */
- /* EXPECT_TRUE(segfaulted_); */
-}
-
-TEST(balloc, multipleCalls_avoidsNeedlessSyscalls) {
- size_t c;
- c = g_syscount;
- ASSERT_NE(NULL, (p = balloc(&b_, 1, 31337)));
- EXPECT_GT(g_syscount, c);
- c = g_syscount;
- ASSERT_NE(NULL, (p = balloc(&b_, 1, 31337 / 2)));
- EXPECT_EQ(g_syscount, c);
- c = g_syscount;
- ASSERT_NE(NULL, (p = balloc(&b_, 1, 31337 * 2)));
- EXPECT_GT(g_syscount, c);
-}
diff --git a/test/libc/runtime/carsort_test.c b/test/libc/runtime/carsort_test.c
new file mode 100644
index 000000000..9239db7d7
--- /dev/null
+++ b/test/libc/runtime/carsort_test.c
@@ -0,0 +1,75 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/alg/alg.h"
+#include "libc/macros.h"
+#include "libc/rand/rand.h"
+#include "libc/runtime/carsort.h"
+#include "libc/str/str.h"
+#include "libc/testlib/ezbench.h"
+#include "libc/testlib/testlib.h"
+
+const int32_t kUnsorted[][2] = {
+ {4, 'a'}, {65, 'b'}, {2, 'c'}, {-1, 'G'}, {-31, 'd'}, {0, 'e'},
+ {99, 'f'}, {2, 'g'}, {83, 'h'}, {782, 'i'}, {1, 'j'},
+};
+
+const int32_t kGolden[][2] = {
+ {-31, 'd'}, {-1, 'G'}, {0, 'e'}, {1, 'j'}, {2, 'c'}, {2, 'g'},
+ {4, 'a'}, {65, 'b'}, {83, 'h'}, {99, 'f'}, {782, 'i'},
+};
+
+int32_t A[ARRAYLEN(kUnsorted)][2];
+int32_t B[2100][2];
+
+TEST(carsort100, test) {
+ memcpy(A, kUnsorted, sizeof(A));
+ carsort100(ARRAYLEN(A), A);
+ ASSERT_EQ(0, memcmp(&A[0], &kGolden[0], sizeof(kUnsorted)));
+}
+
+TEST(carsort1000, test) {
+ memcpy(A, kUnsorted, sizeof(A));
+ carsort1000(ARRAYLEN(A), A);
+ ASSERT_EQ(0, memcmp(&A[0], &kGolden[0], sizeof(kUnsorted)));
+}
+
+TEST(qsort, test) {
+ memcpy(A, kUnsorted, sizeof(A));
+ qsort(A, ARRAYLEN(A), 8, cmpsl);
+ ASSERT_EQ(0, memcmp(&A[0], &kGolden[0], sizeof(kUnsorted)));
+}
+
+BENCH(carsort, benchMedium) {
+ EZBENCH2("medium carsort100", rngset(B, sizeof(B), rand64, -1),
+ carsort100(ARRAYLEN(B), B));
+ EZBENCH2("medium carsort1000", rngset(B, sizeof(B), rand64, -1),
+ carsort1000(ARRAYLEN(B), B));
+ EZBENCH2("medium qsort", rngset(B, sizeof(B), rand64, -1),
+ qsort(B, ARRAYLEN(B), 8, cmpsl));
+}
+
+BENCH(carsort, benchSmall) {
+ EZBENCH2("small carsort100", memcpy(A, kUnsorted, sizeof(A)),
+ carsort100(ARRAYLEN(A), A));
+ EZBENCH2("small carsort1000", memcpy(A, kUnsorted, sizeof(A)),
+ carsort1000(ARRAYLEN(A), A));
+ EZBENCH2("small qsort", memcpy(A, kUnsorted, sizeof(A)),
+ qsort(A, ARRAYLEN(A), 8, cmpsl));
+}
diff --git a/test/libc/runtime/grow_test.c b/test/libc/runtime/grow_test.c
index 51c474b04..bfba721f2 100644
--- a/test/libc/runtime/grow_test.c
+++ b/test/libc/runtime/grow_test.c
@@ -21,7 +21,6 @@
#include "libc/limits.h"
#include "libc/macros.h"
#include "libc/mem/mem.h"
-#include "libc/runtime/mappings.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/testlib/testlib.h"
diff --git a/test/libc/runtime/itsatrap_test.c b/test/libc/runtime/itsatrap_test.c
index 214552ee0..ce221b7af 100644
--- a/test/libc/runtime/itsatrap_test.c
+++ b/test/libc/runtime/itsatrap_test.c
@@ -36,7 +36,7 @@
* @see __addvsi3, __mulvsi3, etc.
*/
-bool overflowed_;
+volatile bool overflowed_;
void __on_arithmetic_overflow(void) {
overflowed_ = true;
@@ -174,6 +174,16 @@ TEST(__mulvdi3, standAndDeliver_aNegativeTimesANegativeEqualsAPositive) {
EXPECT_FALSE(overflowed_);
}
+TEST(__mulvdi3, testOverflow) {
+ volatile int64_t x;
+ x = 3037000500;
+ x *= 3037000499;
+ EXPECT_FALSE(overflowed_);
+ x = 3037000500;
+ x *= 3037000500;
+ EXPECT_TRUE(overflowed_);
+}
+
/* 32-BIT SIGNED ADDITION */
TEST(__addvsi3, testMin1) {
@@ -246,3 +256,99 @@ TEST(__subvdi3, testMax1) {
EXPECT_EQ(LONG_MAX - 1, VEIL("r", LONG_MAX) - 1);
EXPECT_FALSE(overflowed_);
}
+
+/* 128-BIT SIGNED ADDITION */
+
+TEST(__addvti3, testMath) {
+ volatile int128_t x;
+ x = 2;
+ x += 2;
+ EXPECT_EQ(4, x);
+ x = -2;
+ x += -2;
+ EXPECT_EQ(-4, x);
+ x = UINT64_MAX;
+ x += 1;
+ EXPECT_EQ((int128_t)UINT64_MAX + 1, x);
+ EXPECT_FALSE(overflowed_);
+}
+
+TEST(__addvti3, testOverflow) {
+ volatile int128_t x;
+ x = INT128_MAX;
+ x += 1;
+ EXPECT_TRUE(overflowed_);
+}
+
+/* 128-BIT SIGNED SUBTRACTION */
+
+TEST(__subvti3, testMath) {
+ volatile int128_t x;
+ x = -2;
+ x -= 2;
+ EXPECT_EQ(-4, x);
+ x = UINT64_MIN;
+ x -= 1;
+ EXPECT_EQ((int128_t)UINT64_MIN - 1, x);
+ EXPECT_FALSE(overflowed_);
+}
+
+TEST(__subvti3, testOverflow) {
+ volatile int128_t x;
+ x = INT128_MIN;
+ x -= 1;
+ EXPECT_TRUE(overflowed_);
+}
+
+/* 128-BIT SIGNED NEGATION */
+
+TEST(__negvti3, testMath) {
+ volatile int128_t x;
+ x = -2;
+ x = -x;
+ EXPECT_EQ(2, x);
+ EXPECT_FALSE(overflowed_);
+ x = INT128_MAX;
+ x = -x;
+ EXPECT_EQ(INT128_MIN + 1, x);
+ EXPECT_FALSE(overflowed_);
+ x = (uint128_t)0x8000000000000000 << 64 | 0x8000000000000000;
+ x = -x;
+ EXPECT_EQ((uint128_t)0x7fffffffffffffff << 64 | 0x8000000000000000, x);
+ EXPECT_FALSE(overflowed_);
+}
+
+TEST(__negvti3, testOverflow) {
+ volatile int128_t x;
+ x = INT128_MIN;
+ x = -x;
+ EXPECT_TRUE(overflowed_);
+}
+
+/* 128-BIT SIGNED MULTIPLICATION */
+
+TEST(__mulvti3, testMath) {
+ volatile int128_t x;
+ x = 7;
+ x *= 11;
+ EXPECT_EQ(77, x);
+ EXPECT_FALSE(overflowed_);
+ x = 0x1fffffffffffffff;
+ x *= 0x1fffffffffffffff;
+ EXPECT_EQ((uint128_t)0x3ffffffffffffff << 64 | 0xc000000000000001, x);
+ EXPECT_FALSE(overflowed_);
+ x = -0x1fffffffffffffff;
+ x *= 0x1fffffffffffffff;
+ EXPECT_EQ((uint128_t)0xfc00000000000000 << 64 | 0x3fffffffffffffff, x);
+ EXPECT_FALSE(overflowed_);
+}
+
+TEST(__mulvti3, testOverflow) {
+ volatile int128_t x;
+ x = 0xb504f333f9de5be0;
+ x *= 0xb504f333f9de6d28;
+ EXPECT_FALSE(overflowed_);
+ x = 0xb504f333f9de5be0;
+ x *= 0xb504f333f9de6d29;
+ EXPECT_TRUE(overflowed_);
+}
diff --git a/test/libc/runtime/memtrack_test.c b/test/libc/runtime/memtrack_test.c
new file mode 100644
index 000000000..f3b0a5245
--- /dev/null
+++ b/test/libc/runtime/memtrack_test.c
@@ -0,0 +1,289 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/errno.h"
+#include "libc/limits.h"
+#include "libc/log/check.h"
+#include "libc/mem/mem.h"
+#include "libc/runtime/memtrack.h"
+#include "libc/runtime/runtime.h"
+#include "libc/stdio/stdio.h"
+#include "libc/str/str.h"
+#include "libc/testlib/testlib.h"
+
+static bool AreMemoryIntervalsEqual(const struct MemoryIntervals *mm1,
+ const struct MemoryIntervals *mm2) {
+ if (mm1->i != mm2->i) return false;
+ if (memcmp(mm1->p, mm2->p, mm1->i * sizeof(*mm2->p)) != 0) return false;
+ if (memcmp(mm1->h, mm2->h, mm1->i * sizeof(*mm2->h)) != 0) return false;
+ return true;
+}
+
+static void PrintMemoryInterval(const struct MemoryIntervals *mm) {
+ int i;
+ for (i = 0; i < mm->i; ++i) {
+ if (i) fprintf(stderr, ",");
+ fprintf(stderr, "{%d,%d}", mm->p[i].x, mm->p[i].y);
+ }
+ fprintf(stderr, "\n");
+}
+
+static void CheckMemoryIntervalsEqual(const struct MemoryIntervals *mm1,
+ const struct MemoryIntervals *mm2) {
+ if (!AreMemoryIntervalsEqual(mm1, mm2)) {
+ PrintMemoryInterval(mm1);
+ PrintMemoryInterval(mm2);
+ CHECK(!"memory intervals not equal");
+ exit(1);
+ }
+}
+
+static void CheckMemoryIntervalsAreOk(const struct MemoryIntervals *mm) {
+ if (!AreMemoryIntervalsOk(mm)) {
+ PrintMemoryInterval(mm);
+ CHECK(!"memory intervals not ok");
+ exit(1);
+ }
+}
+
+static void RunTrackMemoryIntervalTest(const struct MemoryIntervals t[2], int x,
+ int y, long h) {
+ struct MemoryIntervals *mm;
+ mm = memcpy(memalign(alignof(*t), sizeof(*t)), t, sizeof(*t));
+ CheckMemoryIntervalsAreOk(mm);
+ CHECK_NE(-1, TrackMemoryInterval(mm, x, y, h));
+ CheckMemoryIntervalsAreOk(mm);
+ CheckMemoryIntervalsEqual(mm, t + 1);
+ free(mm);
+}
+
+static void RunReleaseMemoryIntervalsTest(const struct MemoryIntervals t[2],
+ int x, int y) {
+ struct MemoryIntervals *mm;
+ mm = memcpy(memalign(alignof(*t), sizeof(*t)), t, sizeof(*t));
+ CheckMemoryIntervalsAreOk(mm);
+ CHECK_NE(-1, ReleaseMemoryIntervals(mm, x, y, NULL));
+ CheckMemoryIntervalsAreOk(mm);
+ CheckMemoryIntervalsEqual(t + 1, mm);
+ free(mm);
+}
+
+TEST(TrackMemoryInterval, TestEmpty) {
+ static const struct MemoryIntervals mm[2] = {
+ {0, {}, {}},
+ {1, {{2, 2}}, {0}},
+ };
+ RunTrackMemoryIntervalTest(mm, 2, 2, 0);
+}
+
+TEST(TrackMemoryInterval, TestFull) {
+ int i;
+ struct MemoryIntervals *mm;
+ mm = calloc(1, sizeof(struct MemoryIntervals));
+ for (i = 0; i < ARRAYLEN(mm->p); ++i) {
+ CheckMemoryIntervalsAreOk(mm);
+ CHECK_NE(-1, TrackMemoryInterval(mm, i, i, i));
+ CheckMemoryIntervalsAreOk(mm);
+ }
+ CHECK_EQ(-1, TrackMemoryInterval(mm, i, i, i));
+ CHECK_EQ(ENOMEM, errno);
+ CheckMemoryIntervalsAreOk(mm);
+ free(mm);
+}
+
+TEST(TrackMemoryInterval, TestAppend) {
+ static const struct MemoryIntervals mm[2] = {
+ {1, {{2, 2}}, {0}},
+ {1, {{2, 3}}, {0}},
+ };
+ RunTrackMemoryIntervalTest(mm, 3, 3, 0);
+}
+
+TEST(TrackMemoryInterval, TestPrepend) {
+ static const struct MemoryIntervals mm[2] = {
+ {1, {{2, 2}}, {0}},
+ {1, {{1, 2}}, {0}},
+ };
+ RunTrackMemoryIntervalTest(mm, 1, 1, 0);
+}
+
+TEST(TrackMemoryInterval, TestFillHole) {
+ static const struct MemoryIntervals mm[2] = {
+ {4, {{1, 1}, {3, 4}, {5, 5}, {6, 8}}, {0, 0, 1, 0}},
+ {3, {{1, 4}, {5, 5}, {6, 8}}, {0, 1, 0}},
+ };
+ RunTrackMemoryIntervalTest(mm, 2, 2, 0);
+}
+
+TEST(TrackMemoryInterval, TestAppend2) {
+ static const struct MemoryIntervals mm[2] = {
+ {1, {{2, 2}}, {0}},
+ {2, {{2, 2}, {3, 3}}, {0, 1}},
+ };
+ RunTrackMemoryIntervalTest(mm, 3, 3, 1);
+}
+
+TEST(TrackMemoryInterval, TestPrepend2) {
+ static const struct MemoryIntervals mm[2] = {
+ {1, {{2, 2}}, {0}},
+ {2, {{1, 1}, {2, 2}}, {1, 0}},
+ };
+ RunTrackMemoryIntervalTest(mm, 1, 1, 1);
+}
+
+TEST(TrackMemoryInterval, TestFillHole2) {
+ static const struct MemoryIntervals mm[2] = {
+ {4, {{1, 1}, {3, 4}, {5, 5}, {6, 8}}, {0, 0, 1, 0}},
+ {5, {{1, 1}, {2, 2}, {3, 4}, {5, 5}, {6, 8}}, {0, 1, 0, 1, 0}},
+ };
+ RunTrackMemoryIntervalTest(mm, 2, 2, 1);
+}
+
+TEST(FindMemoryInterval, Test) {
+ static const struct MemoryIntervals mm[1] = {
+ {4,
+ {
+ [0] = {1, 1},
+ [1] = {3, 4},
+ [2] = {5, 5},
+ [3] = {6, 8},
+ },
+ {0, 0, 1, 0}},
+ };
+ EXPECT_EQ(0, FindMemoryInterval(mm, 0));
+ EXPECT_EQ(0, FindMemoryInterval(mm, 1));
+ EXPECT_EQ(1, FindMemoryInterval(mm, 2));
+ EXPECT_EQ(1, FindMemoryInterval(mm, 3));
+ EXPECT_EQ(1, FindMemoryInterval(mm, 4));
+ EXPECT_EQ(2, FindMemoryInterval(mm, 5));
+ EXPECT_EQ(3, FindMemoryInterval(mm, 6));
+ EXPECT_EQ(3, FindMemoryInterval(mm, 7));
+ EXPECT_EQ(3, FindMemoryInterval(mm, 8));
+ EXPECT_EQ(4, FindMemoryInterval(mm, 9));
+}
+
+TEST(ReleaseMemoryIntervals, TestEmpty) {
+ static const struct MemoryIntervals mm[2] = {
+ {0, {}, {}},
+ {0, {}, {}},
+ };
+ RunReleaseMemoryIntervalsTest(mm, 2, 2);
+}
+
+TEST(ReleaseMemoryIntervals, TestRemoveElement_UsesInclusiveRange) {
+ static const struct MemoryIntervals mm[2] = {
+ {3, {{0, 0}, {2, 2}, {4, 4}}, {}},
+ {2, {{0, 0}, {4, 4}}, {}},
+ };
+ RunReleaseMemoryIntervalsTest(mm, 2, 2);
+}
+
+TEST(ReleaseMemoryIntervals, TestPunchHole) {
+ static const struct MemoryIntervals mm[2] = {
+ {1, {{0, 9}}, {}},
+ {2, {{0, 3}, {6, 9}}, {}},
+ };
+ RunReleaseMemoryIntervalsTest(mm, 4, 5);
+}
+
+TEST(ReleaseMemoryIntervals, TestShortenLeft) {
+ static const struct MemoryIntervals mm[2] = {
+ {1, {{0, 9}}, {}},
+ {1, {{0, 7}}, {}},
+ };
+ RunReleaseMemoryIntervalsTest(mm, 8, 9);
+}
+
+TEST(ReleaseMemoryIntervals, TestShortenRight) {
+ static const struct MemoryIntervals mm[2] = {
+ {1, {{0, 9}}, {}},
+ {1, {{3, 9}}, {}},
+ };
+ RunReleaseMemoryIntervalsTest(mm, 0, 2);
+}
+
+TEST(ReleaseMemoryIntervals, TestShortenLeft2) {
+ static const struct MemoryIntervals mm[2] = {
+ {1, {{0, 9}}, {}},
+ {1, {{0, 7}}, {}},
+ };
+ RunReleaseMemoryIntervalsTest(mm, 8, 11);
+}
+
+TEST(ReleaseMemoryIntervals, TestShortenRight2) {
+ static const struct MemoryIntervals mm[2] = {
+ {1, {{0, 9}}, {}},
+ {1, {{3, 9}}, {}},
+ };
+ RunReleaseMemoryIntervalsTest(mm, -3, 2);
+}
+
+TEST(ReleaseMemoryIntervals, TestZeroZero) {
+ static const struct MemoryIntervals mm[2] = {
+ {1, {{3, 9}}, {}},
+ {1, {{3, 9}}, {}},
+ };
+ RunReleaseMemoryIntervalsTest(mm, 0, 0);
+}
+
+TEST(ReleaseMemoryIntervals, TestNoopLeft) {
+ static const struct MemoryIntervals mm[2] = {
+ {1, {{3, 9}}, {}},
+ {1, {{3, 9}}, {}},
+ };
+ RunReleaseMemoryIntervalsTest(mm, 1, 2);
+}
+
+TEST(ReleaseMemoryIntervals, TestNoopRight) {
+ static const struct MemoryIntervals mm[2] = {
+ {1, {{3, 9}}, {}},
+ {1, {{3, 9}}, {}},
+ };
+ RunReleaseMemoryIntervalsTest(mm, 10, 10);
+}
+
+TEST(ReleaseMemoryIntervals, TestBigFree) {
+ static const struct MemoryIntervals mm[2] = {
+ {2, {{0, 3}, {6, 9}}, {}},
+ {0, {}, {}},
+ };
+ RunReleaseMemoryIntervalsTest(mm, INT_MIN, INT_MAX);
+}
+
+TEST(ReleaseMemoryIntervals, TestWeirdGap) {
+ static const struct MemoryIntervals mm[2] = {
+ {3, {{10, 10}, {20, 20}, {30, 30}}, {}},
+ {2, {{10, 10}, {30, 30}}, {}},
+ };
+ RunReleaseMemoryIntervalsTest(mm, 15, 25);
+}
+
+TEST(ReleaseMemoryIntervals, TestOutOfMemory) {
+ int i;
+ struct MemoryIntervals *mm;
+ mm = calloc(1, sizeof(struct MemoryIntervals));
+ for (i = 0; i < ARRAYLEN(mm->p); ++i) {
+ CHECK_NE(-1, TrackMemoryInterval(mm, i * 10, i * 10 + 8, 0));
+ }
+ CheckMemoryIntervalsAreOk(mm);
+ CHECK_EQ(-1, ReleaseMemoryIntervals(mm, 4, 4, NULL));
+ CHECK_EQ(ENOMEM, errno);
+ CheckMemoryIntervalsAreOk(mm);
+ free(mm);
+}
diff --git a/test/libc/runtime/mmap_test.c b/test/libc/runtime/mmap_test.c
index 8cb1c06f6..58fb4dbbb 100644
--- a/test/libc/runtime/mmap_test.c
+++ b/test/libc/runtime/mmap_test.c
@@ -21,8 +21,9 @@
#include "libc/bits/xchg.h"
#include "libc/calls/calls.h"
#include "libc/fmt/fmt.h"
+#include "libc/mem/mem.h"
#include "libc/runtime/gc.h"
-#include "libc/runtime/mappings.h"
+#include "libc/runtime/memtrack.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
@@ -33,71 +34,17 @@
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
-unsigned m1;
-
-TEST(mmap, testMapUnmapAnonAnyAddr) {
- void *p;
- m1 = _mm.i;
- EXPECT_NE(MAP_FAILED, (p = mmap(NULL, FRAMESIZE, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)));
- EXPECT_EQ(m1 + 1, _mm.i);
- EXPECT_NE(-1, munmap(p, FRAMESIZE));
- EXPECT_EQ(m1 + 0, _mm.i);
-}
-
-TEST(mmap, testMunmapUnmapsMultiple) {
- void *p1, *p2;
- m1 = _mm.i;
- EXPECT_NE(MAP_FAILED, (p1 = mmap(NULL, FRAMESIZE, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)));
- EXPECT_NE(MAP_FAILED, (p2 = mmap(NULL, FRAMESIZE, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)));
- if ((intptr_t)p1 > (intptr_t)p2) xchg(&p1, &p2);
- EXPECT_EQ(m1 + 2, _mm.i);
- EXPECT_NE(-1, munmap(p1, (intptr_t)p2 + (intptr_t)FRAMESIZE - (intptr_t)p1));
- EXPECT_EQ(m1 + 0, _mm.i);
-}
-
-TEST(mmap, testPartialUnmapRight) {
- if (1) return; /* naaah */
- char *p;
- m1 = _mm.i;
- EXPECT_NE(MAP_FAILED, (p = mmap(NULL, FRAMESIZE * 2, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)));
- EXPECT_EQ(m1 + 1, _mm.i);
- EXPECT_NE(-1, munmap(p + FRAMESIZE, FRAMESIZE));
- EXPECT_EQ(m1 + 1, _mm.i);
- EXPECT_NE(-1, munmap(p, FRAMESIZE));
- EXPECT_EQ(m1 + 0, _mm.i);
-}
-
-TEST(mmap, testPartialUnmapLeft) {
- if (1) return; /* naaah */
- char *p;
- m1 = _mm.i;
- EXPECT_NE(MAP_FAILED, (p = mmap(NULL, FRAMESIZE * 2, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)));
- EXPECT_EQ(m1 + 1, _mm.i);
- EXPECT_NE(-1, munmap(p, FRAMESIZE));
- EXPECT_EQ(m1 + 1, _mm.i);
- EXPECT_NE(-1, munmap(p + FRAMESIZE, FRAMESIZE));
- EXPECT_EQ(m1 + 0, _mm.i);
-}
-
TEST(mmap, testMapFile) {
int fd;
char *p;
char path[PATH_MAX];
sprintf(path, "%s%s.%d", kTmpPath, program_invocation_short_name, getpid());
- m1 = _mm.i;
ASSERT_NE(-1, (fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644)));
EXPECT_EQ(5, write(fd, "hello", 5));
EXPECT_NE(-1, fdatasync(fd));
EXPECT_NE(MAP_FAILED, (p = mmap(NULL, 5, PROT_READ, MAP_PRIVATE, fd, 0)));
- EXPECT_EQ(m1 + 1, _mm.i);
EXPECT_STREQ("hello", p);
EXPECT_NE(-1, munmap(p, 5));
- EXPECT_EQ(m1 + 0, _mm.i);
EXPECT_NE(-1, close(fd));
EXPECT_NE(-1, unlink(path));
}
@@ -106,14 +53,12 @@ TEST(mmap, testMapFile_fdGetsClosed_makesNoDifference) {
int fd;
char *p, buf[16], path[PATH_MAX];
sprintf(path, "%s%s.%d", kTmpPath, program_invocation_short_name, getpid());
- m1 = _mm.i;
ASSERT_NE(-1, (fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644)));
EXPECT_EQ(5, write(fd, "hello", 5));
EXPECT_NE(-1, fdatasync(fd));
EXPECT_NE(MAP_FAILED,
(p = mmap(NULL, 5, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)));
EXPECT_NE(-1, close(fd));
- EXPECT_EQ(m1 + 1, _mm.i);
EXPECT_STREQ("hello", p);
p[1] = 'a';
EXPECT_NE(-1, msync(p, PAGESIZE, MS_SYNC));
@@ -122,12 +67,11 @@ TEST(mmap, testMapFile_fdGetsClosed_makesNoDifference) {
EXPECT_STREQN("hallo", buf, 5);
EXPECT_NE(-1, close(fd));
EXPECT_NE(-1, munmap(p, 5));
- EXPECT_EQ(m1 + 0, _mm.i);
EXPECT_NE(-1, unlink(path));
}
TEST(mmap, testMapFixed_destroysEverythingInItsPath) {
- m1 = _mm.i;
+ unsigned m1 = _mmi.i;
EXPECT_NE(MAP_FAILED, mmap((void *)(kFixedMappingsStart + FRAMESIZE * 0),
FRAMESIZE, PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
@@ -137,10 +81,31 @@ TEST(mmap, testMapFixed_destroysEverythingInItsPath) {
EXPECT_NE(MAP_FAILED, mmap((void *)(kFixedMappingsStart + FRAMESIZE * 2),
FRAMESIZE, PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
- ASSERT_EQ(m1 + 3, _mm.i);
EXPECT_NE(MAP_FAILED, mmap((void *)(kFixedMappingsStart + FRAMESIZE * 0),
FRAMESIZE * 3, PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
- ASSERT_EQ(m1 + 1, _mm.i);
+ ASSERT_GT(_mmi.i, m1);
EXPECT_NE(-1, munmap((void *)kFixedMappingsStart, FRAMESIZE * 3));
+ ASSERT_EQ(m1, _mmi.i);
+}
+
+TEST(isheap, nullPtr) {
+ ASSERT_FALSE(isheap(NULL));
+}
+
+TEST(isheap, stackMemory) {
+ int boop;
+ ASSERT_FALSE(isheap(&boop));
+}
+
+TEST(isheap, malloc) {
+ ASSERT_TRUE(isheap(gc(malloc(1))));
+}
+
+TEST(isheap, emptyMalloc) {
+ ASSERT_TRUE(isheap(gc(malloc(0))));
+}
+
+TEST(isheap, mallocOffset) {
+ ASSERT_TRUE(isheap((char *)gc(malloc(131072)) + 100000));
}
diff --git a/test/libc/runtime/test.mk b/test/libc/runtime/test.mk
index 072560b00..224a7e9ce 100644
--- a/test/libc/runtime/test.mk
+++ b/test/libc/runtime/test.mk
@@ -5,12 +5,14 @@ PKGS += TEST_LIBC_RUNTIME
TEST_LIBC_RUNTIME_SRCS := $(wildcard test/libc/runtime/*.c)
TEST_LIBC_RUNTIME_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_RUNTIME_SRCS))
-TEST_LIBC_RUNTIME_COMS = $(TEST_LIBC_RUNTIME_OBJS:%.o=%.com)
TEST_LIBC_RUNTIME_OBJS = \
$(TEST_LIBC_RUNTIME_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_RUNTIME_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_RUNTIME_COMS = \
+ $(TEST_LIBC_RUNTIME_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_RUNTIME_BINS = \
$(TEST_LIBC_RUNTIME_COMS) \
$(TEST_LIBC_RUNTIME_COMS:%=%.dbg)
@@ -22,13 +24,16 @@ TEST_LIBC_RUNTIME_CHECKS = \
$(TEST_LIBC_RUNTIME_SRCS_TEST:%.c=o/$(MODE)/%.com.runs)
TEST_LIBC_RUNTIME_DIRECTDEPS = \
+ LIBC_ALG \
LIBC_CALLS \
LIBC_CALLS_HEFTY \
LIBC_FMT \
LIBC_MEM \
LIBC_NEXGEN32E \
+ LIBC_RAND \
LIBC_RUNTIME \
LIBC_STDIO \
+ LIBC_LOG \
LIBC_STR \
LIBC_STUBS \
LIBC_SYSV \
@@ -60,6 +65,7 @@ o/$(MODE)/test/libc/runtime/getenv_test.com.runs: \
o/$(MODE)/test/libc/runtime/getenv_test.com
@HELLO=THERE build/runit $@ $<
+o/$(MODE)/test/libc/runtime/fun_test.o \
o/$(MODE)/test/libc/runtime/itsatrap_test.o: \
OVERRIDE_CFLAGS += \
-fno-sanitize=all \
diff --git a/test/libc/sock/test.mk b/test/libc/sock/test.mk
index dbe2c69d4..740a12f5e 100644
--- a/test/libc/sock/test.mk
+++ b/test/libc/sock/test.mk
@@ -5,12 +5,14 @@ PKGS += TEST_LIBC_SOCK
TEST_LIBC_SOCK_SRCS := $(wildcard test/libc/sock/*.c)
TEST_LIBC_SOCK_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_SOCK_SRCS))
-TEST_LIBC_SOCK_COMS = $(TEST_LIBC_SOCK_OBJS:%.o=%.com)
TEST_LIBC_SOCK_OBJS = \
$(TEST_LIBC_SOCK_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_SOCK_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_SOCK_COMS = \
+ $(TEST_LIBC_SOCK_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_SOCK_BINS = \
$(TEST_LIBC_SOCK_COMS) \
$(TEST_LIBC_SOCK_COMS:%=%.dbg)
@@ -51,9 +53,7 @@ o/$(MODE)/test/libc/sock/%.com.dbg: \
$(APE)
@$(APELINK)
-$(TEST_LIBC_SOCK_OBJS): \
- $(BUILD_FILES) \
- test/libc/sock/test.mk
+$(TEST_LIBC_SOCK_OBJS): test/libc/sock/test.mk
.PHONY: o/$(MODE)/test/libc/sock
o/$(MODE)/test/libc/sock: \
diff --git a/test/libc/stdio/test.mk b/test/libc/stdio/test.mk
index d6e56863d..2089ff5d6 100644
--- a/test/libc/stdio/test.mk
+++ b/test/libc/stdio/test.mk
@@ -5,12 +5,14 @@ PKGS += TEST_LIBC_STDIO
TEST_LIBC_STDIO_SRCS := $(wildcard test/libc/stdio/*.c)
TEST_LIBC_STDIO_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_STDIO_SRCS))
-TEST_LIBC_STDIO_COMS = $(TEST_LIBC_STDIO_OBJS:%.o=%.com)
TEST_LIBC_STDIO_OBJS = \
$(TEST_LIBC_STDIO_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_STDIO_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_STDIO_COMS = \
+ $(TEST_LIBC_STDIO_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_STDIO_BINS = \
$(TEST_LIBC_STDIO_COMS) \
$(TEST_LIBC_STDIO_COMS:%=%.dbg)
diff --git a/test/libc/str/crc32c_test.c b/test/libc/str/crc32c_test.c
index 3c1d61bb7..720e9151e 100644
--- a/test/libc/str/crc32c_test.c
+++ b/test/libc/str/crc32c_test.c
@@ -18,6 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/dce.h"
+#include "libc/nexgen32e/crc32.h"
#include "libc/nexgen32e/x86feature.h"
#include "libc/str/str.h"
#include "libc/testlib/testlib.h"
@@ -40,9 +41,10 @@ TEST(crc32c, test) {
strlen(hyperion) - strlen(FANATICS)));
}
-uint32_t crc32c$pure(uint32_t, const char *, size_t) hidden;
-uint32_t crc32c$sse42(uint32_t, const char *, size_t) hidden;
-FIXTURE(crc32c, pure) { *(void **)(&crc32c) = (void *)crc32c$pure; }
+FIXTURE(crc32c, pure) {
+ *(void **)(&crc32c) = (void *)crc32c$pure;
+}
+
FIXTURE(crc32c, sse42) {
if (X86_HAVE(SSE4_2)) {
*(void **)(&crc32c) = (void *)crc32c$sse42;
diff --git a/test/libc/str/memccpy_test.c b/test/libc/str/memccpy_test.c
index 4f2c0b3e5..34d7453fe 100644
--- a/test/libc/str/memccpy_test.c
+++ b/test/libc/str/memccpy_test.c
@@ -17,9 +17,14 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
+#include "libc/str/tinymemccpy.h"
+#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
+void *memccpy2(void *, const void *, int, size_t);
+
TEST(memccpy, testStringCopy) {
char buf[16];
EXPECT_EQ(buf + 3, memccpy(buf, "hi", '\0', sizeof(buf)));
@@ -35,3 +40,21 @@ TEST(memccpy, testZeroLength_doesNothing) {
char buf[1];
EXPECT_EQ(NULL, memccpy(buf, "hi", '\0', 0));
}
+
+TEST(memccpy, memcpy) {
+ unsigned n, n2;
+ char *b1, *b2, *b3, *e1, *e2;
+ for (n = 0; n < 1026; ++n) {
+ b1 = tmalloc(n);
+ b2 = tmalloc(n);
+ b3 = tmalloc(n);
+ e1 = tinymemccpy(b2, b1, 31337, n);
+ e2 = memccpy(b3, b1, 31337, n);
+ n2 = e1 ? e1 - b1 : n;
+ ASSERT_EQ(e1, e2);
+ ASSERT_EQ(0, memcmp(b2, b3, n2));
+ tfree(b3);
+ tfree(b2);
+ tfree(b1);
+ }
+}
diff --git a/test/libc/str/regex_test.c b/test/libc/str/regex_test.c
new file mode 100644
index 000000000..b89b87df3
--- /dev/null
+++ b/test/libc/str/regex_test.c
@@ -0,0 +1,33 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/str/str.h"
+#include "libc/testlib/testlib.h"
+#include "third_party/regex/regex.h"
+
+TEST(regex, test) {
+ regex_t rx;
+ EXPECT_EQ(REG_OK, regcomp(&rx, "^[A-Za-z\x7f-\uffff]{2}$", REG_EXTENDED));
+ EXPECT_EQ(REG_OK, regexec(&rx, "AZ", 0, NULL, 0));
+ EXPECT_EQ(REG_OK, regexec(&rx, "→→", 0, NULL, 0));
+ EXPECT_EQ(REG_NOMATCH, regexec(&rx, "A", 0, NULL, 0));
+ EXPECT_EQ(REG_NOMATCH, regexec(&rx, "→", 0, NULL, 0));
+ EXPECT_EQ(REG_NOMATCH, regexec(&rx, "0", 0, NULL, 0));
+ regfree(&rx);
+}
diff --git a/test/libc/str/sha256_test.c b/test/libc/str/sha256_test.c
index 76dcac3b7..b950401d3 100644
--- a/test/libc/str/sha256_test.c
+++ b/test/libc/str/sha256_test.c
@@ -18,6 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/str/str.h"
+#include "libc/testlib/ezbench.h"
#include "libc/testlib/hyperion.h"
#include "libc/testlib/testlib.h"
@@ -30,8 +31,16 @@ uint8_t *sha256(const char *s) {
return hash;
}
+TEST(sha256, testEmpty) {
+ EXPECT_BINEQ(
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ sha256(""));
+}
+
TEST(sha256, test) {
- EXPECT_BINEQ(u",≥M║_░ú♫&Φ;*┼╣Γ€←▬▲╲▼ºB^s♦3bôïÿ$", sha256("hello"));
+ EXPECT_BINEQ(u",≥M║_░ú♫&Φ;*┼╣Γ€←▬▲\\▼ºB^s♦3bôïÿ$", sha256("hello"));
+ EXPECT_BINEQ("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b98",
+ sha256("hello"));
}
TEST(sha256, testNontrivialSize) {
diff --git a/test/libc/str/strcmp_test.c b/test/libc/str/strcmp_test.c
index c7cb07fd8..7ef413e58 100644
--- a/test/libc/str/strcmp_test.c
+++ b/test/libc/str/strcmp_test.c
@@ -551,7 +551,8 @@ void longstringislong_dupe(size_t size, char data[size], char dupe[size]) {
BENCH(bench_00_strcmp, bench) {
size_t size;
char *dupe, *data;
- size = ROUNDDOWN(getcachesize(kCpuCacheTypeData, 1) / 2, PAGESIZE);
+ size = ROUNDDOWN(MAX(FRAMESIZE, getcachesize(kCpuCacheTypeData, 1)) / 2,
+ PAGESIZE);
data = tgc(tmalloc(size));
dupe = tgc(tmalloc(size));
EZBENCH2("strcmp [identity]", longstringislong(size, data),
@@ -569,7 +570,8 @@ BENCH(bench_00_strcmp, bench) {
BENCH(bench_01_strcasecmp, bench) {
size_t size;
char *dupe, *data;
- size = ROUNDDOWN(getcachesize(kCpuCacheTypeData, 1) / 2, PAGESIZE);
+ size = ROUNDDOWN(MAX(FRAMESIZE, getcachesize(kCpuCacheTypeData, 1)) / 2,
+ PAGESIZE);
data = tgc(tmalloc(size));
dupe = tgc(tmalloc(size));
EZBENCH2("strcasecmp [identity]", longstringislong(size, data),
diff --git a/test/libc/str/strlen_test.c b/test/libc/str/strlen_test.c
index 7199b799e..75efd04bb 100644
--- a/test/libc/str/strlen_test.c
+++ b/test/libc/str/strlen_test.c
@@ -86,7 +86,7 @@ TEST(strnlen, nulNotFound_ReturnsSize) {
for (unsigned i = 0; i < ARRAYLEN(sizes); ++i) {
char *buf = tmalloc(sizes[i]);
memset(buf, ' ', sizes[i]);
- ASSERT_EQ(sizes[i], strnlen(buf, sizes[i]));
+ ASSERT_EQ(sizes[i], strnlen(buf, sizes[i]), "%d", sizes[i]);
tfree(buf);
}
}
diff --git a/test/libc/str/strrchr_test.c b/test/libc/str/strrchr_test.c
index 44dd5bbe3..614819f7c 100644
--- a/test/libc/str/strrchr_test.c
+++ b/test/libc/str/strrchr_test.c
@@ -21,25 +21,29 @@
#include "libc/testlib/testlib.h"
#define T(NAME) NAME
-#define S(S) S
-#define C(C) C
+#define S(S) S
+#define C(C) C
#include "test/libc/str/strrchr_test.inc"
#undef C
#undef S
#undef T
-#define T(NAME) NAME##16
-#define S(S) u##S
-#define C(C) u##C
+#define T(NAME) NAME##16
+#define S(S) u##S
+#define C(C) u##C
+#define strchr(x, y) strchr16(x, y)
#include "test/libc/str/strrchr_test.inc"
+#undef strchr
#undef C
#undef S
#undef T
-#define T(NAME) NAME##32
-#define S(S) L##S
-#define C(C) L##C
+#define T(NAME) NAME##32
+#define S(S) L##S
+#define C(C) L##C
+#define strchr(x, y) wcschr(x, y)
#include "test/libc/str/strrchr_test.inc"
+#undef strchr
#undef C
#undef S
#undef T
diff --git a/test/libc/str/test.mk b/test/libc/str/test.mk
index 2bf473db1..1f6a1985f 100644
--- a/test/libc/str/test.mk
+++ b/test/libc/str/test.mk
@@ -5,12 +5,14 @@ PKGS += TEST_LIBC_STR
TEST_LIBC_STR_SRCS := $(wildcard test/libc/str/*.c)
TEST_LIBC_STR_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_STR_SRCS))
-TEST_LIBC_STR_COMS = $(TEST_LIBC_STR_OBJS:%.o=%.com)
TEST_LIBC_STR_OBJS = \
$(TEST_LIBC_STR_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_STR_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_STR_COMS = \
+ $(TEST_LIBC_STR_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_STR_BINS = \
$(TEST_LIBC_STR_COMS) \
$(TEST_LIBC_STR_COMS:%=%.dbg)
@@ -38,6 +40,7 @@ TEST_LIBC_STR_DIRECTDEPS = \
LIBC_LOG \
LIBC_X \
LIBC_ZIPOS \
+ THIRD_PARTY_REGEX \
THIRD_PARTY_ZLIB
TEST_LIBC_STR_DEPS := \
diff --git a/test/libc/str/undeflate_test.c b/test/libc/str/undeflate_test.c
index d84bf042f..c9dbe793b 100644
--- a/test/libc/str/undeflate_test.c
+++ b/test/libc/str/undeflate_test.c
@@ -23,6 +23,7 @@
#include "libc/log/check.h"
#include "libc/macros.h"
#include "libc/mem/mem.h"
+#include "libc/nexgen32e/crc32.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/rbx.h"
#include "libc/runtime/runtime.h"
@@ -50,6 +51,7 @@ TEST(undeflate, testEmbeddedPlaintextConstant) {
TEST(undeflate, testStatCentralDirectory_notFound_noSysCalls) {
uint64_t c;
struct stat st;
+ stat("zip:doge.txt", &st); /* warmup */
c = g_syscount;
ASSERT_EQ(-1, stat("zip:doge.txt", &st));
ASSERT_EQ(0, g_syscount - c);
diff --git a/test/libc/str/varint_test.c b/test/libc/str/varint_test.c
deleted file mode 100644
index d5c41bc2f..000000000
--- a/test/libc/str/varint_test.c
+++ /dev/null
@@ -1,301 +0,0 @@
-/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
-│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney │
-│ │
-│ This program is free software; you can redistribute it and/or modify │
-│ it under the terms of the GNU General Public License as published by │
-│ the Free Software Foundation; version 2 of the License. │
-│ │
-│ This program is distributed in the hope that it will be useful, but │
-│ WITHOUT ANY WARRANTY; without even the implied warranty of │
-│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
-│ General Public License for more details. │
-│ │
-│ You should have received a copy of the GNU General Public License │
-│ along with this program; if not, write to the Free Software │
-│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
-│ 02110-1301 USA │
-╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/fmt/bing.h"
-#include "libc/limits.h"
-#include "libc/macros.h"
-#include "libc/runtime/gc.h"
-#include "libc/str/str.h"
-#include "libc/str/varint.h"
-#include "libc/testlib/testlib.h"
-#include "libc/x/x.h"
-
-#define TV(X, B, I) __FILE__, __LINE__, #X, X, #B, B, #I, I
-
-static const struct VarintTestVectors {
- const char *file;
- int line;
- const char *xcode;
- uint64_t x;
- const char *bcode;
- uint16_t b[11];
- const char *icode;
- uint8_t i;
-} kv[] = {
- {TV(0x0000000000000000ul, u" ", 1)},
- {TV(0x0000000000000001ul, u"☺ ", 1)},
- {TV(0x0000000000000003ul, u"♥ ", 1)},
- {TV(0x0000000000000007ul, u"• ", 1)},
- {TV(0x000000000000000ful, u"☼ ", 1)},
- {TV(0x000000000000001ful, u"▼ ", 1)},
- {TV(0x000000000000003ful, u"⁇ ", 1)},
- {TV(0x000000000000007ful, u"⌂ ", 1)},
- {TV(0x00000000000000fful, u"λ☺ ", 2)},
- {TV(0x00000000000001fful, u"λ♥ ", 2)},
- {TV(0x00000000000003fful, u"λ• ", 2)},
- {TV(0x00000000000007fful, u"λ☼ ", 2)},
- {TV(0x0000000000000ffful, u"λ▼ ", 2)},
- {TV(0x0000000000001ffful, u"λ⁇ ", 2)},
- {TV(0x0000000000003ffful, u"λ⌂ ", 2)},
- {TV(0x0000000000007ffful, u"λλ☺ ", 3)},
- {TV(0x000000000000fffful, u"λλ♥ ", 3)},
- {TV(0x000000000001fffful, u"λλ• ", 3)},
- {TV(0x000000000003fffful, u"λλ☼ ", 3)},
- {TV(0x000000000007fffful, u"λλ▼ ", 3)},
- {TV(0x00000000000ffffful, u"λλ⁇ ", 3)},
- {TV(0x00000000001ffffful, u"λλ⌂ ", 3)},
- {TV(0x00000000003ffffful, u"λλλ☺ ", 4)},
- {TV(0x00000000007ffffful, u"λλλ♥ ", 4)},
- {TV(0x0000000000fffffful, u"λλλ• ", 4)},
- {TV(0x0000000001fffffful, u"λλλ☼ ", 4)},
- {TV(0x0000000003fffffful, u"λλλ▼ ", 4)},
- {TV(0x0000000007fffffful, u"λλλ⁇ ", 4)},
- {TV(0x000000000ffffffful, u"λλλ⌂ ", 4)},
- {TV(0x000000001ffffffful, u"λλλλ☺ ", 5)},
- {TV(0x000000003ffffffful, u"λλλλ♥ ", 5)},
- {TV(0x000000007ffffffful, u"λλλλ• ", 5)},
- {TV(0x00000000fffffffful, u"λλλλ☼ ", 5)},
- {TV(0x00000001fffffffful, u"λλλλ▼ ", 5)},
- {TV(0x00000003fffffffful, u"λλλλ⁇ ", 5)},
- {TV(0x00000007fffffffful, u"λλλλ⌂ ", 5)},
- {TV(0x0000000ffffffffful, u"λλλλλ☺ ", 6)},
- {TV(0x0000001ffffffffful, u"λλλλλ♥ ", 6)},
- {TV(0x0000003ffffffffful, u"λλλλλ• ", 6)},
- {TV(0x0000007ffffffffful, u"λλλλλ☼ ", 6)},
- {TV(0x000000fffffffffful, u"λλλλλ▼ ", 6)},
- {TV(0x000001fffffffffful, u"λλλλλ⁇ ", 6)},
- {TV(0x000003fffffffffful, u"λλλλλ⌂ ", 6)},
- {TV(0x000007fffffffffful, u"λλλλλλ☺ ", 7)},
- {TV(0x00000ffffffffffful, u"λλλλλλ♥ ", 7)},
- {TV(0x00001ffffffffffful, u"λλλλλλ• ", 7)},
- {TV(0x00003ffffffffffful, u"λλλλλλ☼ ", 7)},
- {TV(0x00007ffffffffffful, u"λλλλλλ▼ ", 7)},
- {TV(0x0000fffffffffffful, u"λλλλλλ⁇ ", 7)},
- {TV(0x0001fffffffffffful, u"λλλλλλ⌂ ", 7)},
- {TV(0x0003fffffffffffful, u"λλλλλλλ☺ ", 8)},
- {TV(0x0007fffffffffffful, u"λλλλλλλ♥ ", 8)},
- {TV(0x000ffffffffffffful, u"λλλλλλλ• ", 8)},
- {TV(0x001ffffffffffffful, u"λλλλλλλ☼ ", 8)},
- {TV(0x003ffffffffffffful, u"λλλλλλλ▼ ", 8)},
- {TV(0x007ffffffffffffful, u"λλλλλλλ⁇ ", 8)},
- {TV(0x00fffffffffffffful, u"λλλλλλλ⌂ ", 8)},
- {TV(0x01fffffffffffffful, u"λλλλλλλλ☺ ", 9)},
- {TV(0x03fffffffffffffful, u"λλλλλλλλ♥ ", 9)},
- {TV(0x07fffffffffffffful, u"λλλλλλλλ• ", 9)},
- {TV(0x0ffffffffffffffful, u"λλλλλλλλ☼ ", 9)},
- {TV(0x1ffffffffffffffful, u"λλλλλλλλ▼ ", 9)},
- {TV(0x3ffffffffffffffful, u"λλλλλλλλ⁇ ", 9)},
- {TV(0x7ffffffffffffffful, u"λλλλλλλλ⌂ ", 9)},
- {TV(0xfffffffffffffffful, u"λλλλλλλλλ☺", 10)},
-};
-
-const struct ZigzagTestVectors {
- const char *file;
- int line;
- const char *xcode;
- int64_t x;
- const char *bcode;
- uint16_t b[11];
- const char *icode;
- uint8_t i;
-} kz[] = {
- {TV(0, u" ", 1)},
- {TV(1, u"☻", 1)},
- {TV(-1, u"☺", 1)},
- {TV(2, u"♦", 1)},
- {TV(-2, u"♥", 1)},
- {TV(4, u"◘", 1)},
- {TV(-4, u"•", 1)},
- {TV(8, u"►", 1)},
- {TV(-8, u"☼", 1)},
- {TV(16, u" ", 1)},
- {TV(-16, u"▼", 1)},
- {TV(32, u"@", 1)},
- {TV(-32, u"⁇", 1)},
- {TV(64, u"Ç☺", 2)},
- {TV(-64, u"⌂", 1)},
- {TV(128, u"Ç☻", 2)},
- {TV(-128, u"λ☺", 2)},
- {TV(256, u"Ç♦", 2)},
- {TV(-256, u"λ♥", 2)},
- {TV(512, u"Ç◘", 2)},
- {TV(-512, u"λ•", 2)},
- {TV(1024, u"Ç►", 2)},
- {TV(-1024, u"λ☼", 2)},
- {TV(2048, u"Ç ", 2)},
- {TV(-2048, u"λ▼", 2)},
- {TV(4096, u"Ç@", 2)},
- {TV(-4096, u"λ⁇", 2)},
- {TV(8192, u"ÇÇ☺", 3)},
- {TV(-8192, u"λ⌂", 2)},
- {TV(16384, u"ÇÇ☻", 3)},
- {TV(-16384, u"λλ☺", 3)},
- {TV(32768, u"ÇÇ♦", 3)},
- {TV(-32768, u"λλ♥", 3)},
- {TV(65536, u"ÇÇ◘", 3)},
- {TV(-65536, u"λλ•", 3)},
- {TV(131072, u"ÇÇ►", 3)},
- {TV(-131072, u"λλ☼", 3)},
- {TV(262144, u"ÇÇ ", 3)},
- {TV(-262144, u"λλ▼", 3)},
- {TV(524288, u"ÇÇ@", 3)},
- {TV(-524288, u"λλ⁇", 3)},
- {TV(1048576, u"ÇÇÇ☺", 4)},
- {TV(-1048576, u"λλ⌂", 3)},
- {TV(2097152, u"ÇÇÇ☻", 4)},
- {TV(-2097152, u"λλλ☺", 4)},
- {TV(4194304, u"ÇÇÇ♦", 4)},
- {TV(-4194304, u"λλλ♥", 4)},
- {TV(8388608, u"ÇÇÇ◘", 4)},
- {TV(-8388608, u"λλλ•", 4)},
- {TV(16777216, u"ÇÇÇ►", 4)},
- {TV(-16777216, u"λλλ☼", 4)},
- {TV(33554432, u"ÇÇÇ ", 4)},
- {TV(-33554432, u"λλλ▼", 4)},
- {TV(67108864, u"ÇÇÇ@", 4)},
- {TV(-67108864, u"λλλ⁇", 4)},
- {TV(134217728, u"ÇÇÇÇ☺", 5)},
- {TV(-134217728, u"λλλ⌂", 4)},
- {TV(268435456, u"ÇÇÇÇ☻", 5)},
- {TV(-268435456, u"λλλλ☺", 5)},
- {TV(536870912, u"ÇÇÇÇ♦", 5)},
- {TV(-536870912, u"λλλλ♥", 5)},
- {TV(1073741824, u"ÇÇÇÇ◘", 5)},
- {TV(-1073741824, u"λλλλ•", 5)},
- {TV(2147483648, u"ÇÇÇÇ►", 5)},
- {TV(-2147483648, u"λλλλ☼", 5)},
- {TV(4294967296, u"ÇÇÇÇ ", 5)},
- {TV(-4294967296, u"λλλλ▼", 5)},
- {TV(8589934592, u"ÇÇÇÇ@", 5)},
- {TV(-8589934592, u"λλλλ⁇", 5)},
- {TV(17179869184, u"ÇÇÇÇÇ☺", 6)},
- {TV(-17179869184, u"λλλλ⌂", 5)},
- {TV(34359738368, u"ÇÇÇÇÇ☻", 6)},
- {TV(-34359738368, u"λλλλλ☺", 6)},
- {TV(68719476736, u"ÇÇÇÇÇ♦", 6)},
- {TV(-68719476736, u"λλλλλ♥", 6)},
- {TV(137438953472, u"ÇÇÇÇÇ◘", 6)},
- {TV(-137438953472, u"λλλλλ•", 6)},
- {TV(274877906944, u"ÇÇÇÇÇ►", 6)},
- {TV(-274877906944, u"λλλλλ☼", 6)},
- {TV(549755813888, u"ÇÇÇÇÇ ", 6)},
- {TV(-549755813888, u"λλλλλ▼", 6)},
- {TV(1099511627776, u"ÇÇÇÇÇ@", 6)},
- {TV(-1099511627776, u"λλλλλ⁇", 6)},
- {TV(2199023255552, u"ÇÇÇÇÇÇ☺", 7)},
- {TV(-2199023255552, u"λλλλλ⌂", 6)},
- {TV(4398046511104, u"ÇÇÇÇÇÇ☻", 7)},
- {TV(-4398046511104, u"λλλλλλ☺", 7)},
- {TV(8796093022208, u"ÇÇÇÇÇÇ♦", 7)},
- {TV(-8796093022208, u"λλλλλλ♥", 7)},
- {TV(17592186044416, u"ÇÇÇÇÇÇ◘", 7)},
- {TV(-17592186044416, u"λλλλλλ•", 7)},
- {TV(35184372088832, u"ÇÇÇÇÇÇ►", 7)},
- {TV(-35184372088832, u"λλλλλλ☼", 7)},
- {TV(70368744177664, u"ÇÇÇÇÇÇ ", 7)},
- {TV(-70368744177664, u"λλλλλλ▼", 7)},
- {TV(140737488355328, u"ÇÇÇÇÇÇ@", 7)},
- {TV(-140737488355328, u"λλλλλλ⁇", 7)},
- {TV(281474976710656, u"ÇÇÇÇÇÇÇ☺", 8)},
- {TV(-281474976710656, u"λλλλλλ⌂", 7)},
- {TV(562949953421312, u"ÇÇÇÇÇÇÇ☻", 8)},
- {TV(-562949953421312, u"λλλλλλλ☺", 8)},
- {TV(1125899906842624, u"ÇÇÇÇÇÇÇ♦", 8)},
- {TV(-1125899906842624, u"λλλλλλλ♥", 8)},
- {TV(2251799813685248, u"ÇÇÇÇÇÇÇ◘", 8)},
- {TV(-2251799813685248, u"λλλλλλλ•", 8)},
- {TV(4503599627370496, u"ÇÇÇÇÇÇÇ►", 8)},
- {TV(-4503599627370496, u"λλλλλλλ☼", 8)},
- {TV(9007199254740992, u"ÇÇÇÇÇÇÇ ", 8)},
- {TV(-9007199254740992, u"λλλλλλλ▼", 8)},
- {TV(18014398509481984, u"ÇÇÇÇÇÇÇ@", 8)},
- {TV(-18014398509481984, u"λλλλλλλ⁇", 8)},
- {TV(36028797018963968, u"ÇÇÇÇÇÇÇÇ☺", 9)},
- {TV(-36028797018963968, u"λλλλλλλ⌂", 8)},
- {TV(72057594037927936, u"ÇÇÇÇÇÇÇÇ☻", 9)},
- {TV(-72057594037927936, u"λλλλλλλλ☺", 9)},
- {TV(144115188075855872, u"ÇÇÇÇÇÇÇÇ♦", 9)},
- {TV(-144115188075855872, u"λλλλλλλλ♥", 9)},
- {TV(288230376151711744, u"ÇÇÇÇÇÇÇÇ◘", 9)},
- {TV(-288230376151711744, u"λλλλλλλλ•", 9)},
- {TV(576460752303423488, u"ÇÇÇÇÇÇÇÇ►", 9)},
- {TV(-576460752303423488, u"λλλλλλλλ☼", 9)},
- {TV(1152921504606846976, u"ÇÇÇÇÇÇÇÇ ", 9)},
- {TV(-1152921504606846976, u"λλλλλλλλ▼", 9)},
- {TV(2305843009213693952, u"ÇÇÇÇÇÇÇÇ@", 9)},
- {TV(-2305843009213693952, u"λλλλλλλλ⁇", 9)},
- {TV(4611686018427387904, u"ÇÇÇÇÇÇÇÇÇ☺", 10)},
- {TV(-4611686018427387904, u"λλλλλλλλ⌂", 9)},
- {TV(INT64_MIN, u"λλλλλλλλλ☺", 10)},
- {TV(INT64_MAX, u"■λλλλλλλλ☺", 10)}, /* wut */
-};
-
-TEST(writevint, test) {
- size_t i;
- uint8_t b[10], *p;
- for (i = 0; i < ARRAYLEN(kv); ++i) {
- memset(b, 0, sizeof(b));
- p = writevint(b, kv[i].x);
- __TEST_EQ(assert, kz[i].file, kz[i].line, __FUNCTION__, kz[i].icode,
- kz[i].xcode, kv[i].i, p - b);
- assertBinaryEquals$cp437(kz[i].file, kz[i].line, __FUNCTION__, kv[i].b, b,
- strlen(kv[i].b), "", false);
- }
-}
-
-TEST(readvint, test) {
- size_t i;
- uint64_t x;
- uint8_t *b, *p;
- for (i = 0; i < ARRAYLEN(kv); ++i) {
- b = gc(unbingstr(kv[i].b));
- p = readvint(b, b + strlen(kv[i].b), &x);
- __TEST_EQ(assert, kv[i].file, kv[i].line, __FUNCTION__, kv[i].icode,
- kv[i].xcode, kv[i].i, (intptr_t)(p - b));
- assertBinaryEquals$cp437(kv[i].file, kv[i].line, __FUNCTION__, kv[i].b, b,
- strlen(kv[i].b), "", false);
- }
-}
-
-TEST(writesint, test) {
- size_t i;
- uint8_t b[10], *p;
- for (i = 0; i < ARRAYLEN(kz); ++i) {
- memset(b, 0, sizeof(b));
- p = writesint(b, kz[i].x);
- __TEST_EQ(assert, kz[i].file, kz[i].line, __FUNCTION__, kz[i].icode,
- kz[i].xcode, kz[i].i, (intptr_t)(p - b));
- assertBinaryEquals$cp437(kz[i].file, kz[i].line, __FUNCTION__, kz[i].b, b,
- strlen(kz[i].b), "", false);
- }
-}
-
-TEST(readsint, test) {
- size_t i;
- int64_t x;
- uint8_t *b, *p;
- for (i = 0; i < ARRAYLEN(kz); ++i) {
- b = gc(unbingstr(kz[i].b));
- p = readsint(b, b + strlen(kz[i].b), &x);
- __TEST_EQ(assert, kz[i].file, kz[i].line, __FUNCTION__, kz[i].icode,
- kz[i].xcode, kz[i].i, (intptr_t)(p - b));
- assertBinaryEquals$cp437(kz[i].file, kz[i].line, __FUNCTION__, kz[i].b, b,
- strlen(kz[i].b), "", false);
- }
-}
diff --git a/test/libc/time/dsleep_test.c b/test/libc/time/dsleep_test.c
index f11b81e66..646b18758 100644
--- a/test/libc/time/dsleep_test.c
+++ b/test/libc/time/dsleep_test.c
@@ -30,7 +30,7 @@
TEST(fastdiv, test) {
long x = 123000000321;
EXPECT_EQ(123, div1000000000int64(x));
- EXPECT_EQ(321, mod1000000000int64(x));
+ EXPECT_EQ(321, rem1000000000int64(x));
}
TEST(dsleep, test) {
diff --git a/test/libc/time/test.mk b/test/libc/time/test.mk
index 7ea990e3c..c8164e3c0 100644
--- a/test/libc/time/test.mk
+++ b/test/libc/time/test.mk
@@ -5,13 +5,15 @@ PKGS += TEST_LIBC_TIME
TEST_LIBC_TIME_SRCS := $(wildcard test/libc/time/*.c)
TEST_LIBC_TIME_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_TIME_SRCS))
-TEST_LIBC_TIME_COMS = $(TEST_LIBC_TIME_OBJS:%.o=%.com)
TEST_LIBC_TIME_BINS = $(TEST_LIBC_TIME_COMS) $(TEST_LIBC_TIME_COMS:%=%.dbg)
TEST_LIBC_TIME_OBJS = \
$(TEST_LIBC_TIME_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_TIME_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_TIME_COMS = \
+ $(TEST_LIBC_TIME_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_TIME_TESTS = $(TEST_LIBC_TIME_SRCS_TEST:%.c=o/$(MODE)/%.com.ok)
TEST_LIBC_TIME_CHECKS = \
$(TEST_LIBC_TIME_SRCS_TEST:%.c=o/$(MODE)/%.com.runs)
diff --git a/test/libc/conv/sizemultiply_test.c b/test/libc/tinymath/logb_test.c
similarity index 71%
rename from test/libc/conv/sizemultiply_test.c
rename to test/libc/tinymath/logb_test.c
index 5d583de94..29f79df2f 100644
--- a/test/libc/conv/sizemultiply_test.c
+++ b/test/libc/tinymath/logb_test.c
@@ -17,34 +17,37 @@
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
-#include "libc/conv/conv.h"
-#include "libc/conv/sizemultiply.h"
#include "libc/limits.h"
+#include "libc/math.h"
#include "libc/testlib/testlib.h"
+#include "libc/tinymath/tinymath.h"
-size_t out;
-
-TEST(sizemultiply, testMultiplication) {
- EXPECT_TRUE(sizemultiply(&out, 7, 11));
- EXPECT_EQ(7 * 11, out);
- EXPECT_TRUE(sizemultiply(&out, 11, 7));
- EXPECT_EQ(7 * 11, out);
- EXPECT_TRUE(sizemultiply(&out, 0, 11));
- EXPECT_EQ(0 * 11, out);
- EXPECT_TRUE(sizemultiply(&out, 11, 0));
- EXPECT_EQ(0 * 11, out);
+TEST(ilogb, yolo) {
+ EXPECT_EQ(0, ilogb(1));
+ EXPECT_EQ(1, ilogb(2));
+ EXPECT_EQ(2, ilogb(4));
+ EXPECT_EQ(63, ilogb(1e19));
+ EXPECT_EQ(0, ilogbf(1));
+ EXPECT_EQ(1, ilogbf(2));
+ EXPECT_EQ(2, ilogbf(4));
+ EXPECT_EQ(63, ilogb(1e19));
+ EXPECT_EQ(0, ilogbl(1));
+ EXPECT_EQ(1, ilogbl(2));
+ EXPECT_EQ(2, ilogbl(4));
+ EXPECT_EQ(63, ilogbl(1e19));
}
-TEST(sizemultiply, testOverflow_alwaysReturnsSizeMax) {
- EXPECT_FALSE(sizemultiply(&out, 7, SIZE_MAX));
- EXPECT_EQ(SIZE_MAX, out);
- EXPECT_FALSE(sizemultiply(&out, SIZE_MAX, 7));
- EXPECT_EQ(SIZE_MAX, out);
-}
-
-TEST(sizemultiply, testOverflow_closeButNoCigar) {
- EXPECT_TRUE(sizemultiply(&out, SIZE_MAX, 1));
- EXPECT_EQ(SIZE_MAX, out);
- EXPECT_TRUE(sizemultiply(&out, 1, SIZE_MAX));
- EXPECT_EQ(SIZE_MAX, out);
+TEST(logb, yolo) {
+ EXPECT_EQ(0, (int)logb(1));
+ EXPECT_EQ(1, (int)logb(2));
+ EXPECT_EQ(2, (int)logb(4));
+ EXPECT_EQ(63, (int)logb(1e19));
+ EXPECT_EQ(0, (int)logbf(1));
+ EXPECT_EQ(1, (int)logbf(2));
+ EXPECT_EQ(2, (int)logbf(4));
+ EXPECT_EQ(63, (int)logb(1e19));
+ EXPECT_EQ(0, (int)logbl(1));
+ EXPECT_EQ(1, (int)logbl(2));
+ EXPECT_EQ(2, (int)logbl(4));
+ EXPECT_EQ(63, (int)logbl(1e19));
}
diff --git a/test/libc/tinymath/round_test.c b/test/libc/tinymath/round_test.c
index e189424d4..649bbde2b 100644
--- a/test/libc/tinymath/round_test.c
+++ b/test/libc/tinymath/round_test.c
@@ -27,15 +27,6 @@
float tinymath_roundf$k8(float);
double tinymath_round$k8(double);
-TEST(round, test) {
- EXPECT_STREQ("-3", gc(xdtoa(tinymath_round(-2.5))));
- EXPECT_STREQ("-2", gc(xdtoa(tinymath_round(-1.5))));
- EXPECT_STREQ("-1", gc(xdtoa(tinymath_round(-.5))));
- EXPECT_STREQ("1", gc(xdtoa(tinymath_round(.5))));
- EXPECT_STREQ("2", gc(xdtoa(tinymath_round(1.5))));
- EXPECT_STREQ("3", gc(xdtoa(tinymath_round(2.5))));
-}
-
TEST(round, testCornerCases) {
EXPECT_STREQ("-0", gc(xdtoa(tinymath_round(-0.0))));
EXPECT_STREQ("nan", gc(xdtoa(tinymath_round(NAN))));
@@ -44,6 +35,105 @@ TEST(round, testCornerCases) {
EXPECT_STREQ("-inf", gc(xdtoa(tinymath_round(-INFINITY))));
}
+TEST(round, test) {
+ EXPECT_STREQ("-3", gc(xdtoa(tinymath_round(-2.5))));
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_round(-1.5))));
+ EXPECT_STREQ("-1", gc(xdtoa(tinymath_round(-.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_round(-.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_round(.4))));
+ EXPECT_STREQ("1", gc(xdtoa(tinymath_round(.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_round(1.5))));
+ EXPECT_STREQ("3", gc(xdtoa(tinymath_round(2.5))));
+}
+
+TEST(roundf, test) {
+ EXPECT_STREQ("-3", gc(xdtoa(tinymath_roundf(-2.5))));
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_roundf(-1.5))));
+ EXPECT_STREQ("-1", gc(xdtoa(tinymath_roundf(-.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_roundf(-.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_roundf(.4))));
+ EXPECT_STREQ("1", gc(xdtoa(tinymath_roundf(.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_roundf(1.5))));
+ EXPECT_STREQ("3", gc(xdtoa(tinymath_roundf(2.5))));
+}
+
+TEST(roundl, test) {
+ EXPECT_STREQ("-3", gc(xdtoa(tinymath_roundl(-2.5))));
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_roundl(-1.5))));
+ EXPECT_STREQ("-1", gc(xdtoa(tinymath_roundl(-.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_roundl(-.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_roundl(.4))));
+ EXPECT_STREQ("1", gc(xdtoa(tinymath_roundl(.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_roundl(1.5))));
+ EXPECT_STREQ("3", gc(xdtoa(tinymath_roundl(2.5))));
+}
+
+TEST(nearbyint, test) {
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_nearbyint(-2.5))));
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_nearbyint(-1.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_nearbyint(-.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_nearbyint(-.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_nearbyint(.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_nearbyint(.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_nearbyint(1.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_nearbyint(2.5))));
+}
+
+TEST(nearbyintf, test) {
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_nearbyintf(-2.5))));
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_nearbyintf(-1.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_nearbyintf(-.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_nearbyintf(-.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_nearbyintf(.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_nearbyintf(.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_nearbyintf(1.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_nearbyintf(2.5))));
+}
+
+TEST(nearbyintl, test) {
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_nearbyintl(-2.5))));
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_nearbyintl(-1.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_nearbyintl(-.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_nearbyintl(-.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_nearbyintl(.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_nearbyintl(.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_nearbyintl(1.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_nearbyintl(2.5))));
+}
+
+TEST(rint, test) {
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_rint(-2.5))));
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_rint(-1.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_rint(-.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_rint(-.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_rint(.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_rint(.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_rint(1.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_rint(2.5))));
+}
+
+TEST(rintf, test) {
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_rintf(-2.5))));
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_rintf(-1.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_rintf(-.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_rintf(-.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_rintf(.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_rintf(.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_rintf(1.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_rintf(2.5))));
+}
+
+TEST(rintl, test) {
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_rintl(-2.5))));
+ EXPECT_STREQ("-2", gc(xdtoa(tinymath_rintl(-1.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_rintl(-.5))));
+ EXPECT_STREQ("-0", gc(xdtoa(tinymath_rintl(-.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_rintl(.4))));
+ EXPECT_STREQ("0", gc(xdtoa(tinymath_rintl(.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_rintl(1.5))));
+ EXPECT_STREQ("2", gc(xdtoa(tinymath_rintl(2.5))));
+}
+
TEST(lround, test) {
EXPECT_EQ(-3, tinymath_lround(-2.5));
EXPECT_EQ(-2, tinymath_lround(-1.5));
@@ -54,15 +144,6 @@ TEST(lround, test) {
EXPECT_EQ(3, tinymath_lround(2.5));
}
-TEST(roundf, test) {
- EXPECT_STREQ("-3", gc(xdtoa(tinymath_roundf(-2.5))));
- EXPECT_STREQ("-2", gc(xdtoa(tinymath_roundf(-1.5))));
- EXPECT_STREQ("-1", gc(xdtoa(tinymath_roundf(-.5))));
- EXPECT_STREQ("1", gc(xdtoa(tinymath_roundf(.5))));
- EXPECT_STREQ("2", gc(xdtoa(tinymath_roundf(1.5))));
- EXPECT_STREQ("3", gc(xdtoa(tinymath_roundf(2.5))));
-}
-
TEST(roundf, testCornerCases) {
EXPECT_STREQ("-0", gc(xdtoa(tinymath_roundf(-0.0))));
EXPECT_STREQ("nan", gc(xdtoa(tinymath_roundf(NAN))));
diff --git a/test/libc/tinymath/test.mk b/test/libc/tinymath/test.mk
index 1f10bf02a..3cab2a522 100644
--- a/test/libc/tinymath/test.mk
+++ b/test/libc/tinymath/test.mk
@@ -5,17 +5,20 @@ PKGS += TEST_LIBC_TINYMATH
TEST_LIBC_TINYMATH_SRCS := $(wildcard test/libc/tinymath/*.c)
TEST_LIBC_TINYMATH_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_TINYMATH_SRCS))
-TEST_LIBC_TINYMATH_COMS = $(TEST_LIBC_TINYMATH_OBJS:%.o=%.com)
TEST_LIBC_TINYMATH_OBJS = \
$(TEST_LIBC_TINYMATH_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_TINYMATH_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_TINYMATH_COMS = \
+ $(TEST_LIBC_TINYMATH_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_TINYMATH_BINS = \
$(TEST_LIBC_TINYMATH_COMS) \
$(TEST_LIBC_TINYMATH_COMS:%=%.dbg)
-TEST_LIBC_TINYMATH_TESTS = $(TEST_LIBC_TINYMATH_SRCS_TEST:%.c=o/$(MODE)/%.com.ok)
+TEST_LIBC_TINYMATH_TESTS = \
+ $(TEST_LIBC_TINYMATH_SRCS_TEST:%.c=o/$(MODE)/%.com.ok)
TEST_LIBC_TINYMATH_CHECKS = \
$(TEST_LIBC_TINYMATH_SRCS_TEST:%.c=o/$(MODE)/%.com.runs)
diff --git a/test/libc/unicode/test.mk b/test/libc/unicode/test.mk
index b75762dd0..305a28832 100644
--- a/test/libc/unicode/test.mk
+++ b/test/libc/unicode/test.mk
@@ -5,12 +5,14 @@ PKGS += TEST_LIBC_UNICODE
TEST_LIBC_UNICODE_SRCS := $(wildcard test/libc/unicode/*.c)
TEST_LIBC_UNICODE_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_UNICODE_SRCS))
-TEST_LIBC_UNICODE_COMS = $(TEST_LIBC_UNICODE_OBJS:%.o=%.com)
TEST_LIBC_UNICODE_OBJS = \
$(TEST_LIBC_UNICODE_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_UNICODE_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_UNICODE_COMS = \
+ $(TEST_LIBC_UNICODE_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_UNICODE_BINS = \
$(TEST_LIBC_UNICODE_COMS) \
$(TEST_LIBC_UNICODE_COMS:%=%.dbg)
diff --git a/test/libc/unicode/wcwidth_test.c b/test/libc/unicode/wcwidth_test.c
index b644c4fa9..c26fc8392 100644
--- a/test/libc/unicode/wcwidth_test.c
+++ b/test/libc/unicode/wcwidth_test.c
@@ -61,21 +61,6 @@ TEST(wcwidth, testCjkWidesAndCombiningLowLines_widthIsNotLength) {
/*────────────────────────────────────────────────────┴─*/
}
-TEST(strwidth, testEmoji_cosmoHelpsYouBuildInclusiveProductsEasily) {
- /* ┌─If this line is solid your terminal
- │ is respectful and inclusive towards
- │ our friends w/ rich and interesting
- │ backgrounds that aren't Anglo-Saxon
- │
- │ ┌─This line being solid, means your
- │ │ terminal needs a patch to address
- │ │ issues concerning racism
- │ │
- ──────────────────────────────────────┼─┼─*/
- EXPECT_EQ(02, wcswidth(/**/ L"👦🏿" /*- │ - */));
- /*────────────────────────────────────┼─┼─*/
-}
-
TEST(strwidth, tab) {
EXPECT_EQ(32, strwidth("mov 0x0(%rip),%rcx \t"));
}
diff --git a/test/libc/x/test.mk b/test/libc/x/test.mk
index c0415657e..0d4e70af8 100644
--- a/test/libc/x/test.mk
+++ b/test/libc/x/test.mk
@@ -5,12 +5,14 @@ PKGS += TEST_LIBC_X
TEST_LIBC_X_SRCS := $(wildcard test/libc/x/*.c)
TEST_LIBC_X_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_X_SRCS))
-TEST_LIBC_X_COMS = $(TEST_LIBC_X_OBJS:%.o=%.com)
TEST_LIBC_X_OBJS = \
$(TEST_LIBC_X_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_X_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_X_COMS = \
+ $(TEST_LIBC_X_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_X_BINS = \
$(TEST_LIBC_X_COMS) \
$(TEST_LIBC_X_COMS:%=%.dbg)
@@ -29,7 +31,8 @@ TEST_LIBC_X_DIRECTDEPS = \
LIBC_RUNTIME \
LIBC_X \
LIBC_STUBS \
- LIBC_TESTLIB
+ LIBC_TESTLIB \
+ THIRD_PARTY_DTOA
TEST_LIBC_X_DEPS := \
$(call uniq,$(foreach x,$(TEST_LIBC_X_DIRECTDEPS),$($(x))))
diff --git a/test/libc/xed/test.mk b/test/libc/xed/test.mk
index f58cc65ff..b1c9b78a6 100644
--- a/test/libc/xed/test.mk
+++ b/test/libc/xed/test.mk
@@ -51,12 +51,14 @@ $(TEST_LIBC_XED_TESTLIB_A): \
PKGS += TEST_LIBC_XED
TEST_LIBC_XED_SRCS = $(filter %_test.c,$(TEST_LIBC_XED_FILES))
-TEST_LIBC_XED_COMS = $(TEST_LIBC_XED_OBJS:%.o=%.com)
TEST_LIBC_XED_OBJS = \
$(TEST_LIBC_XED_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_LIBC_XED_SRCS:%.c=o/$(MODE)/%.o)
+TEST_LIBC_XED_COMS = \
+ $(TEST_LIBC_XED_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_LIBC_XED_BINS = \
$(TEST_LIBC_XED_COMS) \
$(TEST_LIBC_XED_COMS:%=%.dbg)
diff --git a/test/libc/xed/x86ild_lib.c b/test/libc/xed/x86ild_lib.c
index b6252b50f..022175538 100644
--- a/test/libc/xed/x86ild_lib.c
+++ b/test/libc/xed/x86ild_lib.c
@@ -41,7 +41,7 @@ testonly int ild(const char16_t *codez) {
error = xed_instruction_length_decode(
xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_LONG_64),
gc(unbingx86op(codez)), strlen16(codez) + 16);
- return error == XED_ERROR_NONE ? xedd.decoded_length : -error;
+ return error == XED_ERROR_NONE ? xedd.length : -error;
}
/**
@@ -53,7 +53,7 @@ testonly int ildreal(const char16_t *codez) {
error = xed_instruction_length_decode(
xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_REAL),
gc(unbingx86op(codez)), strlen16(codez) + 16);
- return error == XED_ERROR_NONE ? xedd.decoded_length : -error;
+ return error == XED_ERROR_NONE ? xedd.length : -error;
}
/**
@@ -65,5 +65,5 @@ testonly int ildlegacy(const char16_t *codez) {
error = xed_instruction_length_decode(
xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_LEGACY_32),
gc(unbingx86op(codez)), strlen16(codez) + 16);
- return error == XED_ERROR_NONE ? xedd.decoded_length : -error;
+ return error == XED_ERROR_NONE ? xedd.length : -error;
}
diff --git a/test/libc/xed/x86ild_popular_i86_test.c b/test/libc/xed/x86ild_popular_i86_test.c
index fc86101ce..b271b356a 100644
--- a/test/libc/xed/x86ild_popular_i86_test.c
+++ b/test/libc/xed/x86ild_popular_i86_test.c
@@ -1416,7 +1416,7 @@ TEST(x86ild, test_415C) {
ISA_SET: I86
SHORT: pop r12
*/
- EXPECT_EQ(2, ild(u"A╲"));
+ EXPECT_EQ(2, ild(u"A\\"));
}
TEST(x86ild, test_488B04C500000000) {
diff --git a/test/libc/xed/x86ild_test.c b/test/libc/xed/x86ild_test.c
index c24635f32..c58ee2c86 100644
--- a/test/libc/xed/x86ild_test.c
+++ b/test/libc/xed/x86ild_test.c
@@ -18,6 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/runtime/gc.h"
+#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
#include "test/libc/xed/lib.h"
#include "third_party/xed/x86.h"
@@ -94,6 +95,24 @@ TEST(x86ild, testAmd3dnow) {
0, xed_instruction_length_decode(
xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_LEGACY_32),
gc(unbingx86op(u"☼☼╚ª")), 4));
- ASSERT_EQ(true, xedd.operands.amd3dnow);
- ASSERT_EQ(0xa6, xedd.operands.nominal_opcode);
+ ASSERT_EQ(true, (int)xedd.op.amd3dnow);
+ ASSERT_EQ(0xa6, xedd.op.opcode);
+}
+
+TEST(x86ild, testPopToMemory) {
+ ASSERT_EQ(3, ild(u"ÅF◘")); /* 8f 46 08 */
+}
+
+TEST(x86ild, testFinit) {
+ ASSERT_EQ(1, ild(u"¢█π")); /* 9B DB E3: fwait */
+ ASSERT_EQ(2, ild(u"█π")); /* DB E3: fninit */
+}
+
+BENCH(x86ild, bench) {
+ uint8_t *x = gc(unbingx86op(u"b▓m◘Pî± ►"));
+ struct XedDecodedInst xedd;
+ EZBENCH2("x86ild", donothing,
+ xed_instruction_length_decode(
+ xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_LONG_64),
+ x, XED_MAX_INSTRUCTION_BYTES));
}
diff --git a/test/net/http/test.mk b/test/net/http/test.mk
index 17a777297..ba9c90653 100644
--- a/test/net/http/test.mk
+++ b/test/net/http/test.mk
@@ -5,13 +5,15 @@ PKGS += TEST_NET_HTTP
TEST_NET_HTTP_SRCS := $(wildcard test/net/http/*.c)
TEST_NET_HTTP_SRCS_TEST = $(filter %_test.c,$(TEST_NET_HTTP_SRCS))
-TEST_NET_HTTP_COMS = $(TEST_NET_HTTP_OBJS:%.o=%.com)
TEST_NET_HTTP_BINS = $(TEST_NET_HTTP_COMS) $(TEST_NET_HTTP_COMS:%=%.dbg)
TEST_NET_HTTP_OBJS = \
$(TEST_NET_HTTP_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_NET_HTTP_SRCS:%.c=o/$(MODE)/%.o)
+TEST_NET_HTTP_COMS = \
+ $(TEST_NET_HTTP_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_NET_HTTP_TESTS = \
$(TEST_NET_HTTP_SRCS_TEST:%.c=o/$(MODE)/%.com.ok)
diff --git a/test/net/http/uriparse_test.c b/test/net/http/uriparse_test.c
index 77370020e..8650f5eaf 100644
--- a/test/net/http/uriparse_test.c
+++ b/test/net/http/uriparse_test.c
@@ -18,6 +18,7 @@
│ 02110-1301 USA │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/bits/bits.h"
+#include "libc/bits/initializer.h"
#include "libc/errno.h"
#include "libc/log/log.h"
#include "libc/macros.h"
@@ -69,7 +70,7 @@ static struct UriMem {
struct UriKeyval params[4], queries[4];
} urimem_;
-INITIALIZER(100, _init_uri, {
+static textstartup void init() {
uri.segs.n = ARRAYLEN(urimem_.segs);
uri.segs.p = urimem_.segs;
uri.params.n = ARRAYLEN(urimem_.params);
@@ -78,7 +79,9 @@ INITIALIZER(100, _init_uri, {
uri.queries.p = urimem_.queries;
uri.paramsegs.n = ARRAYLEN(urimem_.paramsegs);
uri.paramsegs.p = urimem_.paramsegs;
-})
+}
+
+const void *const g_name_ctor[] initarray = {init};
TEST(uriparse, sipPstnUri) {
EXPECT_NE(-1, URIPARSE("sip:+12125650666"));
diff --git a/test/tool/build/lib/alu_test.c b/test/tool/build/lib/alu_test.c
new file mode 100644
index 000000000..36fbd73a5
--- /dev/null
+++ b/test/tool/build/lib/alu_test.c
@@ -0,0 +1,106 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/assert.h"
+#include "libc/limits.h"
+#include "libc/runtime/runtime.h"
+#include "libc/stdio/stdio.h"
+#include "libc/testlib/testlib.h"
+#include "test/tool/build/lib/optest.h"
+#include "tool/build/lib/alu.h"
+#include "tool/build/lib/flags.h"
+
+#define NATIVE_ALU2(MODE, INSTRUCTION) \
+ asm("pushf\n\t" \
+ "andl\t%3,(%%rsp)\n\t" \
+ "orl\t%4,(%%rsp)\n\t" \
+ "popf\n\t" INSTRUCTION "\t%" MODE "2,%" MODE "0\n\t" \
+ "pushf\n\t" \
+ "pop\t%q1" \
+ : "+r"(x), "=rm"(*f) \
+ : "r"(y), "i"(~FMASK), "r"(*f & FMASK) \
+ : "cc")
+
+#define NATIVE_ALU2_ANYBITS(INSTRUCTION, MUTATING) \
+ switch (w) { \
+ case 0: \
+ NATIVE_ALU2("b", INSTRUCTION); \
+ if (MUTATING) x &= 0xff; \
+ return x; \
+ case 1: \
+ NATIVE_ALU2("w", INSTRUCTION); \
+ if (MUTATING) x &= 0xffff; \
+ return x; \
+ case 2: \
+ NATIVE_ALU2("k", INSTRUCTION); \
+ return x; \
+ case 3: \
+ NATIVE_ALU2("q", INSTRUCTION); \
+ return x; \
+ default: \
+ abort(); \
+ }
+
+int64_t RunGolden(char w, int h, uint64_t x, uint64_t y, uint32_t *f) {
+ bool rw;
+ rw = !(h & ALU_TEST);
+ switch (h & 7) {
+ case ALU_OR:
+ NATIVE_ALU2_ANYBITS("or", rw);
+ case ALU_AND:
+ if (rw) {
+ NATIVE_ALU2_ANYBITS("and", rw);
+ } else {
+ NATIVE_ALU2_ANYBITS("test", rw);
+ }
+ case ALU_XOR:
+ NATIVE_ALU2_ANYBITS("xor", rw);
+ case ALU_SBB:
+ NATIVE_ALU2_ANYBITS("sbb", rw);
+ case ALU_CMP:
+ rw = false;
+ NATIVE_ALU2_ANYBITS("cmp", rw);
+ case ALU_SUB:
+ NATIVE_ALU2_ANYBITS("sub", rw);
+ case ALU_ADC:
+ NATIVE_ALU2_ANYBITS("adc", rw);
+ case ALU_ADD:
+ NATIVE_ALU2_ANYBITS("add", rw);
+ default:
+ abort();
+ }
+}
+
+const uint8_t kAluOps[] = {
+ ALU_ADD, ALU_OR, ALU_ADC, ALU_SBB, ALU_AND, ALU_SUB, ALU_XOR, ALU_CMP, ALU_AND | ALU_TEST,
+};
+
+const char *const kAluNames[] = {
+ [ALU_ADD] = "add", [ALU_OR] = "or", [ALU_ADC] = "adc",
+ [ALU_SBB] = "sbb", [ALU_AND] = "and", [ALU_SUB] = "sub",
+ [ALU_XOR] = "xor", [ALU_CMP] = "cmp", [ALU_AND | ALU_TEST] = "test",
+};
+
+int64_t RunOpTest(char w, int h, uint64_t x, uint64_t y, uint32_t *f) {
+ return Alu(w, h, x, y, f);
+}
+
+TEST(alu, test) {
+ RunOpTests(kAluOps, ARRAYLEN(kAluOps), kAluNames);
+}
diff --git a/test/tool/build/lib/bitscan_test.c b/test/tool/build/lib/bitscan_test.c
new file mode 100644
index 000000000..babab3dd9
--- /dev/null
+++ b/test/tool/build/lib/bitscan_test.c
@@ -0,0 +1,112 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/macros.h"
+#include "libc/testlib/testlib.h"
+#include "test/tool/build/lib/numbers.h"
+#include "tool/build/lib/bitscan.h"
+#include "tool/build/lib/flags.h"
+
+#define OSZ 00000000040
+#define REXW 00000004000
+
+struct Machine m[1];
+struct XedDecodedInst xedd[1];
+
+void SetUp(void) {
+ m->xedd = xedd;
+}
+
+TEST(bsr64, test) {
+ bool zf;
+ uint64_t i, w, x, a, b;
+ m->xedd->op.rde = REXW;
+ for (i = 0; i < ARRAYLEN(kNumbers); ++i) {
+ x = kNumbers[i];
+ a = AluBsr(m, 0, x);
+ asm("bsrq\t%2,%0" : "=r"(b), "=@ccz"(zf) : "r"(x));
+ ASSERT_EQ(zf, GetFlag(m->flags, FLAGS_ZF));
+ if (!zf) ASSERT_EQ(a, b);
+ }
+}
+
+TEST(bsr32, test) {
+ bool zf;
+ uint32_t i, w, x, a, b;
+ m->xedd->op.rde = 0;
+ for (i = 0; i < ARRAYLEN(kNumbers); ++i) {
+ x = kNumbers[i];
+ a = AluBsr(m, 0, x);
+ asm("bsrl\t%2,%0" : "=r"(b), "=@ccz"(zf) : "r"(x));
+ ASSERT_EQ(zf, GetFlag(m->flags, FLAGS_ZF));
+ if (!zf) ASSERT_EQ(a, b);
+ }
+}
+
+TEST(bsr16, test) {
+ bool zf;
+ uint16_t i, w, x, a, b;
+ m->xedd->op.rde = OSZ;
+ for (i = 0; i < ARRAYLEN(kNumbers); ++i) {
+ x = kNumbers[i];
+ a = AluBsr(m, 0, x);
+ asm("bsrw\t%2,%0" : "=r"(b), "=@ccz"(zf) : "r"(x));
+ ASSERT_EQ(zf, GetFlag(m->flags, FLAGS_ZF));
+ if (!zf) ASSERT_EQ(a, b);
+ }
+}
+
+TEST(bsf64, test) {
+ bool zf;
+ uint64_t i, w, x, a, b;
+ m->xedd->op.rde = REXW;
+ for (i = 0; i < ARRAYLEN(kNumbers); ++i) {
+ x = kNumbers[i];
+ a = AluBsf(m, 0, x);
+ asm("bsfq\t%2,%0" : "=r"(b), "=@ccz"(zf) : "r"(x));
+ ASSERT_EQ(zf, GetFlag(m->flags, FLAGS_ZF));
+ if (!zf) ASSERT_EQ(a, b);
+ }
+}
+
+TEST(bsf32, test) {
+ bool zf;
+ uint32_t i, w, x, a, b;
+ m->xedd->op.rde = 0;
+ for (i = 0; i < ARRAYLEN(kNumbers); ++i) {
+ x = kNumbers[i];
+ a = AluBsf(m, 0, x);
+ asm("bsfl\t%2,%0" : "=r"(b), "=@ccz"(zf) : "r"(x));
+ ASSERT_EQ(zf, GetFlag(m->flags, FLAGS_ZF));
+ if (!zf) ASSERT_EQ(a, b);
+ }
+}
+
+TEST(bsf16, test) {
+ bool zf;
+ uint16_t i, w, x, a, b;
+ m->xedd->op.rde = OSZ;
+ for (i = 0; i < ARRAYLEN(kNumbers); ++i) {
+ x = kNumbers[i];
+ a = AluBsf(m, 0, x);
+ asm("bsfw\t%2,%0" : "=r"(b), "=@ccz"(zf) : "r"(x));
+ ASSERT_EQ(zf, GetFlag(m->flags, FLAGS_ZF));
+ if (!zf) ASSERT_EQ(a, b, "%#lx", x);
+ }
+}
diff --git a/test/tool/build/lib/bsu_test.c b/test/tool/build/lib/bsu_test.c
new file mode 100644
index 000000000..75245e53b
--- /dev/null
+++ b/test/tool/build/lib/bsu_test.c
@@ -0,0 +1,225 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/macros.h"
+#include "libc/runtime/runtime.h"
+#include "libc/stdio/stdio.h"
+#include "libc/testlib/testlib.h"
+#include "test/tool/build/lib/numbers.h"
+#include "test/tool/build/lib/optest.h"
+#include "tool/build/lib/alu.h"
+#include "tool/build/lib/flags.h"
+#include "tool/build/lib/machine.h"
+
+#define NATIVE_ALU2(MODE, INSTRUCTION) \
+ asm("pushf\n\t" \
+ "andl\t%3,(%%rsp)\n\t" \
+ "orl\t%4,(%%rsp)\n\t" \
+ "popf\n\t" INSTRUCTION "\t%b2,%" MODE "0\n\t" \
+ "pushf\n\t" \
+ "pop\t%q1" \
+ : "+r"(x), "=r"(*f) \
+ : "c"(y), "i"(~FMASK), "r"(*f & FMASK) \
+ : "cc")
+
+#define NATIVE_ALU2_ANYBITS(INSTRUCTION, MUTATING) \
+ switch (w) { \
+ case 0: \
+ NATIVE_ALU2("b", INSTRUCTION); \
+ if (MUTATING) x &= 0xff; \
+ return x; \
+ case 1: \
+ NATIVE_ALU2("w", INSTRUCTION); \
+ if (MUTATING) x &= 0xffff; \
+ return x; \
+ case 2: \
+ NATIVE_ALU2("k", INSTRUCTION); \
+ return x; \
+ case 3: \
+ NATIVE_ALU2("q", INSTRUCTION); \
+ return x; \
+ default: \
+ abort(); \
+ }
+
+int64_t RunGolden(char w, int h, uint64_t x, uint64_t y, uint32_t *f) {
+ switch (h & 7) {
+ case BSU_ROR:
+ NATIVE_ALU2_ANYBITS("ror", true);
+ case BSU_SHL:
+ NATIVE_ALU2_ANYBITS("shl", true);
+ case BSU_SAL:
+ NATIVE_ALU2_ANYBITS("sal", true);
+ case BSU_RCR:
+ NATIVE_ALU2_ANYBITS("rcr", true);
+ case BSU_SAR:
+ NATIVE_ALU2_ANYBITS("sar", true);
+ case BSU_SHR:
+ NATIVE_ALU2_ANYBITS("shr", true);
+ case BSU_RCL:
+ NATIVE_ALU2_ANYBITS("rcl", true);
+ case BSU_ROL:
+ NATIVE_ALU2_ANYBITS("rol", true);
+ default:
+ abort();
+ }
+}
+
+const uint8_t kBsuOps[] = {
+ BSU_SHR, BSU_SHL, BSU_ROL, BSU_ROR, BSU_RCL,
+ BSU_RCR, BSU_SHL, BSU_SAL, BSU_SAR,
+};
+
+const char *const kBsuNames[] = {
+ [BSU_ROL] = "rol", [BSU_ROR] = "ror", [BSU_RCL] = "rcl", [BSU_RCR] = "rcr",
+ [BSU_SHL] = "shl", [BSU_SHR] = "shr", [BSU_SAL] = "sal", [BSU_SAR] = "sar",
+};
+
+int64_t RunOpTest(char w, int h, uint64_t x, uint64_t y, uint32_t *f) {
+ return Bsu(w, h, x, y, f);
+}
+
+void FixupUndefOpTestFlags(char w, int h, uint64_t x, uint64_t y,
+ uint32_t goldenflags, uint32_t *flags) {
+ y &= w == 3 ? 0x3F : 0x1F;
+ if (y != 0 && y != 1) {
+ *flags = SetFlag(*flags, FLAGS_OF, GetFlag(goldenflags, FLAGS_OF));
+ }
+}
+
+TEST(bsu, test) {
+ RunOpTests(kBsuOps, ARRAYLEN(kBsuOps), kBsuNames);
+}
+
+TEST(shrd32, smoke) {
+ int i, j, k;
+ uint8_t b, cf, of;
+ uint32_t x, y, z1, f, z2, unflag;
+ for (i = 0; i < ARRAYLEN(kNumbers); ++i) {
+ x = kNumbers[i];
+ for (j = 0; j < ARRAYLEN(kNumbers); ++j) {
+ y = kNumbers[j];
+ for (k = 0; k < ARRAYLEN(kNumbers); ++k) {
+ f = 0;
+ cf = 0;
+ of = 0;
+ b = kNumbers[k];
+ z1 = BsuDoubleShift(2, x, y, b, true, &f);
+ z2 = x;
+ asm("xor\t%1,%1\n\t"
+ "shrd\t%5,%4,%0"
+ : "+rm"(z2), "=&r"(unflag), "=@ccc"(cf), "=@cco"(of)
+ : "r"(y), "c"(b)
+ : "cc");
+ ASSERT_EQ(z2, z1);
+ ASSERT_EQ(cf, GetFlag(f, FLAGS_CF));
+ if (b <= 1) ASSERT_EQ(of, GetFlag(f, FLAGS_OF));
+ }
+ }
+ }
+}
+
+TEST(shld16, smoke) {
+ uint32_t f;
+ int i, j, k;
+ uint8_t b, cf, of;
+ uint16_t x, y, z1, z2, unflag;
+ for (i = 0; i < ARRAYLEN(kNumbers); ++i) {
+ x = kNumbers[i];
+ for (j = 0; j < ARRAYLEN(kNumbers); ++j) {
+ y = kNumbers[j];
+ for (k = 0; k < ARRAYLEN(kNumbers); ++k) {
+ f = 0;
+ cf = 0;
+ of = 0;
+ b = kNumbers[k];
+ if (b > 16) continue;
+ z1 = BsuDoubleShift(1, x, y, b, false, &f);
+ z2 = x;
+ asm("xor\t%1,%1\n\t"
+ "shldw\t%5,%4,%0"
+ : "+rm"(z2), "=&r"(unflag), "=@ccc"(cf), "=@cco"(of)
+ : "r"(y), "c"(b)
+ : "cc");
+ ASSERT_EQ(z2, z1);
+ ASSERT_EQ(cf, GetFlag(f, FLAGS_CF));
+ if (b <= 1) ASSERT_EQ(of, GetFlag(f, FLAGS_OF));
+ }
+ }
+ }
+}
+
+TEST(shld32, smoke) {
+ int i, j, k;
+ uint8_t b, cf, of;
+ uint32_t x, y, z1, f, z2, unflag;
+ for (i = 0; i < ARRAYLEN(kNumbers); ++i) {
+ x = kNumbers[i];
+ for (j = 0; j < ARRAYLEN(kNumbers); ++j) {
+ y = kNumbers[j];
+ for (k = 0; k < ARRAYLEN(kNumbers); ++k) {
+ f = 0;
+ cf = 0;
+ of = 0;
+ b = kNumbers[k];
+ if (b > 32) continue;
+ z1 = BsuDoubleShift(2, x, y, b, false, &f);
+ z2 = x;
+ asm("xor\t%1,%1\n\t"
+ "shld\t%5,%4,%0"
+ : "+rm"(z2), "=&r"(unflag), "=@ccc"(cf), "=@cco"(of)
+ : "r"(y), "c"(b)
+ : "cc");
+ ASSERT_EQ(z2, z1);
+ ASSERT_EQ(cf, GetFlag(f, FLAGS_CF));
+ if (b <= 1) ASSERT_EQ(of, GetFlag(f, FLAGS_OF));
+ }
+ }
+ }
+}
+
+TEST(shld64, smoke) {
+ uint32_t f;
+ int i, j, k;
+ uint8_t b, cf, of;
+ uint64_t x, y, z1, z2, unflag;
+ for (i = 0; i < ARRAYLEN(kNumbers); ++i) {
+ x = kNumbers[i];
+ for (j = 0; j < ARRAYLEN(kNumbers); ++j) {
+ y = kNumbers[j];
+ for (k = 0; k < ARRAYLEN(kNumbers); ++k) {
+ f = 0;
+ cf = 0;
+ of = 0;
+ b = kNumbers[k];
+ if (b > 64) continue;
+ z1 = BsuDoubleShift(3, x, y, b, false, &f);
+ z2 = x;
+ asm("xor\t%1,%1\n\t"
+ "shldq\t%5,%4,%0"
+ : "+rm"(z2), "=&r"(unflag), "=@ccc"(cf), "=@cco"(of)
+ : "r"(y), "c"(b)
+ : "cc");
+ ASSERT_EQ(z2, z1);
+ ASSERT_EQ(cf, GetFlag(f, FLAGS_CF));
+ if (b <= 1) ASSERT_EQ(of, GetFlag(f, FLAGS_OF));
+ }
+ }
+ }
+}
diff --git a/test/tool/build/lib/disinst_test.c b/test/tool/build/lib/disinst_test.c
new file mode 100644
index 000000000..d1e647ff9
--- /dev/null
+++ b/test/tool/build/lib/disinst_test.c
@@ -0,0 +1,88 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/testlib/testlib.h"
+#include "tool/build/lib/dis.h"
+
+char b1[64];
+char b2[64];
+struct Dis d[1];
+struct DisBuilder b = {d, d->xedd, 0};
+
+TEST(DisInst, testInt3) {
+ uint8_t op[] = {0xcc};
+ xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, op, sizeof(op)));
+ DisInst(b, b1, DisSpec(d->xedd, b2));
+ EXPECT_STREQ("int3 ", b1);
+}
+
+TEST(DisInst, testImmMem_needsSuffix) {
+ uint8_t op[] = {0x80, 0x3c, 0x07, 0x00};
+ xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, op, sizeof(op)));
+ DisInst(b, b1, DisSpec(d->xedd, b2));
+ EXPECT_STREQ("cmpb $0,(%rdi,%rax)", b1);
+}
+
+TEST(DisInst, testImmReg_doesntNeedSuffix) {
+ uint8_t op[] = {0xb8, 0x08, 0x70, 0x40, 0x00};
+ xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, op, sizeof(op)));
+ DisInst(b, b1, DisSpec(d->xedd, b2));
+ EXPECT_STREQ("mov $0x407008,%eax", b1);
+}
+
+TEST(DisInst, testPuttingOnTheRiz) {
+ static uint8_t ops[][15] = {
+ {0x8d, 0b00110100, 0b00100110},
+ {0x67, 0x8d, 0b00110100, 0b11100110},
+ {0x67, 0x8d, 0b10110100, 0b11100101, 0, 0, 0, 0},
+ {0x8d, 0b00110100, 0b11100101, 0x37, 0x13, 0x03, 0},
+ {0x8d, 0b10110100, 0b11100101, 0, 0, 0, 0},
+ };
+ xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, ops[0], sizeof(ops[0])));
+ DisInst(b, b1, DisSpec(d->xedd, b2));
+ EXPECT_STREQ("lea (%rsi),%esi", b1);
+ xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, ops[1], sizeof(ops[1])));
+ DisInst(b, b1, DisSpec(d->xedd, b2));
+ EXPECT_STREQ("lea (%esi,%eiz,8),%esi", b1);
+ xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, ops[2], sizeof(ops[2])));
+ DisInst(b, b1, DisSpec(d->xedd, b2));
+ EXPECT_STREQ("lea 0(%ebp,%eiz,8),%esi", b1);
+ xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, ops[3], sizeof(ops[3])));
+ DisInst(b, b1, DisSpec(d->xedd, b2));
+ EXPECT_STREQ("lea 0x31337,%esi", b1);
+ xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, ops[4], sizeof(ops[4])));
+ DisInst(b, b1, DisSpec(d->xedd, b2));
+ EXPECT_STREQ("lea 0(%rbp,%riz,8),%esi", b1);
+}
+
+TEST(DisInst, testSibIndexOnly) {
+ uint8_t op[] = {76, 141, 4, 141, 0, 0, 0, 0}; /* lea 0x0(,%rcx,4),%r8 */
+ xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, op, sizeof(op)));
+ DisInst(b, b1, DisSpec(d->xedd, b2));
+ EXPECT_STREQ("lea 0(,%rcx,4),%r8", b1);
+}
diff --git a/test/tool/build/lib/divmul_test.c b/test/tool/build/lib/divmul_test.c
new file mode 100644
index 000000000..f1831b66f
--- /dev/null
+++ b/test/tool/build/lib/divmul_test.c
@@ -0,0 +1,606 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/bits/progn.h"
+#include "libc/calls/struct/sigaction.h"
+#include "libc/log/check.h"
+#include "libc/runtime/gc.h"
+#include "libc/sysv/consts/sa.h"
+#include "libc/sysv/consts/sig.h"
+#include "libc/testlib/ezbench.h"
+#include "libc/testlib/testlib.h"
+#include "libc/x/x.h"
+#include "tool/build/lib/divmul.h"
+#include "tool/build/lib/endian.h"
+#include "tool/build/lib/flags.h"
+
+#define CX 1
+#define OSZ 00000000040
+#define REXW 00000004000
+#define RM(x) (0000000700 & ((x) << 006))
+#define MOD(x) (0060000000 & ((x) << 026))
+
+jmp_buf sigfpejmp;
+struct Machine m[1];
+struct sigaction oldsigfpe[1];
+struct XedDecodedInst xedd[1];
+
+void OnSigFpe(void) {
+ /* ProTip: gdb -ex 'handle SIGFPE nostop noprint pass' */
+ longjmp(sigfpejmp, 1);
+}
+
+void SetUp(void) {
+ m->xedd = xedd;
+ InitMachine(m);
+ CHECK_NE(-1, xsigaction(SIGFPE, OnSigFpe, SA_NODEFER, 0, oldsigfpe));
+}
+
+void TearDown(void) {
+ m->xedd = xedd;
+ CHECK_NE(-1, sigaction(SIGFPE, oldsigfpe, NULL));
+}
+
+TEST(imul8, test) {
+ static const uint8_t A[] = {0x00, 0x01, 0x80, 0x7F, 0x81, 0x7E, 0xFF, 0xBF};
+ int i, j;
+ int16_t ax;
+ bool cf, of;
+ m->xedd->op.rde = MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ Write8(m->ax, A[i]);
+ Write8(m->cx, A[j]);
+ OpMulAxAlEbSigned(m);
+ asm volatile("imulb\t%3"
+ : "=a"(ax), "=@ccc"(cf), "=@cco"(of)
+ : "q"(A[j]), "0"(A[i])
+ : "cc");
+ EXPECT_EQ(ax, (int16_t)Read16(m->ax));
+ EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
+ EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
+ }
+ }
+}
+
+TEST(imul16, test) {
+ static const uint16_t A[] = {0x0000, 0x0001, 0x8000, 0x7FFF, 0x8001, 0x7FFE,
+ 0xFFFF, 0xBeef, 0x00b5, 0x00b6, 0xb504, 0xb505};
+ int i, j;
+ bool cf, of;
+ uint16_t dx, ax;
+ m->xedd->op.rde = OSZ | MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ Write16(m->ax, A[i]);
+ Write16(m->cx, A[j]);
+ OpMulRdxRaxEvqpSigned(m);
+ asm("imulw\t%4"
+ : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of)
+ : "r"(A[j]), "1"(A[i])
+ : "cc");
+ EXPECT_EQ((int32_t)((uint32_t)dx << 16 | ax),
+ (int32_t)((uint32_t)Read16(m->dx) << 16 | Read16(m->ax)));
+ EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
+ EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
+ }
+ }
+}
+
+TEST(imul32, test) {
+ static const uint32_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF,
+ 0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0xDeadBeef,
+ 0x000000b6, 0x0000b504, 0x0000b505, 0xb504f334};
+ int i, j;
+ bool cf, of;
+ uint32_t dx, ax;
+ m->xedd->op.rde = MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ Write32(m->ax, A[i]);
+ Write32(m->cx, A[j]);
+ OpMulRdxRaxEvqpSigned(m);
+ asm("imull\t%4"
+ : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of)
+ : "r"(A[j]), "1"(A[i])
+ : "cc");
+ EXPECT_EQ((int64_t)((uint64_t)dx << 32 | ax),
+ (int64_t)((uint64_t)Read32(m->dx) << 32 | Read32(m->ax)));
+ EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
+ EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
+ }
+ }
+}
+
+TEST(imul64, test) {
+ static const uint64_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF,
+ 0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0xDeadBeef,
+ 0x000000b6, 0x0000b504, 0x0000b505, 0xb504f334};
+ int i, j;
+ bool cf, of;
+ uint64_t dx, ax;
+ m->xedd->op.rde = REXW | MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ Write64(m->ax, A[i]);
+ Write64(m->cx, A[j]);
+ OpMulRdxRaxEvqpSigned(m);
+ asm("imulq\t%4"
+ : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of)
+ : "r"(A[j]), "1"(A[i])
+ : "cc");
+ EXPECT_EQ((int128_t)((uint128_t)dx << 64 | ax),
+ (int128_t)((uint128_t)Read64(m->dx) << 64 | Read64(m->ax)));
+ EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
+ EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
+ }
+ }
+}
+
+TEST(mul8, test) {
+ static const uint8_t A[] = {0x00, 0x01, 0x80, 0x7F, 0x81, 0x7E, 0xFF, 0xb6};
+ int i, j;
+ uint16_t ax;
+ bool cf, of;
+ m->xedd->op.rde = MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ Write8(m->ax, A[i]);
+ Write8(m->cx, A[j]);
+ OpMulAxAlEbUnsigned(m);
+ asm volatile("mulb\t%3"
+ : "=a"(ax), "=@ccc"(cf), "=@cco"(of)
+ : "q"(A[j]), "0"(A[i])
+ : "cc");
+ EXPECT_EQ(ax, Read16(m->ax));
+ EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
+ EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
+ }
+ }
+}
+
+TEST(mul16, test) {
+ static const uint16_t A[] = {0x0000, 0x0001, 0x8000, 0x7FFF,
+ 0x8001, 0x7FFE, 0xFFFF, 0x00b6};
+ int i, j;
+ bool cf, of;
+ uint16_t dx, ax;
+ m->xedd->op.rde = OSZ | MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ Write16(m->ax, A[i]);
+ Write16(m->cx, A[j]);
+ OpMulRdxRaxEvqpUnsigned(m);
+ asm("mulw\t%4"
+ : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of)
+ : "r"(A[j]), "1"(A[i])
+ : "cc");
+ EXPECT_EQ((uint32_t)((uint32_t)dx << 16 | ax),
+ (uint32_t)((uint32_t)Read16(m->dx) << 16 | Read16(m->ax)));
+ EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
+ EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
+ }
+ }
+}
+
+TEST(mul32, test) {
+ static const uint32_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF,
+ 0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0x000000b5,
+ 0x000000b6, 0x0000b504, 0x0000b505, 0xb504f334};
+ int i, j;
+ bool cf, of;
+ uint32_t dx, ax;
+ m->xedd->op.rde = MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ Write32(m->ax, A[i]);
+ Write32(m->cx, A[j]);
+ OpMulRdxRaxEvqpUnsigned(m);
+ asm("mull\t%4"
+ : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of)
+ : "r"(A[j]), "1"(A[i])
+ : "cc");
+ EXPECT_EQ((uint64_t)((uint64_t)dx << 32 | ax),
+ (uint64_t)((uint64_t)Read32(m->dx) << 32 | Read32(m->ax)));
+ EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
+ EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
+ }
+ }
+}
+
+TEST(mul64, test) {
+ static const uint64_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF,
+ 0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0x000000b6,
+ 0x0000b504, 0x0000b505, 0xb504f333, 0xb504f334};
+ int i, j;
+ bool cf, of;
+ uint64_t dx, ax;
+ m->xedd->op.rde = REXW | MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ Write64(m->ax, A[i]);
+ Write64(m->cx, A[j]);
+ OpMulRdxRaxEvqpUnsigned(m);
+ asm("mulq\t%4"
+ : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of)
+ : "r"(A[j]), "1"(A[i])
+ : "cc");
+ EXPECT_EQ((uint128_t)((uint128_t)dx << 64 | ax),
+ (uint128_t)((uint128_t)Read64(m->dx) << 64 | Read64(m->ax)));
+ EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF));
+ EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF));
+ }
+ }
+}
+
+TEST(idiv8, test) {
+ static const uint8_t A[] = {0x00, 0x01, 0x80, 0x7F, 0x81, 0x7E, 0xFF, 0xBF};
+ uint16_t remquo;
+ bool gotthrow, gotsigfpe;
+ int8_t i, j, k, w, x, a, b;
+ int8_t quotient, remainder;
+ m->xedd->op.rde = MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ for (k = 0; k < ARRAYLEN(A); ++k) {
+ m->ax[1] = A[i];
+ m->ax[0] = A[j];
+ m->cx[0] = A[k];
+ gotthrow = false;
+ gotsigfpe = false;
+ if (!setjmp(m->onhalt)) {
+ OpDivAlAhAxEbSigned(m);
+ } else {
+ gotthrow = true;
+ }
+ if (!setjmp(sigfpejmp)) {
+ asm("idivb\t%1"
+ : "=a"(remquo)
+ : "q"(A[k]), "0"((int16_t)(A[i] << 8 | A[j]))
+ : "cc");
+ } else {
+ gotsigfpe = true;
+ }
+ EXPECT_EQ(gotsigfpe, gotthrow);
+ if (!gotsigfpe && !gotthrow) {
+ quotient = (int8_t)remquo;
+ remainder = (int8_t)(remquo >> 8);
+ EXPECT_EQ(quotient, (int8_t)m->ax[0]);
+ EXPECT_EQ(remainder, (int8_t)m->ax[1]);
+ }
+ }
+ }
+ }
+}
+
+TEST(idiv16, test) {
+ static const uint16_t A[] = {0x0000, 0x0001, 0x8000, 0x7FFF,
+ 0x8001, 0x7FFE, 0xFFFF, 0xBeef};
+ bool gotthrow, gotsigfpe;
+ int16_t i, j, k, w, x, a, b;
+ int16_t quotient, remainder;
+ m->xedd->op.rde = OSZ | MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ for (k = 0; k < ARRAYLEN(A); ++k) {
+ memcpy(m->dx, &A[i], 2);
+ memcpy(m->ax, &A[j], 2);
+ memcpy(m->cx, &A[k], 2);
+ if (!setjmp(m->onhalt)) {
+ gotthrow = false;
+ OpDivRdxRaxEvqpSigned(m);
+ } else {
+ gotthrow = true;
+ }
+ if (!setjmp(sigfpejmp)) {
+ gotsigfpe = false;
+ asm("idivw\t%2"
+ : "=d"(remainder), "=a"(quotient)
+ : "r"(A[k]), "0"(A[i]), "1"(A[j])
+ : "cc");
+ } else {
+ gotsigfpe = true;
+ }
+ EXPECT_EQ(gotsigfpe, gotthrow);
+ if (!gotsigfpe && !gotthrow) {
+ EXPECT_EQ(quotient, (int16_t)Read16(m->ax));
+ EXPECT_EQ(remainder, (int16_t)Read16(m->dx));
+ }
+ }
+ }
+ }
+}
+
+TEST(idiv32, test) {
+ static const uint32_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF,
+ 0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0xDeadBeef};
+ bool gotthrow, gotsigfpe;
+ int32_t i, j, k, w, x, a, b;
+ int32_t quotient, remainder;
+ m->xedd->op.rde = MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ for (k = 0; k < ARRAYLEN(A); ++k) {
+ memcpy(m->dx, &A[i], 4);
+ memcpy(m->ax, &A[j], 4);
+ memcpy(m->cx, &A[k], 4);
+ if (!setjmp(m->onhalt)) {
+ gotthrow = false;
+ OpDivRdxRaxEvqpSigned(m);
+ } else {
+ gotthrow = true;
+ }
+ if (!setjmp(sigfpejmp)) {
+ gotsigfpe = false;
+ asm("idivl\t%2"
+ : "=d"(remainder), "=a"(quotient)
+ : "r"(A[k]), "0"(A[i]), "1"(A[j])
+ : "cc");
+ } else {
+ gotsigfpe = true;
+ }
+ EXPECT_EQ(gotsigfpe, gotthrow);
+ if (!gotsigfpe && !gotthrow) {
+ EXPECT_EQ(quotient, (int32_t)Read32(m->ax));
+ EXPECT_EQ(remainder, (int32_t)Read32(m->dx));
+ }
+ }
+ }
+ }
+}
+
+TEST(idiv64, test) {
+ static const uint64_t A[] = {0x0000000000000000, 0x0000000000000001,
+ 0x8000000000000000, 0x7FFFFFFFFFFFFFFF,
+ 0x8000000000000001, 0x7FFFFFFFFFFFFFFE,
+ 0xFFFFFFFFFFFFFFFF, 0x00DeadBeefCafe00};
+ bool gotthrow, gotsigfpe;
+ int64_t i, j, k, w, x, a, b;
+ int64_t quotient, remainder;
+ m->xedd->op.rde = REXW | MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ for (k = 0; k < ARRAYLEN(A); ++k) {
+ memcpy(m->dx, &A[i], 8);
+ memcpy(m->ax, &A[j], 8);
+ memcpy(m->cx, &A[k], 8);
+ if (!setjmp(m->onhalt)) {
+ gotthrow = false;
+ OpDivRdxRaxEvqpSigned(m);
+ } else {
+ gotthrow = true;
+ }
+ if (!setjmp(sigfpejmp)) {
+ gotsigfpe = false;
+ asm("idivq\t%2"
+ : "=d"(remainder), "=a"(quotient)
+ : "r"(A[k]), "0"(A[i]), "1"(A[j])
+ : "cc");
+ } else {
+ gotsigfpe = true;
+ }
+ EXPECT_EQ(gotsigfpe, gotthrow);
+ if (!gotsigfpe && !gotthrow) {
+ EXPECT_EQ(quotient, (int64_t)Read64(m->ax));
+ EXPECT_EQ(remainder, (int64_t)Read64(m->dx));
+ }
+ }
+ }
+ }
+}
+
+TEST(div, test) {
+ static const uint8_t A[] = {0x00, 0x01, 0x80, 0x7F, 0x81, 0x7E, 0xFF, 0xBF};
+ uint16_t remquo;
+ bool gotthrow, gotsigfpe;
+ uint8_t i, j, k, w, x, a, b;
+ uint8_t quotient, remainder;
+ m->xedd->op.rde = MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ for (k = 0; k < ARRAYLEN(A); ++k) {
+ m->ax[1] = A[i];
+ m->ax[0] = A[j];
+ m->cx[0] = A[k];
+ gotthrow = false;
+ gotsigfpe = false;
+ if (!setjmp(m->onhalt)) {
+ OpDivAlAhAxEbUnsigned(m);
+ } else {
+ gotthrow = true;
+ }
+ if (!setjmp(sigfpejmp)) {
+ asm("divb\t%1"
+ : "=a"(remquo)
+ : "q"(A[k]), "0"((uint16_t)(A[i] << 8 | A[j]))
+ : "cc");
+ } else {
+ gotsigfpe = true;
+ }
+ EXPECT_EQ(gotsigfpe, gotthrow);
+ if (!gotsigfpe && !gotthrow) {
+ quotient = (uint8_t)remquo;
+ remainder = (uint8_t)(remquo >> 8);
+ EXPECT_EQ(quotient, (uint8_t)m->ax[0]);
+ EXPECT_EQ(remainder, (uint8_t)m->ax[1]);
+ }
+ }
+ }
+ }
+}
+
+TEST(div16, test) {
+ static const uint16_t A[] = {0x0000, 0x0001, 0x8000, 0x7FFF,
+ 0x8001, 0x7FFE, 0xFFFF, 0xBeef};
+ bool gotthrow, gotsigfpe;
+ uint16_t i, j, k, w, x, a, b;
+ uint16_t quotient, remainder;
+ m->xedd->op.rde = OSZ | MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ for (k = 0; k < ARRAYLEN(A); ++k) {
+ memcpy(m->dx, &A[i], 2);
+ memcpy(m->ax, &A[j], 2);
+ memcpy(m->cx, &A[k], 2);
+ if (!setjmp(m->onhalt)) {
+ gotthrow = false;
+ OpDivRdxRaxEvqpUnsigned(m);
+ } else {
+ gotthrow = true;
+ }
+ if (!setjmp(sigfpejmp)) {
+ gotsigfpe = false;
+ asm("divw\t%2"
+ : "=d"(remainder), "=a"(quotient)
+ : "r"(A[k]), "0"(A[i]), "1"(A[j])
+ : "cc");
+ } else {
+ gotsigfpe = true;
+ }
+ EXPECT_EQ(gotsigfpe, gotthrow);
+ if (!gotsigfpe && !gotthrow) {
+ EXPECT_EQ(quotient, (uint16_t)Read16(m->ax));
+ EXPECT_EQ(remainder, (uint16_t)Read16(m->dx));
+ }
+ }
+ }
+ }
+}
+
+TEST(div32, test) {
+ static const uint32_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF,
+ 0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0xDeadBeef};
+ bool gotthrow, gotsigfpe;
+ uint32_t i, j, k, w, x, a, b;
+ uint32_t quotient, remainder;
+ m->xedd->op.rde = MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ for (k = 0; k < ARRAYLEN(A); ++k) {
+ memcpy(m->dx, &A[i], 4);
+ memcpy(m->ax, &A[j], 4);
+ memcpy(m->cx, &A[k], 4);
+ if (!setjmp(m->onhalt)) {
+ gotthrow = false;
+ OpDivRdxRaxEvqpUnsigned(m);
+ } else {
+ gotthrow = true;
+ }
+ if (!setjmp(sigfpejmp)) {
+ gotsigfpe = false;
+ asm("divl\t%2"
+ : "=d"(remainder), "=a"(quotient)
+ : "r"(A[k]), "0"(A[i]), "1"(A[j])
+ : "cc");
+ } else {
+ gotsigfpe = true;
+ }
+ EXPECT_EQ(gotsigfpe, gotthrow);
+ if (!gotsigfpe && !gotthrow) {
+ EXPECT_EQ(quotient, (uint32_t)Read32(m->ax));
+ EXPECT_EQ(remainder, (uint32_t)Read32(m->dx));
+ }
+ }
+ }
+ }
+}
+
+TEST(div64, test) {
+ static const uint64_t A[] = {0x0000000000000000, 0x0000000000000001,
+ 0x8000000000000000, 0x7FFFFFFFFFFFFFFF,
+ 0x8000000000000001, 0x7FFFFFFFFFFFFFFE,
+ 0xFFFFFFFFFFFFFFFF, 0x00DeadBeefCafe00};
+ bool gotthrow, gotsigfpe;
+ uint64_t i, j, k, w, x, a, b;
+ uint64_t quotient, remainder;
+ m->xedd->op.rde = REXW | MOD(3) | RM(CX);
+ for (i = 0; i < ARRAYLEN(A); ++i) {
+ for (j = 0; j < ARRAYLEN(A); ++j) {
+ for (k = 0; k < ARRAYLEN(A); ++k) {
+ memcpy(m->dx, &A[i], 8);
+ memcpy(m->ax, &A[j], 8);
+ memcpy(m->cx, &A[k], 8);
+ if (!setjmp(m->onhalt)) {
+ gotthrow = false;
+ OpDivRdxRaxEvqpUnsigned(m);
+ } else {
+ gotthrow = true;
+ }
+ if (!setjmp(sigfpejmp)) {
+ gotsigfpe = false;
+ asm("divq\t%2"
+ : "=d"(remainder), "=a"(quotient)
+ : "r"(A[k]), "0"(A[i]), "1"(A[j])
+ : "cc");
+ } else {
+ gotsigfpe = true;
+ }
+ EXPECT_EQ(gotsigfpe, gotthrow);
+ if (!gotsigfpe && !gotthrow) {
+ EXPECT_EQ(quotient, (uint64_t)Read64(m->ax));
+ EXPECT_EQ(remainder, (uint64_t)Read64(m->dx));
+ }
+ }
+ }
+ }
+}
+
+BENCH(imul, bench) {
+ volatile register int8_t x8, y8;
+ volatile register int16_t x16, y16;
+ volatile register int32_t x32, y32;
+ volatile register int64_t x64, y64;
+ EZBENCH2("imul8", PROGN(x8 = 7, y8 = 18), y8 *= x8);
+ EZBENCH2("imul16", PROGN(x16 = 123, y16 = 116), y16 *= x16);
+ EZBENCH2("imul32", PROGN(x32 = 0x238943, y32 = 0x238), y32 *= x32);
+ EZBENCH2("imul64", PROGN(x64 = 0x23894329838932, y64 = 0x238), y64 *= x64);
+}
+
+BENCH(idiv, bench) {
+ volatile register int8_t x8, y8;
+ volatile register int16_t x16, y16;
+ volatile register int32_t x32, y32;
+ volatile register int64_t x64, y64;
+ EZBENCH2("idiv8", PROGN(x8 = 7, y8 = 18), x8 /= y8);
+ EZBENCH2("idiv16", PROGN(x16 = 123, y16 = 116), x16 /= y16);
+ EZBENCH2("idiv32", PROGN(x32 = 0x238943298, y32 = 0x238), x32 /= y32);
+ EZBENCH2("idiv64", PROGN(x64 = 0x23894329838932, y64 = 0x238), x64 /= y64);
+}
+
+BENCH(mul, bench) {
+ volatile register uint8_t x8, y8;
+ volatile register uint16_t x16, y16;
+ volatile register uint32_t x32, y32;
+ volatile register uint64_t x64, y64;
+ EZBENCH2("mul8", PROGN(x8 = 7, y8 = 18), y8 *= x8);
+ EZBENCH2("mul16", PROGN(x16 = 123, y16 = 116), y16 *= x16);
+ EZBENCH2("mul32", PROGN(x32 = 0x238943, y32 = 0x238), y32 *= x32);
+ EZBENCH2("mul64", PROGN(x64 = 0x23894329838932, y64 = 0x238), y64 *= x64);
+}
+
+BENCH(div, bench) {
+ volatile register uint8_t x8, y8;
+ volatile register uint16_t x16, y16;
+ volatile register uint32_t x32, y32;
+ volatile register uint64_t x64, y64;
+ EZBENCH2("div8", PROGN(x8 = 7, y8 = 18), x8 /= y8);
+ EZBENCH2("div16", PROGN(x16 = 123, y16 = 116), x16 /= y16);
+ EZBENCH2("div32", PROGN(x32 = 0x238943298, y32 = 0x238), x32 /= y32);
+ EZBENCH2("div64", PROGN(x64 = 0x23894329838932, y64 = 0x238), x64 /= y64);
+}
diff --git a/test/tool/build/lib/machine_test.c b/test/tool/build/lib/machine_test.c
new file mode 100644
index 000000000..f21be6a91
--- /dev/null
+++ b/test/tool/build/lib/machine_test.c
@@ -0,0 +1,298 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/bits/progn.h"
+#include "libc/fmt/bing.h"
+#include "libc/math.h"
+#include "libc/mem/mem.h"
+#include "libc/runtime/gc.h"
+#include "libc/stdio/stdio.h"
+#include "libc/testlib/ezbench.h"
+#include "libc/testlib/testlib.h"
+#include "libc/x/x.h"
+#include "tool/build/lib/endian.h"
+#include "tool/build/lib/fpu.h"
+#include "tool/build/lib/machine.h"
+#include "tool/build/lib/memory.h"
+
+const uint8_t kPi80[] = {
+ 0xd9, 0xe8, // fld1
+ 0xb8, 0x0a, 0x00, 0x00, 0x00, // mov $0xa,%eax
+ 0x31, 0xd2, // xor %edx,%edx
+ 0xd9, 0xee, // fldz
+ 0x48, 0x98, // cltq
+ 0x48, 0x39, 0xc2, // cmp %rax,%rdx
+ 0xd9, 0x05, 0x1a, 0x00, 0x00, 0x00, // flds 0x1a(%rip)
+ 0x7d, 0x13, // jge 2b
+ 0xde, 0xc1, // faddp
+ 0x48, 0xff, 0xc2, // inc %rdx
+ 0xd9, 0xfa, // fsqrt
+ 0xd9, 0x05, 0x0f, 0x00, 0x00, 0x00, // flds 15(%rip)
+ 0xd8, 0xc9, // fmul %st(1),%st
+ 0xde, 0xca, // fmulp %st,%st(2)
+ 0xeb, 0xe2, // jmp d
+ 0xdd, 0xd9, // fstp %st(1)
+ 0xde, 0xf1, // fdivp
+ 0xf4, // hlt
+ 0x00, 0x00, 0x00, 0x40, // .float 2.0
+ 0x00, 0x00, 0x00, 0x3f, // .float 0.5
+};
+
+const uint8_t kTenthprime[] = {
+ 0x31, 0xd2, // xor %edx,%edx
+ 0x45, 0x31, 0xc0, // xor %r8d,%r8d
+ 0x31, 0xc9, // xor %ecx,%ecx
+ 0xbe, 0x03, 0x00, 0x00, 0x00, // mov $0x3,%esi
+ 0x41, 0xff, 0xc0, // inc %r8d
+ 0x44, 0x89, 0xc0, // mov %r8d,%eax
+ 0x83, 0xf9, 0x0a, // cmp $0xa,%ecx
+ 0x74, 0x0b, // je 20
+ 0x99, // cltd
+ 0xf7, 0xfe, // idiv %esi
+ 0x83, 0xfa, 0x01, // cmp $0x1,%edx
+ 0x83, 0xd9, 0xff, // sbb $-1,%ecx
+ 0xeb, 0xea, // jmp a
+ 0xf4, // hlt
+};
+
+const uint8_t kTenthprime2[] = {
+ 0xE8, 0x11, 0x00, 0x00, 0x00, //
+ 0xF4, //
+ 0x89, 0xF8, //
+ 0xB9, 0x03, 0x00, 0x00, 0x00, //
+ 0x99, //
+ 0xF7, 0xF9, //
+ 0x85, 0xD2, //
+ 0x0F, 0x95, 0xC0, //
+ 0xC3, //
+ 0x55, //
+ 0x48, 0x89, 0xE5, //
+ 0x31, 0xF6, //
+ 0x45, 0x31, 0xC0, //
+ 0x44, 0x89, 0xC7, //
+ 0xE8, 0xDF, 0xFF, 0xFF, 0xFF, //
+ 0x0F, 0xB6, 0xC0, //
+ 0x66, 0x83, 0xF8, 0x01, //
+ 0x83, 0xDE, 0xFF, //
+ 0x41, 0xFF, 0xC0, //
+ 0x83, 0xFE, 0x0A, //
+ 0x75, 0xE6, //
+ 0x44, 0x89, 0xC0, //
+ 0x5D, //
+ 0xC3, //
+};
+
+int64_t base;
+uint8_t *real;
+size_t realsize;
+struct Machine *m;
+
+void SetUp(void) {
+ base = 0;
+ m = NewMachine();
+ realsize = 0x10000;
+ real = tmemalign(PAGESIZE, ROUNDUP(realsize, PAGESIZE));
+ RegisterMemory(m, base, real, realsize);
+ m->ip = base;
+ Write64(m->sp, m->ip + realsize);
+}
+
+void TearDown(void) {
+ ResetRam(m);
+ tfree(real);
+ free(m);
+}
+
+int ExecuteUntilHalt(struct Machine *m) {
+ int rc;
+ if (!(rc = setjmp(m->onhalt))) {
+ for (;;) {
+ LoadInstruction(m);
+ ExecuteInstruction(m);
+ }
+ } else {
+ return rc;
+ }
+}
+
+TEST(machine, test) {
+ memcpy(real, kTenthprime, sizeof(kTenthprime));
+ ASSERT_EQ(kMachineHalt, ExecuteUntilHalt(m));
+ ASSERT_EQ(15, Read32(m->ax));
+}
+
+TEST(machine, testFpu) {
+ memcpy(real, kPi80, sizeof(kPi80));
+ ASSERT_EQ(kMachineHalt, ExecuteUntilHalt(m));
+ ASSERT_TRUE(fabs(3.14159 - FpuPop(m)) < 0.0001);
+ m->ip = base;
+ ASSERT_EQ(kMachineHalt, ExecuteUntilHalt(m));
+ ASSERT_TRUE(fabs(3.14159 - FpuPop(m)) < 0.0001);
+}
+
+BENCH(machine, benchPrimeNumberPrograms) {
+ memcpy(real, kTenthprime2, sizeof(kTenthprime2));
+ EZBENCH2("tenthprime2", m->ip = base, ExecuteUntilHalt(m));
+ ASSERT_EQ(15, Read32(m->ax));
+ memcpy(real, kTenthprime, sizeof(kTenthprime));
+ EZBENCH2("tenthprime", m->ip = base, ExecuteUntilHalt(m));
+ ASSERT_EQ(15, Read32(m->ax));
+}
+
+BENCH(machine, benchFpu) {
+ memcpy(real, kPi80, sizeof(kPi80));
+ EZBENCH2("pi80", m->ip = base, PROGN(ExecuteUntilHalt(m), FpuPop(m)));
+}
+
+BENCH(machine, benchLoadExec2) {
+ uint8_t kMovCode[] = {0xbe, 0x03, 0x00, 0x00, 0x00};
+ memcpy(real, kMovCode, sizeof(kMovCode));
+ LoadInstruction(m);
+ EZBENCH2("mov", m->ip = base, ExecuteInstruction(m));
+}
+
+BENCH(machine, benchLoadExec3) {
+ uint8_t kMovdCode[] = {0x66, 0x0f, 0x6e, 0xc0};
+ Write64(m->ax, 0);
+ memcpy(real, kMovdCode, sizeof(kMovdCode));
+ LoadInstruction(m);
+ EZBENCH2("movd", m->ip = base, ExecuteInstruction(m));
+}
+
+BENCH(machine, benchLoadExec4) {
+ uint8_t kAddpsRegregCode[] = {0x0f, 0x58, 0xC0};
+ uint8_t kAddpsMemregCode[] = {0x0f, 0x58, 0x00};
+ Write64(m->ax, 0);
+ memcpy(real, kAddpsRegregCode, sizeof(kAddpsRegregCode));
+ LoadInstruction(m);
+ EZBENCH2("addps reg reg", m->ip = base, ExecuteInstruction(m));
+ memcpy(real, kAddpsMemregCode, sizeof(kAddpsMemregCode));
+ LoadInstruction(m);
+ EZBENCH2("addps mem reg", m->ip = base, ExecuteInstruction(m));
+}
+
+BENCH(machine, benchLoadExec5) {
+ uint8_t kPaddwRegregCode[] = {0x66, 0x0F, 0xFD, 0xC0};
+ uint8_t kPaddwMemregCode[] = {0x66, 0x0F, 0xFD, 0x00};
+ Write64(m->ax, 0);
+ memcpy(real, kPaddwRegregCode, sizeof(kPaddwRegregCode));
+ LoadInstruction(m);
+ EZBENCH2("paddw", m->ip = base, ExecuteInstruction(m));
+ memcpy(real, kPaddwMemregCode, sizeof(kPaddwMemregCode));
+ LoadInstruction(m);
+ EZBENCH2("paddw mem", m->ip = base, ExecuteInstruction(m));
+}
+
+BENCH(machine, benchLoadExec6) {
+ uint8_t kPsubqRegregCode[] = {0x66, 0x0F, 0xFB, 0xC0};
+ uint8_t kPsubqMemregCode[] = {0x66, 0x0F, 0xFB, 0x00};
+ Write64(m->ax, 0);
+ memcpy(real, kPsubqRegregCode, sizeof(kPsubqRegregCode));
+ LoadInstruction(m);
+ EZBENCH2("psubq", m->ip = base, ExecuteInstruction(m));
+ memcpy(real, kPsubqMemregCode, sizeof(kPsubqMemregCode));
+ LoadInstruction(m);
+ EZBENCH2("psubq mem", m->ip = base, ExecuteInstruction(m));
+}
+
+BENCH(machine, benchAddqMem) {
+ uint8_t kAddMemregCode[] = {0x48, 0x03, 0x08};
+ Write64(m->ax, 0);
+ memcpy(real, kAddMemregCode, sizeof(kAddMemregCode));
+ LoadInstruction(m);
+ EZBENCH2("addq mem", m->ip = base, ExecuteInstruction(m));
+}
+
+BENCH(machine, benchAddlMem) {
+ uint8_t kAddMemregCode[] = {0x03, 0x08};
+ Write64(m->ax, 0);
+ memcpy(real, kAddMemregCode, sizeof(kAddMemregCode));
+ LoadInstruction(m);
+ EZBENCH2("addl mem", m->ip = base, ExecuteInstruction(m));
+}
+
+BENCH(machine, benchAddq) {
+ uint8_t kAddqCode[] = {0x48, 0x01, 0xd8};
+ Write64(m->ax, 0);
+ memcpy(real, kAddqCode, sizeof(kAddqCode));
+ LoadInstruction(m);
+ EZBENCH2("addq", m->ip = base, ExecuteInstruction(m));
+}
+
+BENCH(machine, benchAddb) {
+ uint8_t kAddbCode[] = {0x00, 0xd8};
+ Write64(m->ax, 0);
+ memcpy(real, kAddbCode, sizeof(kAddbCode));
+ LoadInstruction(m);
+ EZBENCH2("addb", m->ip = base, ExecuteInstruction(m));
+}
+
+BENCH(machine, benchXorReg) {
+ memcpy(real, kTenthprime, sizeof(kTenthprime));
+ LoadInstruction(m);
+ EZBENCH2("xor", m->ip = base, ExecuteInstruction(m));
+}
+
+BENCH(machine, benchLoadExec8) {
+ uint8_t kFchsCode[] = {0xd9, 0xe0};
+ Write64(m->ax, 0);
+ OpFinit(m);
+ *FpuSt(m, 0) = M_PI;
+ FpuSetTag(m, 0, kFpuTagValid);
+ memcpy(real, kFchsCode, sizeof(kFchsCode));
+ LoadInstruction(m);
+ EZBENCH2("fchs", m->ip = base, ExecuteInstruction(m));
+}
+
+BENCH(machine, benchPushpop) {
+ uint8_t kPushpop[] = {0x50, 0x58};
+ Write64(m->ax, 0);
+ memcpy(real, kPushpop, sizeof(kPushpop));
+ EZBENCH2("pushpop", m->ip = base,
+ PROGN(LoadInstruction(m), ExecuteInstruction(m), LoadInstruction(m),
+ ExecuteInstruction(m)));
+}
+
+BENCH(machine, benchPause) {
+ uint8_t kPause[] = {0xf3, 0x90};
+ Write64(m->ax, 0);
+ memcpy(real, kPause, sizeof(kPause));
+ LoadInstruction(m);
+ EZBENCH2("pause", m->ip = base, ExecuteInstruction(m));
+}
+
+BENCH(machine, benchClc) {
+ uint8_t kClc[] = {0xf8};
+ Write64(m->ax, 0);
+ memcpy(real, kClc, sizeof(kClc));
+ LoadInstruction(m);
+ EZBENCH2("clc", m->ip = base, ExecuteInstruction(m));
+}
+
+BENCH(machine, benchNop) {
+ uint8_t kNop[] = {0x90};
+ Write64(m->ax, 0);
+ memcpy(real, kNop, sizeof(kNop));
+ LoadInstruction(m);
+ EZBENCH2("nop", m->ip = base, ExecuteInstruction(m));
+}
+
+TEST(machine, sizeIsReasonable) {
+ ASSERT_LE(sizeof(struct Machine), 65536);
+}
diff --git a/test/tool/build/lib/modrm_test.c b/test/tool/build/lib/modrm_test.c
new file mode 100644
index 000000000..4a63a2fd0
--- /dev/null
+++ b/test/tool/build/lib/modrm_test.c
@@ -0,0 +1,105 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/mem/mem.h"
+#include "libc/runtime/gc.h"
+#include "libc/testlib/testlib.h"
+#include "third_party/xed/x86.h"
+#include "tool/build/lib/endian.h"
+#include "tool/build/lib/machine.h"
+#include "tool/build/lib/modrm.h"
+
+TEST(modrm, testAddressSizeOverride_isNotPresent_keepsWholeExpression) {
+ struct Machine *m = gc(NewMachine());
+ struct XedDecodedInst *xedd = gc(calloc(1, sizeof(struct XedDecodedInst)));
+ uint8_t op[] = {0x8d, 0x04, 0x03}; /* lea (%rbx,%rax,1),%eax */
+ m->xedd = xedd;
+ Write64(m->bx, 0x2);
+ Write64(m->ax, 0xffffffff);
+ xed_decoded_inst_zero_set_mode(xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(xedd, op, sizeof(op)));
+ EXPECT_EQ(0x100000001, ComputeAddress(m));
+}
+
+TEST(modrm, testAddressSizeOverride_isPresent_modulesWholeExpression) {
+ struct Machine *m = gc(NewMachine());
+ struct XedDecodedInst *xedd = gc(calloc(1, sizeof(struct XedDecodedInst)));
+ uint8_t op[] = {0x67, 0x8d, 0x04, 0x03}; /* lea (%ebx,%eax,1),%eax */
+ m->xedd = xedd;
+ Write64(m->bx, 0x2);
+ Write64(m->ax, 0xffffffff);
+ xed_decoded_inst_zero_set_mode(xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(xedd, op, sizeof(op)));
+ EXPECT_EQ(0x000000001, ComputeAddress(m));
+}
+
+TEST(modrm, testOverflow_doesntTriggerTooling) {
+ struct Machine *m = gc(NewMachine());
+ struct XedDecodedInst *xedd = gc(calloc(1, sizeof(struct XedDecodedInst)));
+ uint8_t op[] = {0x8d, 0x04, 0x03}; /* lea (%rbx,%rax,1),%eax */
+ m->xedd = xedd;
+ Write64(m->bx, 0x0000000000000001);
+ Write64(m->ax, 0x7fffffffffffffff);
+ xed_decoded_inst_zero_set_mode(xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(xedd, op, sizeof(op)));
+ EXPECT_EQ(0x8000000000000000ull, (uint64_t)ComputeAddress(m));
+}
+
+TEST(modrm, testPuttingOnTheRiz) {
+ struct Machine *m = gc(NewMachine());
+ static uint8_t ops[][15] = {
+ {0x8d, 0b00110100, 0b00100110}, // lea (%rsi),%esi
+ {0x67, 0x8d, 0b00110100, 0b11100110}, // lea (%esi,%eiz,8),%esi
+ {103, 141, 180, 229, 55, 19, 3, 0}, // lea 0x31337(%ebp,%eiz,8),%esi
+ {141, 52, 229, 55, 19, 3, 0}, // lea 0x31337,%esi
+ };
+ m->xedd = gc(calloc(1, sizeof(struct XedDecodedInst)));
+ Write64(m->si, 0x100000001);
+ Write64(m->bp, 0x200000002);
+ xed_decoded_inst_zero_set_mode(m->xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(m->xedd, ops[0], sizeof(ops[0])));
+ EXPECT_EQ(0x100000001, ComputeAddress(m));
+ xed_decoded_inst_zero_set_mode(m->xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(m->xedd, ops[1], sizeof(ops[1])));
+ EXPECT_EQ(0x000000001, ComputeAddress(m));
+ xed_decoded_inst_zero_set_mode(m->xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(m->xedd, ops[2], sizeof(ops[2])));
+ EXPECT_EQ(0x31339, ComputeAddress(m));
+ xed_decoded_inst_zero_set_mode(m->xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(m->xedd, ops[3], sizeof(ops[3])));
+ EXPECT_EQ(0x31337, ComputeAddress(m));
+}
+
+TEST(modrm, testSibIndexOnly) {
+ // mod = 0b00 (0)
+ // reg = 0b000 (0)
+ // rm = 0b100 (4)
+ // scale = 0b10 (2)
+ // index = 0b001 (1)
+ // base = 0b101 (5)
+ struct Machine *m = gc(NewMachine());
+ struct XedDecodedInst *xedd = gc(calloc(1, sizeof(struct XedDecodedInst)));
+ uint8_t op[] = {76, 141, 4, 141, 0, 0, 0, 0}; /* lea 0x0(,%rcx,4),%r8 */
+ m->xedd = xedd;
+ Write64(m->bp, 0x123);
+ Write64(m->cx, 0x123);
+ xed_decoded_inst_zero_set_mode(xedd, XED_MACHINE_MODE_LONG_64);
+ ASSERT_EQ(0, xed_instruction_length_decode(xedd, op, sizeof(op)));
+ EXPECT_EQ(0x123 * 4, (uint64_t)ComputeAddress(m));
+}
diff --git a/test/tool/build/lib/numbers.c b/test/tool/build/lib/numbers.c
new file mode 100644
index 000000000..ca45837b7
--- /dev/null
+++ b/test/tool/build/lib/numbers.c
@@ -0,0 +1,57 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "test/tool/build/lib/numbers.h"
+
+const uint64_t kNumbers[102] = {
+ 0x0000000000000000, 0x0000000080000000, 0x0000000000008000,
+ 0x0000000000000001, 0x0000000000000002, 0x0000000000000003,
+ 0x0000000000000004, 0x0000000000000005, 0x0000000000000006,
+ 0x0000000000000007, 0x0000000000000008, 0x0000000000000009,
+ 0x000000000000000a, 0x000000000000000b, 0x000000000000000c,
+ 0x000000000000000d, 0x000000000000000e, 0x000000000000000f,
+ 0x0000000000000010, 0x0000000000000011, 0x0000000000000012,
+ 0x0000000000000013, 0x0000000000000014, 0x0000000000000015,
+ 0x0000000000000016, 0x0000000000000017, 0x0000000000000018,
+ 0x0000000000000019, 0x000000000000001a, 0x000000000000001b,
+ 0x000000000000001c, 0x000000000000001d, 0x000000000000001e,
+ 0x000000000000001f, 0x0000000000000020, 0x0000000000000040,
+ 0x8000000000000000, 0x0000000000000080, 0x0000000000000002,
+ 0x0000000000000001, 0x0000000000000004, 0x00000000C0000000,
+ 0xC000000000000000, 0x000000000000C000, 0x00000000000000C0,
+ 0x0000000000000003, 0x000000000000E000, 0x00000000E0000000,
+ 0xE000000000000000, 0x00000000000000E0, 0x000000000000001F,
+ 0x00000000000000FC, 0x000000000000003F, 0x000000000000007F,
+ 0x00000000000000FB, 0x00000000000000FD, 0x00000000000000FE,
+ 0x00000000000000FF, 0x000000000000FF1F, 0x0000000000001FFF,
+ 0x000000000000FFFC, 0x0000000000003FFF, 0x000000000000FF3F,
+ 0x000000000000FFFD, 0x000000000000FFFE, 0x000000000000FFFB,
+ 0x000000000000FF7F, 0x0000000000007FFF, 0x000000000000FFFF,
+ 0x00000000FFFF1FFF, 0x00000000FFFFFF1F, 0x000000001FFFFFFF,
+ 0x00000000FFFFFF3F, 0x00000000FFFF3FFF, 0x00000000FFFFFFFC,
+ 0x000000003FFFFFFF, 0x00000000FFFFFF7F, 0x00000000FFFFFFFD,
+ 0x00000000FFFFFFFE, 0x00000000FFFFFFFB, 0x000000007FFFFFFF,
+ 0x00000000FFFF7FFF, 0x00000000FFFFFFFF, 0xFFFFFFFF1FFFFFFF,
+ 0x1FFFFFFFFFFFFFFF, 0xFFFFFFFFFFFF1FFF, 0xFFFFFFFFFFFFFF1F,
+ 0xFFFFFFFFFFFFFFFC, 0xFFFFFFFFFFFF3FFF, 0xFFFFFFFF3FFFFFFF,
+ 0xFFFFFFFFFFFFFF3F, 0x3FFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFD,
+ 0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFB, 0xFFFFFFFF7FFFFFFF,
+ 0x7FFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFF7F, 0xFFFFFFFFFFFF7FFF,
+ 0xFFFFFFFFFFFFFFFF, 0x00DeadBeefCafe00, 0x0000031337000000,
+};
diff --git a/test/tool/build/lib/numbers.h b/test/tool/build/lib/numbers.h
new file mode 100644
index 000000000..6ab84b2d1
--- /dev/null
+++ b/test/tool/build/lib/numbers.h
@@ -0,0 +1,10 @@
+#ifndef COSMOPOLITAN_TEST_TOOL_BUILD_LIB_NUMBERS_H_
+#define COSMOPOLITAN_TEST_TOOL_BUILD_LIB_NUMBERS_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+extern const uint64_t kNumbers[102];
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_TEST_TOOL_BUILD_LIB_NUMBERS_H_ */
diff --git a/test/tool/build/lib/optest.c b/test/tool/build/lib/optest.c
new file mode 100644
index 000000000..8022e3462
--- /dev/null
+++ b/test/tool/build/lib/optest.c
@@ -0,0 +1,94 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/bits/weaken.h"
+#include "libc/macros.h"
+#include "libc/runtime/runtime.h"
+#include "libc/stdio/stdio.h"
+#include "test/tool/build/lib/numbers.h"
+#include "test/tool/build/lib/optest.h"
+#include "tool/build/lib/flags.h"
+
+const char kOpSuffix[] = {'b', 'w', 'l', 'q'};
+
+void(RunOpTests)(const uint8_t *ops, size_t n, const char *const *opnames,
+ const char *file, int line, const char *func) {
+ uint64_t x, y;
+ uint64_t xn, xp;
+ uint32_t f0, f1, f2;
+ long failed, succeeded;
+ int w, h, i, j, c, z, s, o, p;
+ failed = 0;
+ succeeded = 0;
+ for (w = 0; w < 4; ++w) {
+ for (h = 0; h < n; ++h) {
+ for (z = 0; z < 2; ++z) {
+ for (o = 0; o < 2; ++o) {
+ for (s = 0; s < 2; ++s) {
+ for (i = 0; i < ARRAYLEN(kNumbers); ++i) {
+ for (j = 0; j < ARRAYLEN(kNumbers); ++j) {
+ for (c = 0; c < 2; ++c) {
+ x = kNumbers[i];
+ y = kNumbers[j];
+ f2 = f1 = f0 = c << FLAGS_CF | z << FLAGS_ZF | s << FLAGS_SF |
+ o << FLAGS_OF;
+ xn = RunGolden(w, ops[h], x, y, &f1);
+ xp = RunOpTest(w, ops[h], x, y, &f2);
+ if (weaken(FixupUndefOpTestFlags)) {
+ FixupUndefOpTestFlags(w, ops[h], x, y, f1, &f2);
+ }
+ if (xn == xp && (f1 & FMASK) == (f2 & FMASK)) {
+ succeeded++;
+ } else if (failed++ < 10) {
+ fprintf(stderr,
+ "%s:%d:%s: %s%c failed\n\t"
+ "𝑥 %016x\n\t"
+ "𝑦 %016x %c%c%c%c 0NPlODITSZKA1PVC\n\t"
+ "𝑥ₙ %016x %c%c%c%c %016b\n\t"
+ "𝑥ₚ %016x %c%c%c%c %016b\n",
+ file, line, func, opnames[ops[h]], kOpSuffix[w], x,
+ y, ".C"[c], ".Z"[z], ".S"[s], ".O"[o], xn,
+ ".C"[!!(f1 & (1 << FLAGS_CF))],
+ ".Z"[!!(f1 & (1 << FLAGS_ZF))],
+ ".S"[!!(f1 & (1 << FLAGS_SF))],
+ ".O"[!!(f1 & (1 << FLAGS_OF))], f1, xp,
+ ".C"[!!(f2 & (1 << FLAGS_CF))],
+ ".Z"[!!(f2 & (1 << FLAGS_ZF))],
+ ".S"[!!(f2 & (1 << FLAGS_SF))],
+ ".O"[!!(f2 & (1 << FLAGS_OF))], f2);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (failed) {
+ fprintf(stderr,
+ "\n"
+ "passing: %d%%\n"
+ "succeeded: %,ld\n"
+ "failed: %,ld\n"
+ "\n",
+ (int)((1 - (double)failed / succeeded) * 100), succeeded, failed);
+ exit(1);
+ }
+}
diff --git a/test/tool/build/lib/optest.h b/test/tool/build/lib/optest.h
new file mode 100644
index 000000000..b2ed6e1fc
--- /dev/null
+++ b/test/tool/build/lib/optest.h
@@ -0,0 +1,21 @@
+#ifndef COSMOPOLITAN_TEST_TOOL_BUILD_LIB_OPTEST_H_
+#define COSMOPOLITAN_TEST_TOOL_BUILD_LIB_OPTEST_H_
+#include "tool/build/lib/flags.h"
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+#define FMASK (1 << FLAGS_CF | 1 << FLAGS_ZF | 1 << FLAGS_SF | 1 << FLAGS_OF)
+
+void RunOpTests(const uint8_t *, size_t, const char *const *, const char *, int,
+ const char *);
+
+int64_t RunGolden(char, int, uint64_t, uint64_t, uint32_t *);
+int64_t RunOpTest(char, int, uint64_t, uint64_t, uint32_t *);
+void FixupUndefOpTestFlags(char, int, uint64_t, uint64_t, uint32_t, uint32_t *);
+
+#define RunOpTests(ops, n, names) \
+ RunOpTests(ops, n, names, __FILE__, __LINE__, __func__)
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_TEST_TOOL_BUILD_LIB_OPTEST_H_ */
diff --git a/test/tool/build/lib/pml4t_test.c b/test/tool/build/lib/pml4t_test.c
new file mode 100644
index 000000000..9d1b73ae1
--- /dev/null
+++ b/test/tool/build/lib/pml4t_test.c
@@ -0,0 +1,150 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/log/check.h"
+#include "libc/macros.h"
+#include "libc/mem/mem.h"
+#include "libc/stdio/stdio.h"
+#include "libc/testlib/ezbench.h"
+#include "libc/testlib/testlib.h"
+#include "tool/build/lib/memory.h"
+#include "tool/build/lib/pml4t.h"
+
+struct Unmapped {
+ size_t i;
+ struct UnmappedEntry {
+ void *addr;
+ size_t size;
+ } p[8];
+};
+
+struct Unmapped unmapped;
+uint64_t *pt2, *pt3, *pt4;
+pml4t_t cr3;
+
+void *NewPage(void) {
+ void *p;
+ p = tmemalign(4096, 4096);
+ CHECK_ALIGNED(4096, p);
+ memset(p, 0, 4096);
+ return p;
+}
+
+void FreePage(void *p) {
+ tfree(p);
+}
+
+int FakeMunmap(void *addr, size_t size) {
+ return 0;
+}
+
+int MockMunmap(void *addr, size_t size) {
+ CHECK_LT(unmapped.i, ARRAYLEN(unmapped.p));
+ unmapped.p[unmapped.i].addr = addr;
+ unmapped.p[unmapped.i].size = size;
+ unmapped.i++;
+ return 0;
+}
+
+void SetUp(void) {
+ memset(cr3, 0, sizeof(cr3));
+}
+
+void TearDown(void) {
+ unmapped.i = 0;
+ FreePml4t(cr3, -0x800000000000, 0x800000000000, FreePage, FakeMunmap);
+}
+
+TEST(pml4t, testHighestAddress) {
+ ASSERT_NE(-1,
+ RegisterPml4t(cr3, 0x7fffffffe000, 0x3133700000, 0x2000, NewPage));
+ ASSERT_TRUE(IsValidPage(cr3[255]));
+ pt2 = UnmaskPageAddr(cr3[255]);
+ ASSERT_TRUE(IsValidPage(pt2[511]));
+ pt3 = UnmaskPageAddr(pt2[511]);
+ ASSERT_TRUE(IsValidPage(pt3[511]));
+ pt4 = UnmaskPageAddr(pt3[511]);
+ ASSERT_TRUE(IsValidPage(pt4[510]));
+ ASSERT_TRUE(IsValidPage(pt4[511]));
+ EXPECT_EQ(0x3133700000, UnmaskPageAddr(pt4[510]));
+ EXPECT_EQ(0x3133701000, UnmaskPageAddr(pt4[511]));
+}
+
+TEST(pml4t, testLowestAddress) {
+ ASSERT_NE(-1,
+ RegisterPml4t(cr3, -0x800000000000, 0x31337000, 0x2000, NewPage));
+ ASSERT_TRUE(IsValidPage(cr3[256]));
+ pt2 = UnmaskPageAddr(cr3[256]);
+ ASSERT_TRUE(IsValidPage(pt2[0]));
+ pt3 = UnmaskPageAddr(pt2[0]);
+ ASSERT_TRUE(IsValidPage(pt3[0]));
+ pt4 = UnmaskPageAddr(pt3[0]);
+ ASSERT_TRUE(IsValidPage(pt4[0]));
+ ASSERT_TRUE(IsValidPage(pt4[1]));
+}
+
+TEST(pml4t, testOverlapsExistingRegistration_overwritesRegistration) {
+ ASSERT_NE(-1,
+ RegisterPml4t(cr3, 0x7fffffffe000, 0x31337000, 0x1000, NewPage));
+ ASSERT_TRUE(IsValidPage(cr3[255]));
+ pt2 = UnmaskPageAddr(cr3[255]);
+ ASSERT_TRUE(IsValidPage(pt2[511]));
+ pt3 = UnmaskPageAddr(pt2[511]);
+ ASSERT_TRUE(IsValidPage(pt3[511]));
+ pt4 = UnmaskPageAddr(pt3[511]);
+ EXPECT_TRUE(IsValidPage(pt4[510]));
+ EXPECT_EQ(0x31337000, UnmaskPageAddr(pt4[510]));
+ ASSERT_NE(-1, RegisterPml4t(cr3, 0x7fffffffe000, 0x31337000 + 0x1000, 0x1000,
+ NewPage));
+ EXPECT_TRUE(IsValidPage(pt4[510]));
+ EXPECT_EQ(0x31337000 + 0x1000, UnmaskPageAddr(pt4[510]));
+}
+
+TEST(pml4t, testFindPml4t_holeTooSmall_skipsOver) {
+ ASSERT_NE(-1, RegisterPml4t(cr3, 0x700000000, 0, 0x1000, NewPage));
+ ASSERT_NE(-1, RegisterPml4t(cr3, 0x700005000, 0, 0x1000, NewPage));
+ ASSERT_EQ(0x700001000, FindPml4t(cr3, 0x700000000, 0x01000, NewPage));
+ ASSERT_EQ(0x700006000, FindPml4t(cr3, 0x700000000, 0x10000, NewPage));
+}
+
+TEST(pml4t, testFreePmlt) {
+ ASSERT_NE(-1, RegisterPml4t(cr3, 0x000005000, 0x123000, 0x2000, NewPage));
+ ASSERT_NE(-1, RegisterPml4t(cr3, 0x000000000, 0x321000, 0x1000, NewPage));
+ ASSERT_NE(-1, FreePml4t(cr3, -0x800000000000, 0x800000000000, FreePage,
+ MockMunmap));
+ ASSERT_EQ(2, unmapped.i);
+ EXPECT_EQ(0x321000, unmapped.p[0].addr);
+ EXPECT_EQ(0x1000, unmapped.p[0].size);
+ EXPECT_EQ(0x123000, unmapped.p[1].addr);
+ EXPECT_EQ(0x2000, unmapped.p[1].size);
+}
+
+BENCH(pml4t, bench) {
+ EZBENCH2("RegisterPml4t 1mb fresh",
+ FreePml4t(cr3, -0x800000000000, 0x800000000000, free, FakeMunmap),
+ RegisterPml4t(cr3, 0x00100000, 0x31337000, 0x00100000, MallocPage));
+ EZBENCH2("RegisterPml4t 1gb fresh",
+ FreePml4t(cr3, -0x800000000000, 0x800000000000, free, FakeMunmap),
+ RegisterPml4t(cr3, 0x40000000, 0x31337000, 0x40000000, MallocPage));
+ EZBENCH2("RegisterPml4t 1mb overwrite", donothing,
+ RegisterPml4t(cr3, 0x00100000, 0x31337000, 0x00100000, MallocPage));
+ EZBENCH2("RegisterPml4t 1gb overwrite", donothing,
+ RegisterPml4t(cr3, 0x40000000, 0x31337000, 0x40000000, MallocPage));
+ FreePml4t(cr3, -0x800000000000, 0x800000000000, free, FakeMunmap);
+}
diff --git a/test/tool/build/lib/test.mk b/test/tool/build/lib/test.mk
index 886990be5..88a00835f 100644
--- a/test/tool/build/lib/test.mk
+++ b/test/tool/build/lib/test.mk
@@ -3,14 +3,21 @@
PKGS += TEST_TOOL_BUILD_LIB
-TEST_TOOL_BUILD_LIB_SRCS := $(wildcard test/tool/build/lib/*.c)
+TEST_TOOL_BUILD_LIB = $(TOOL_BUILD_LIB_A_DEPS) $(TOOL_BUILD_LIB_A)
+TEST_TOOL_BUILD_LIB_A = o/$(MODE)/test/tool/build/lib/buildlib.a
+TEST_TOOL_BUILD_LIB_FILES := $(wildcard test/tool/build/lib/*)
+TEST_TOOL_BUILD_LIB_SRCS = $(filter %.c,$(TEST_TOOL_BUILD_LIB_FILES))
TEST_TOOL_BUILD_LIB_SRCS_TEST = $(filter %_test.c,$(TEST_TOOL_BUILD_LIB_SRCS))
+TEST_TOOL_BUILD_LIB_HDRS = $(filter %.h,$(TEST_TOOL_BUILD_LIB_FILES))
TEST_TOOL_BUILD_LIB_COMS = $(TEST_TOOL_BUILD_LIB_OBJS:%.o=%.com)
TEST_TOOL_BUILD_LIB_OBJS = \
$(TEST_TOOL_BUILD_LIB_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_TOOL_BUILD_LIB_SRCS:%.c=o/$(MODE)/%.o)
+TEST_TOOL_BUILD_LIB_COMS = \
+ $(TEST_TOOL_BUILD_LIB_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_TOOL_BUILD_LIB_BINS = \
$(TEST_TOOL_BUILD_LIB_COMS) \
$(TEST_TOOL_BUILD_LIB_COMS:%=%.dbg)
@@ -19,34 +26,49 @@ TEST_TOOL_BUILD_LIB_TESTS = \
$(TEST_TOOL_BUILD_LIB_SRCS_TEST:%.c=o/$(MODE)/%.com.ok)
TEST_TOOL_BUILD_LIB_CHECKS = \
+ $(TEST_TOOL_BUILD_LIB_HDRS:%=o/$(MODE)/%.ok) \
$(TEST_TOOL_BUILD_LIB_SRCS_TEST:%.c=o/$(MODE)/%.com.runs)
TEST_TOOL_BUILD_LIB_DIRECTDEPS = \
LIBC_X \
+ LIBC_CALLS \
LIBC_MEM \
LIBC_NEXGEN32E \
LIBC_RUNTIME \
+ LIBC_FMT \
+ LIBC_STDIO \
+ LIBC_LOG \
+ LIBC_SYSV \
LIBC_STUBS \
+ LIBC_UNICODE \
LIBC_TESTLIB \
- TOOL_BUILD_LIB
+ TOOL_BUILD_LIB \
+ THIRD_PARTY_COMPILER_RT \
+ THIRD_PARTY_XED
TEST_TOOL_BUILD_LIB_DEPS := \
$(call uniq,$(foreach x,$(TEST_TOOL_BUILD_LIB_DIRECTDEPS),$($(x))))
-o/$(MODE)/test/tool/build/lib/buildlib.pkg: \
+$(TEST_TOOL_BUILD_LIB_A): \
+ test/tool/build/lib/ \
+ $(TEST_TOOL_BUILD_LIB_A).pkg \
+ $(TEST_TOOL_BUILD_LIB_OBJS)
+
+$(TEST_TOOL_BUILD_LIB_A).pkg: \
$(TEST_TOOL_BUILD_LIB_OBJS) \
$(foreach x,$(TEST_TOOL_BUILD_LIB_DIRECTDEPS),$($(x)_A).pkg)
o/$(MODE)/test/tool/build/lib/%.com.dbg: \
$(TEST_TOOL_BUILD_LIB_DEPS) \
+ $(TEST_TOOL_BUILD_LIB_A) \
o/$(MODE)/test/tool/build/lib/%.o \
- o/$(MODE)/test/tool/build/lib/buildlib.pkg \
+ $(TEST_TOOL_BUILD_LIB_A).pkg \
$(LIBC_TESTMAIN) \
$(CRT) \
$(APE)
@$(APELINK)
-.PHONY: o/$(MODE)/test/tool/build/lib
+.PHONY: o/$(MODE)/test/tool/build/lib
o/$(MODE)/test/tool/build/lib: \
$(TEST_TOOL_BUILD_LIB_BINS) \
$(TEST_TOOL_BUILD_LIB_CHECKS)
diff --git a/test/tool/build/lib/x87_test.c b/test/tool/build/lib/x87_test.c
new file mode 100644
index 000000000..672d09754
--- /dev/null
+++ b/test/tool/build/lib/x87_test.c
@@ -0,0 +1,39 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/testlib/testlib.h"
+#include "tool/build/lib/x87.h"
+
+TEST(x87, fprem) {
+ ASSERT_LDBL_EQ(1, fprem(1, -1.5, NULL));
+ ASSERT_LDBL_EQ(1.1766221079117338e-14L,
+ fprem(12300000000000000.L, .0000000000000123L, NULL));
+}
+
+TEST(x87, fprem1) {
+ ASSERT_LDBL_EQ(-.5, fprem1(1, -1.5, NULL));
+ ASSERT_LDBL_EQ(-5.337789208826618e-16,
+ fprem1(12300000000000000.L, .0000000000000123L, NULL));
+}
+
+TEST(x87, fpremFlags) {
+ uint32_t sw = 0xffff;
+ ASSERT_LDBL_EQ(1, fprem(1, -1.5, &sw));
+ ASSERT_EQ(0b1011100011111111, sw);
+}
diff --git a/test/tool/build/lib/xlaterrno_test.c b/test/tool/build/lib/xlaterrno_test.c
new file mode 100644
index 000000000..4cd4768e0
--- /dev/null
+++ b/test/tool/build/lib/xlaterrno_test.c
@@ -0,0 +1,28 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2020 Justine Alexandra Roberts Tunney │
+│ │
+│ This program is free software; you can redistribute it and/or modify │
+│ it under the terms of the GNU General Public License as published by │
+│ the Free Software Foundation; version 2 of the License. │
+│ │
+│ This program is distributed in the hope that it will be useful, but │
+│ WITHOUT ANY WARRANTY; without even the implied warranty of │
+│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
+│ General Public License for more details. │
+│ │
+│ You should have received a copy of the GNU General Public License │
+│ along with this program; if not, write to the Free Software │
+│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
+│ 02110-1301 USA │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/errno.h"
+#include "libc/testlib/testlib.h"
+#include "tool/build/lib/xlaterrno.h"
+
+TEST(xlaterrno, test) {
+ EXPECT_EQ(95, XlatErrno(EOPNOTSUPP));
+ EXPECT_EQ(90, XlatErrno(EMSGSIZE));
+ EXPECT_EQ(133, XlatErrno(EHWPOISON));
+}
diff --git a/test/tool/viz/lib/test.mk b/test/tool/viz/lib/test.mk
index 631862fb6..252447054 100644
--- a/test/tool/viz/lib/test.mk
+++ b/test/tool/viz/lib/test.mk
@@ -5,12 +5,14 @@ PKGS += TEST_TOOL_VIZ_LIB
TEST_TOOL_VIZ_LIB_SRCS := $(wildcard test/tool/viz/lib/*.c)
TEST_TOOL_VIZ_LIB_SRCS_TEST = $(filter %_test.c,$(TEST_TOOL_VIZ_LIB_SRCS))
-TEST_TOOL_VIZ_LIB_COMS = $(TEST_TOOL_VIZ_LIB_OBJS:%.o=%.com)
TEST_TOOL_VIZ_LIB_OBJS = \
$(TEST_TOOL_VIZ_LIB_SRCS:%=o/$(MODE)/%.zip.o) \
$(TEST_TOOL_VIZ_LIB_SRCS:%.c=o/$(MODE)/%.o)
+TEST_TOOL_VIZ_LIB_COMS = \
+ $(TEST_TOOL_VIZ_LIB_SRCS:%.c=o/$(MODE)/%.com)
+
TEST_TOOL_VIZ_LIB_BINS = \
$(TEST_TOOL_VIZ_LIB_COMS) \
$(TEST_TOOL_VIZ_LIB_COMS:%=%.dbg)
diff --git a/third_party/bzip2/bzip2.mk b/third_party/bzip2/bzip2.mk
index a2461c931..f5a51785f 100644
--- a/third_party/bzip2/bzip2.mk
+++ b/third_party/bzip2/bzip2.mk
@@ -27,9 +27,7 @@ o/$(MODE)/third_party/bzip2/µbunzip2.com.dbg: \
$(APE)
@$(APELINK)
-$(THIRD_PARTY_BZIP2_OBJS): \
- $(BUILD_FILES) \
- third_party/bzip2/bzip2.mk
+$(THIRD_PARTY_BZIP2_OBJS): third_party/bzip2/bzip2.mk
.PHONY: o/$(MODE)/third_party/bzip2
o/$(MODE)/third_party/bzip2: $(THIRD_PARTY_BZIP2_BINS)
diff --git a/third_party/compiler_rt/compiler_rt.mk b/third_party/compiler_rt/compiler_rt.mk
index 33f162929..369923366 100644
--- a/third_party/compiler_rt/compiler_rt.mk
+++ b/third_party/compiler_rt/compiler_rt.mk
@@ -43,16 +43,13 @@ $(THIRD_PARTY_COMPILER_RT_A).pkg: \
$(foreach x,$(THIRD_PARTY_COMPILER_RT_A_DIRECTDEPS),$($(x)_A).pkg)
$(THIRD_PARTY_COMPILER_RT_A_OBJS): \
- DEFAULT_COPTS += \
- -DCRT_HAS_128BIT
-
-o/$(MODE)/third_party/compiler_rt/multc3.o \
-o/$(MODE)/third_party/compiler_rt/divtc3.o: \
- DEFAULT_COPTS += \
- -w
+ DEFAULT_CFLAGS += \
+ $(OLD_CODE) \
+ -DCRT_HAS_128BIT
THIRD_PARTY_COMPILER_RT_LIBS = $(foreach x,$(THIRD_PARTY_COMPILER_RT_ARTIFACTS),$($(x)))
THIRD_PARTY_COMPILER_RT_SRCS = $(foreach x,$(THIRD_PARTY_COMPILER_RT_ARTIFACTS),$($(x)_SRCS))
+THIRD_PARTY_COMPILER_RT_HDRS = $(foreach x,$(THIRD_PARTY_COMPILER_RT_ARTIFACTS),$($(x)_HDRS))
THIRD_PARTY_COMPILER_RT_CHECKS = $(foreach x,$(THIRD_PARTY_COMPILER_RT_ARTIFACTS),$($(x)_CHECKS))
THIRD_PARTY_COMPILER_RT_OBJS = $(foreach x,$(THIRD_PARTY_COMPILER_RT_ARTIFACTS),$($(x)_OBJS))
diff --git a/third_party/compiler_rt/divmodti4.c b/third_party/compiler_rt/divmodti4.c
new file mode 100644
index 000000000..4816b8e17
--- /dev/null
+++ b/third_party/compiler_rt/divmodti4.c
@@ -0,0 +1,64 @@
+#if 0
+/*─────────────────────────────────────────────────────────────────╗
+│ To the extent possible under law, Justine Tunney has waived │
+│ all copyright and related or neighboring rights to division, │
+│ as it is written in the following disclaimers: │
+│ • http://unlicense.org/ │
+│ • http://creativecommons.org/publicdomain/zero/1.0/ │
+╚─────────────────────────────────────────────────────────────────*/
+#endif
+#include "libc/calls/calls.h"
+#include "third_party/compiler_rt/int_lib.h"
+
+/**
+ * Divides 128-bit signed integers w/ remainder.
+ *
+ * @param a is numerator
+ * @param b is denominator
+ * @param opt_out_rem receives euclidean division remainder if not null
+ * @return quotient or result of division
+ * @note rounds towards zero
+ */
+COMPILER_RT_ABI ti_int __divmodti4(ti_int a, ti_int b, tu_int *opt_out_rem) {
+ int k;
+ tu_int r;
+ ti_int sa, sb, sq, sr, x, y, q;
+ k = sizeof(ti_int) * CHAR_BIT - 1;
+ if (b < 0 && a == ((ti_int)1 << k)) {
+ volatile int x = 0;
+ x = 1 / x; // raise(SIGFPE)
+ }
+ sa = a >> k; // sa = a < 0 ? -1 : 0
+ sb = b >> k; // sb = b < 0 ? -1 : 0
+ x = (a ^ sa) - sa; // negate if sa == -1
+ y = (b ^ sb) - sb; // negate if sb == -1
+ sq = sa ^ sb; // sign of quotient
+ sr = sa; // sign of remainder
+ q = __udivmodti4(x, y, &r); // unsigned divide
+ q = (q ^ sq) - sq; // fix quotient sign
+ r = (r ^ sr) - sr; // fix remainder sign
+ if (opt_out_rem) *opt_out_rem = r;
+ return q;
+}
+
+/*
+
+ Intel Kabylake i9-9900 @ 3.10GHz Client Grade
+
+ idiv32 l: 27𝑐 9𝑛𝑠
+ idiv64 l: 27𝑐 9𝑛𝑠
+ divmodti4 small / small l: 42𝑐 14𝑛𝑠
+ divmodti4 small / large l: 14𝑐 5𝑛𝑠
+ divmodti4 large / small l: 92𝑐 30𝑛𝑠
+ divmodti4 large / large l: 209𝑐 68𝑛𝑠
+
+ Intel Kabylake i3-8100 @ 3.60GHz Client Grade
+
+ idiv32 l: 51𝑐 14𝑛𝑠
+ idiv64 l: 51𝑐 14𝑛𝑠
+ divmodti4 small / small l: 83𝑐 23𝑛𝑠
+ divmodti4 small / large l: 26𝑐 7𝑛𝑠
+ divmodti4 large / small l: 175𝑐 48𝑛𝑠
+ divmodti4 large / large l: 389𝑐 107𝑛𝑠
+
+*/
diff --git a/third_party/compiler_rt/divti3.c b/third_party/compiler_rt/divti3.c
index 48bbe58a2..b2bbc7aaa 100644
--- a/third_party/compiler_rt/divti3.c
+++ b/third_party/compiler_rt/divti3.c
@@ -1,36 +1,22 @@
-/* clang-format off */
-/* ===-- divti3.c - Implement __divti3 -------------------------------------===
- *
- * The LLVM Compiler Infrastructure
- *
- * This file is dual licensed under the MIT and the University of Illinois Open
- * Source Licenses. See LICENSE.TXT for details.
- *
- * ===----------------------------------------------------------------------===
- *
- * This file implements __divti3 for the compiler_rt library.
- *
- * ===----------------------------------------------------------------------===
- */
-
-STATIC_YOINK("huge_compiler_rt_license");
-
+#if 0
+/*─────────────────────────────────────────────────────────────────╗
+│ To the extent possible under law, Justine Tunney has waived │
+│ all copyright and related or neighboring rights to division, │
+│ as it is written in the following disclaimers: │
+│ • http://unlicense.org/ │
+│ • http://creativecommons.org/publicdomain/zero/1.0/ │
+╚─────────────────────────────────────────────────────────────────*/
+#endif
#include "third_party/compiler_rt/int_lib.h"
-#ifdef CRT_HAS_128BIT
-
-/* Returns: a / b */
-
-COMPILER_RT_ABI ti_int
-__divti3(ti_int a, ti_int b)
-{
- const int bits_in_tword_m1 = (int)(sizeof(ti_int) * CHAR_BIT) - 1;
- ti_int s_a = a >> bits_in_tword_m1; /* s_a = a < 0 ? -1 : 0 */
- ti_int s_b = b >> bits_in_tword_m1; /* s_b = b < 0 ? -1 : 0 */
- a = (a ^ s_a) - s_a; /* negate if s_a == -1 */
- b = (b ^ s_b) - s_b; /* negate if s_b == -1 */
- s_a ^= s_b; /* sign of quotient */
- return (__udivmodti4(a, b, (tu_int*)0) ^ s_a) - s_a; /* negate if s_a == -1 */
+/**
+ * Divides 128-bit signed integers.
+ *
+ * @param a is numerator
+ * @param b is denominator
+ * @return quotient or result of division
+ * @note rounds towards zero
+ */
+COMPILER_RT_ABI ti_int __divti3(ti_int a, ti_int b) {
+ return __divmodti4(a, b, NULL);
}
-
-#endif /* CRT_HAS_128BIT */
diff --git a/third_party/compiler_rt/int_lib.h b/third_party/compiler_rt/int_lib.h
index 9e80c3a65..65e84f780 100644
--- a/third_party/compiler_rt/int_lib.h
+++ b/third_party/compiler_rt/int_lib.h
@@ -16,6 +16,7 @@
#ifndef INT_LIB_H
#define INT_LIB_H
+#define CRT_HAS_128BIT 1
/* Assumption: lool univac arithmetic */
/* Assumption: lool cray signed shift */
@@ -74,6 +75,7 @@ COMPILER_RT_ABI su_int __udivmodsi4(su_int a, su_int b, su_int* rem);
COMPILER_RT_ABI du_int __udivmoddi4(du_int a, du_int b, du_int* rem);
#ifdef CRT_HAS_128BIT
COMPILER_RT_ABI si_int __clzti2(ti_int a);
+COMPILER_RT_ABI ti_int __divmodti4(ti_int a, ti_int b, tu_int *rem);
COMPILER_RT_ABI tu_int __udivmodti4(tu_int a, tu_int b, tu_int* rem);
#endif
diff --git a/third_party/compiler_rt/udivmodti4.c b/third_party/compiler_rt/udivmodti4.c
index e0109aa8d..5acefffdb 100644
--- a/third_party/compiler_rt/udivmodti4.c
+++ b/third_party/compiler_rt/udivmodti4.c
@@ -1,241 +1,137 @@
-/* clang-format off */
-/* ===-- udivmodti4.c - Implement __udivmodti4 -----------------------------===
- *
- * The LLVM Compiler Infrastructure
- *
- * This file is dual licensed under the MIT and the University of Illinois Open
- * Source Licenses. See LICENSE.TXT for details.
- *
- * ===----------------------------------------------------------------------===
- *
- * This file implements __udivmodti4 for the compiler_rt library.
- *
- * ===----------------------------------------------------------------------===
- */
-
-STATIC_YOINK("huge_compiler_rt_license");
-
+#if 0
+/*─────────────────────────────────────────────────────────────────╗
+│ To the extent possible under law, Justine Tunney has waived │
+│ all copyright and related or neighboring rights to division, │
+│ as it is written in the following disclaimers: │
+│ • http://unlicense.org/ │
+│ • http://creativecommons.org/publicdomain/zero/1.0/ │
+╚─────────────────────────────────────────────────────────────────*/
+#endif
#include "third_party/compiler_rt/int_lib.h"
-#ifdef CRT_HAS_128BIT
-
-/* Effects: if rem != 0, *rem = a % b
- * Returns: a / b
+/**
+ * Returns 128 bit division result by 64 bit.
+ *
+ * Result must fit in 64 bits. Remainder is stored in r.
+ *
+ * @see libdivide libdivide_128_div_64_to_64() division fallback
+ * @see Knuth, Volume 2, section 4.3.1, Algorithm D for correctness proof
+ * @see https://danlark.org/2020/06/14/128-bit-division/
*/
-
-/* Translated from Figure 3-40 of The PowerPC Compiler Writer's Guide */
-
-COMPILER_RT_ABI tu_int
-__udivmodti4(tu_int a, tu_int b, tu_int* rem)
-{
- const unsigned n_udword_bits = sizeof(du_int) * CHAR_BIT;
- const unsigned n_utword_bits = sizeof(tu_int) * CHAR_BIT;
- utwords n;
- n.all = a;
- utwords d;
- d.all = b;
- utwords q;
- utwords r;
- unsigned sr;
- /* special cases, X is unknown, K != 0 */
- if (n.s.high == 0)
- {
- if (d.s.high == 0)
- {
- /* 0 X
- * ---
- * 0 X
- */
- if (rem)
- *rem = n.s.low % d.s.low;
- return n.s.low / d.s.low;
- }
- /* 0 X
- * ---
- * K X
- */
- if (rem)
- *rem = n.s.low;
- return 0;
- }
- /* n.s.high != 0 */
- if (d.s.low == 0)
- {
- if (d.s.high == 0)
- {
- /* K X
- * ---
- * 0 0
- */
- if (rem)
- *rem = n.s.high % d.s.low;
- return n.s.high / d.s.low;
- }
- /* d.s.high != 0 */
- if (n.s.low == 0)
- {
- /* K 0
- * ---
- * K 0
- */
- if (rem)
- {
- r.s.high = n.s.high % d.s.high;
- r.s.low = 0;
- *rem = r.all;
- }
- return n.s.high / d.s.high;
- }
- /* K K
- * ---
- * K 0
- */
- if ((d.s.high & (d.s.high - 1)) == 0) /* if d is a power of 2 */
- {
- if (rem)
- {
- r.s.low = n.s.low;
- r.s.high = n.s.high & (d.s.high - 1);
- *rem = r.all;
- }
- return n.s.high >> __builtin_ctzll(d.s.high);
- }
- /* K K
- * ---
- * K 0
- */
- sr = __builtin_clzll(d.s.high) - __builtin_clzll(n.s.high);
- /* 0 <= sr <= n_udword_bits - 2 or sr large */
- if (sr > n_udword_bits - 2)
- {
- if (rem)
- *rem = n.all;
- return 0;
- }
- ++sr;
- /* 1 <= sr <= n_udword_bits - 1 */
- /* q.all = n.all << (n_utword_bits - sr); */
- q.s.low = 0;
- q.s.high = n.s.low << (n_udword_bits - sr);
- /* r.all = n.all >> sr; */
- r.s.high = n.s.high >> sr;
- r.s.low = (n.s.high << (n_udword_bits - sr)) | (n.s.low >> sr);
- }
- else /* d.s.low != 0 */
- {
- if (d.s.high == 0)
- {
- /* K X
- * ---
- * 0 K
- */
- if ((d.s.low & (d.s.low - 1)) == 0) /* if d is a power of 2 */
- {
- if (rem)
- *rem = n.s.low & (d.s.low - 1);
- if (d.s.low == 1)
- return n.all;
- sr = __builtin_ctzll(d.s.low);
- q.s.high = n.s.high >> sr;
- q.s.low = (n.s.high << (n_udword_bits - sr)) | (n.s.low >> sr);
- return q.all;
- }
- /* K X
- * ---
- * 0 K
- */
- sr = 1 + n_udword_bits + __builtin_clzll(d.s.low)
- - __builtin_clzll(n.s.high);
- /* 2 <= sr <= n_utword_bits - 1
- * q.all = n.all << (n_utword_bits - sr);
- * r.all = n.all >> sr;
- */
- if (sr == n_udword_bits)
- {
- q.s.low = 0;
- q.s.high = n.s.low;
- r.s.high = 0;
- r.s.low = n.s.high;
- }
- else if (sr < n_udword_bits) // 2 <= sr <= n_udword_bits - 1
- {
- q.s.low = 0;
- q.s.high = n.s.low << (n_udword_bits - sr);
- r.s.high = n.s.high >> sr;
- r.s.low = (n.s.high << (n_udword_bits - sr)) | (n.s.low >> sr);
- }
- else // n_udword_bits + 1 <= sr <= n_utword_bits - 1
- {
- q.s.low = n.s.low << (n_utword_bits - sr);
- q.s.high = (n.s.high << (n_utword_bits - sr)) |
- (n.s.low >> (sr - n_udword_bits));
- r.s.high = 0;
- r.s.low = n.s.high >> (sr - n_udword_bits);
- }
- }
- else
- {
- /* K X
- * ---
- * K K
- */
- sr = __builtin_clzll(d.s.high) - __builtin_clzll(n.s.high);
- /*0 <= sr <= n_udword_bits - 1 or sr large */
- if (sr > n_udword_bits - 1)
- {
- if (rem)
- *rem = n.all;
- return 0;
- }
- ++sr;
- /* 1 <= sr <= n_udword_bits
- * q.all = n.all << (n_utword_bits - sr);
- * r.all = n.all >> sr;
- */
- q.s.low = 0;
- if (sr == n_udword_bits)
- {
- q.s.high = n.s.low;
- r.s.high = 0;
- r.s.low = n.s.high;
- }
- else
- {
- r.s.high = n.s.high >> sr;
- r.s.low = (n.s.high << (n_udword_bits - sr)) | (n.s.low >> sr);
- q.s.high = n.s.low << (n_udword_bits - sr);
- }
- }
- }
- /* Not a special case
- * q and r are initialized with:
- * q.all = n.all << (n_utword_bits - sr);
- * r.all = n.all >> sr;
- * 1 <= sr <= n_utword_bits - 1
- */
- su_int carry = 0;
- for (; sr > 0; --sr)
- {
- /* r:q = ((r:q) << 1) | carry */
- r.s.high = (r.s.high << 1) | (r.s.low >> (n_udword_bits - 1));
- r.s.low = (r.s.low << 1) | (q.s.high >> (n_udword_bits - 1));
- q.s.high = (q.s.high << 1) | (q.s.low >> (n_udword_bits - 1));
- q.s.low = (q.s.low << 1) | carry;
- /* carry = 0;
- * if (r.all >= d.all)
- * {
- * r.all -= d.all;
- * carry = 1;
- * }
- */
- const ti_int s = (ti_int)(d.all - r.all - 1) >> (n_utword_bits - 1);
- carry = s & 1;
- r.all -= d.all & s;
- }
- q.all = (q.all << 1) | carry;
- if (rem)
- *rem = r.all;
- return q.all;
+forceinline du_int udiv128by64to64default(du_int u1, du_int u0, du_int v,
+ du_int *r) {
+ const unsigned n_udword_bits = sizeof(du_int) * CHAR_BIT;
+ const du_int b = 1ULL << (n_udword_bits / 2); // Number base (32 bits)
+ du_int un1, un0; // Norm. dividend LSD's
+ du_int vn1, vn0; // Norm. divisor digits
+ du_int q1, q0; // Quotient digits
+ du_int un64, un21, un10; // Dividend digit pairs
+ du_int rhat; // Remainder
+ si_int s; // Normalization shift
+ s = __builtin_clzll(v);
+ if (s > 0) {
+ // Normalize the divisor.
+ v = v << s;
+ un64 = (u1 << s) | (u0 >> (n_udword_bits - s));
+ un10 = u0 << s; // Shift dividend left
+ } else {
+ // Avoid undefined behavior of (u0 >> 64).
+ un64 = u1;
+ un10 = u0;
+ }
+ // Break divisor up into two 32-bit digits.
+ vn1 = v >> (n_udword_bits / 2);
+ vn0 = v & 0xFFFFFFFF;
+ // Break right half of dividend into two digits.
+ un1 = un10 >> (n_udword_bits / 2);
+ un0 = un10 & 0xFFFFFFFF;
+ // Compute the first quotient digit, q1.
+ q1 = un64 / vn1;
+ rhat = un64 - q1 * vn1;
+ // q1 has at most error 2. No more than 2 iterations.
+ while (q1 >= b || q1 * vn0 > b * rhat + un1) {
+ q1 = q1 - 1;
+ rhat = rhat + vn1;
+ if (rhat >= b) break;
+ }
+ un21 = un64 * b + un1 - q1 * v;
+ // Compute the second quotient digit.
+ q0 = un21 / vn1;
+ rhat = un21 - q0 * vn1;
+ // q0 has at most error 2. No more than 2 iterations.
+ while (q0 >= b || q0 * vn0 > b * rhat + un0) {
+ q0 = q0 - 1;
+ rhat = rhat + vn1;
+ if (rhat >= b) break;
+ }
+ *r = (un21 * b + un0 - q0 * v) >> s;
+ return q1 * b + q0;
}
-#endif /* CRT_HAS_128BIT */
+forceinline du_int udiv128by64to64(du_int u1, du_int u0, du_int v, du_int *r) {
+#ifdef __x86_64__
+ du_int result;
+ asm("div\t%2" : "=a"(result), "=d"(*r) : "r"(v), "a"(u0), "d"(u1) : "cc");
+ return result;
+#else
+ return udiv128by64to64default(u1, u0, v, r);
+#endif
+}
+
+/**
+ * Performs 128-bit unsigned division and remainder.
+ *
+ * @param a is dividend
+ * @param b is divisor
+ * @param rem receives remainder if not NULL
+ */
+COMPILER_RT_ABI tu_int __udivmodti4(tu_int a, tu_int b, tu_int *rem) {
+ const unsigned n_utword_bits = sizeof(tu_int) * CHAR_BIT;
+ utwords dividend, divisor, quotient, remainder;
+ si_int shift;
+ dividend.all = a;
+ divisor.all = b;
+ if (divisor.all > dividend.all) {
+ if (rem) *rem = dividend.all;
+ return 0;
+ }
+ // When the divisor fits in 64 bits, we can use an optimized path.
+ if (divisor.s.high == 0) {
+ remainder.s.high = 0;
+ if (dividend.s.high < divisor.s.low) {
+ // The result fits in 64 bits.
+ quotient.s.low = udiv128by64to64(dividend.s.high, dividend.s.low,
+ divisor.s.low, &remainder.s.low);
+ quotient.s.high = 0;
+ } else {
+ // First, divide with the high part to get the remainder in
+ // dividend.s.high. After that dividend.s.high < divisor.s.low.
+ quotient.s.high = dividend.s.high / divisor.s.low;
+ dividend.s.high = dividend.s.high % divisor.s.low;
+ quotient.s.low = udiv128by64to64(dividend.s.high, dividend.s.low,
+ divisor.s.low, &remainder.s.low);
+ }
+ if (rem) *rem = remainder.all;
+ return quotient.all;
+ }
+ // 0 <= shift <= 63.
+ shift = __builtin_clzll(divisor.s.high) - __builtin_clzll(dividend.s.high);
+ divisor.all <<= shift;
+ quotient.s.high = 0;
+ quotient.s.low = 0;
+ for (; shift >= 0; --shift) {
+ quotient.s.low <<= 1;
+ // Branch free version of.
+ // if (dividend.all >= divisor.all)
+ // {
+ // dividend.all -= divisor.all;
+ // carry = 1;
+ // }
+ ti_int s = (ti_int)(divisor.all - dividend.all - 1) >> (n_utword_bits - 1);
+ quotient.s.low |= s & 1;
+ dividend.all -= divisor.all & s;
+ divisor.all >>= 1;
+ }
+ if (rem) *rem = dividend.all;
+ return quotient.all;
+}
diff --git a/third_party/compiler_rt/udivti3.c b/third_party/compiler_rt/udivti3.c
index f4a6b3626..8aa79bd30 100644
--- a/third_party/compiler_rt/udivti3.c
+++ b/third_party/compiler_rt/udivti3.c
@@ -1,30 +1,14 @@
-/* clang-format off */
-/* ===-- udivti3.c - Implement __udivti3 -----------------------------------===
- *
- * The LLVM Compiler Infrastructure
- *
- * This file is dual licensed under the MIT and the University of Illinois Open
- * Source Licenses. See LICENSE.TXT for details.
- *
- * ===----------------------------------------------------------------------===
- *
- * This file implements __udivti3 for the compiler_rt library.
- *
- * ===----------------------------------------------------------------------===
- */
-
-STATIC_YOINK("huge_compiler_rt_license");
-
+#if 0
+/*─────────────────────────────────────────────────────────────────╗
+│ To the extent possible under law, Justine Tunney has waived │
+│ all copyright and related or neighboring rights to division, │
+│ as it is written in the following disclaimers: │
+│ • http://unlicense.org/ │
+│ • http://creativecommons.org/publicdomain/zero/1.0/ │
+╚─────────────────────────────────────────────────────────────────*/
+#endif
#include "third_party/compiler_rt/int_lib.h"
-#ifdef CRT_HAS_128BIT
-
-/* Returns: a / b */
-
-COMPILER_RT_ABI tu_int
-__udivti3(tu_int a, tu_int b)
-{
- return __udivmodti4(a, b, 0);
+COMPILER_RT_ABI tu_int __udivti3(tu_int a, tu_int b) {
+ return __udivmodti4(a, b, NULL);
}
-
-#endif /* CRT_HAS_128BIT */
diff --git a/third_party/ctags/COPYING b/third_party/ctags/COPYING
new file mode 100644
index 000000000..60549be51
--- /dev/null
+++ b/third_party/ctags/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C) 19yy
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/third_party/ctags/README.txt b/third_party/ctags/README.txt
new file mode 100644
index 000000000..9ef1ccebd
--- /dev/null
+++ b/third_party/ctags/README.txt
@@ -0,0 +1,1195 @@
+CTAGS(1) Exuberant Ctags CTAGS(1)
+
+
+
+𝐍𝐀𝐌𝐄
+ ctags - Generate tag files for source code
+
+
+
+𝐒𝐘𝐍𝐎𝐏𝐒𝐈𝐒
+ 𝗰𝘁𝗮𝗴𝘀 [𝗼𝗽𝘁𝗶𝗼𝗻𝘀] [f̲i̲l̲e̲(̲s̲)̲]
+
+ 𝗲𝘁𝗮𝗴𝘀 [𝗼𝗽𝘁𝗶𝗼𝗻𝘀] [f̲i̲l̲e̲(̲s̲)̲]
+
+
+
+𝐃𝐄𝐒𝐂𝐑𝐈𝐏𝐓𝐈𝐎𝐍
+ The 𝗰𝘁𝗮𝗴𝘀 and 𝗲𝘁𝗮𝗴𝘀 programs (hereinafter collectively referred
+ to as 𝗰𝘁𝗮𝗴𝘀, except where distinguished) generate an index (or
+ "tag") file for a variety of language objects found in f̲i̲l̲e̲(̲s̲)̲.
+ This tag file allows these items to be quickly and easily located
+ by a text editor or other utility. A "tag" signifies a language
+ object for which an index entry is available (or, alternatively,
+ the index entry created for that object).
+
+ Alternatively, 𝗰𝘁𝗮𝗴𝘀 can generate a cross reference file which
+ lists, in human readable form, information about the various
+ source objects found in a set of language files.
+
+ Tag index files are supported by numerous editors, which allow
+ the user to locate the object associated with a name appearing in
+ a source file and jump to the file and line which defines the
+ name. Those known about at the time of this release are:
+
+ 𝐕𝗶(1) and its derivatives (e.g. Elvis, Vim, Vile, Lemmy),
+ 𝐂𝐑𝗶𝐒𝐏, 𝐄𝗺𝗮𝗰𝘀, 𝐅𝐓𝐄 (Folding Text Editor), 𝐉𝐄𝐃, 𝗷𝐄𝗱𝗶𝘁, 𝐌𝗶𝗻𝗲𝗱,
+ 𝐍𝐄𝗱𝗶𝘁 (Nirvana Edit), 𝐓𝐒𝐄 (The SemWare Editor), 𝐔𝗹𝘁𝗿𝗮𝐄𝗱𝗶𝘁,
+ 𝐖𝗼𝗿𝗸𝐒𝗽𝗮𝗰𝗲, 𝐗𝟮, 𝐙𝗲𝘂𝘀
+
+ 𝐂𝘁𝗮𝗴𝘀 is capable of generating different kinds of tags for each
+ of many different languages. For a complete list of supported
+ languages, the names by which they are recognized, and the kinds
+ of tags which are generated for each, see the --𝗹𝗶𝘀𝘁-𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀
+ and --𝗹𝗶𝘀𝘁-𝗸𝗶𝗻𝗱𝘀 options.
+
+
+
+𝐒𝐎𝐔𝐑𝐂𝐄 𝐅𝐈𝐋𝐄𝐒
+ Unless the --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲-𝗳𝗼𝗿𝗰𝗲 option is specified, the language of
+ each source file is automatically selected based upon a mapping
+ of file names to languages. The mappings in effect for each lan‐
+ guage may be display using the --𝗹𝗶𝘀𝘁-𝗺𝗮𝗽𝘀 option and may be
+ changed using the --𝗹𝗮𝗻𝗴𝗺𝗮𝗽 option. On platforms which support
+ it, if the name of a file is not mapped to a language and the
+ file is executable, the first line of the file is checked to see
+ if the file is a "#!" script for a recognized language.
+
+ By default, all other files names are ignored. This permits run‐
+ ning 𝗰𝘁𝗮𝗴𝘀 on all files in either a single directory (e.g. "ctags
+ *"), or on all files in an entire source directory tree (e.g.
+ "ctags -R"), since only those files whose names are mapped to
+ languages will be scanned.
+
+ [The reason that .h extensions are mapped to C++ files rather
+ than C files is because it is common to use .h extensions in C++,
+ and no harm results in treating them as C++ files.]
+
+
+
+𝐎𝐏𝐓𝐈𝐎𝐍𝐒
+ Despite the wealth of available options, defaults are set so that
+ 𝗰𝘁𝗮𝗴𝘀 is most commonly executed without any options (e.g. "ctags
+ *", or "ctags -R"), which will create a tag file in the current
+ directory for all recognized source files. The options described
+ below are provided merely to allow custom tailoring to meet spe‐
+ cial needs.
+
+ Note that spaces separating the single-letter options from their
+ parameters are optional.
+
+ Note also that the boolean parameters to the long form options
+ (those beginning with "--" and that take a "[̲=̲y̲e̲s̲|n̲o̲]̲" parameter)
+ may be omitted, in which case "=y̲e̲s̲" is implied. (e.g. --𝘀𝗼𝗿𝘁 is
+ equivalent to --𝘀𝗼𝗿𝘁=y̲e̲s̲). Note further that "=1̲" and "=o̲n̲" are
+ considered synonyms for "=y̲e̲s̲", and that "=0̲" and "=o̲f̲f̲" are con‐
+ sidered synonyms for "=n̲o̲".
+
+ Some options are either ignored or useful only when used while
+ running in etags mode (see -𝗲 option). Such options will be
+ noted.
+
+ Most options may appear anywhere on the command line, affecting
+ only those files which follow the option. A few options, however,
+ must appear before the first file name and will be noted as such.
+
+ Options taking language names will accept those names in either
+ upper or lower case. See the --𝗹𝗶𝘀𝘁-𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀 option for a com‐
+ plete list of the built-in language names.
+
+
+ -𝗮 Equivalent to --𝗮𝗽𝗽𝗲𝗻𝗱.
+
+
+ -𝐁 Use backward searching patterns (e.g. ?pattern?). [Ignored
+ in etags mode]
+
+
+ -𝗲 Enable etags mode, which will create a tag file for use with
+ the Emacs editor. Alternatively, if 𝗰𝘁𝗮𝗴𝘀 is invoked by a
+ name containing the string "etags" (either by renaming, or
+ creating a link to, the executable), etags mode will be en‐
+ abled. This option must appear before the first file name.
+
+
+ -𝗳 t̲a̲g̲f̲i̲l̲e̲
+ Use the name specified by t̲a̲g̲f̲i̲l̲e̲ for the tag file (default
+ is "tags", or "TAGS" when running in etags mode). If t̲a̲g̲f̲i̲l̲e̲
+ is specified as "-", then the tag file is written to stan‐
+ dard output instead. 𝐂𝘁𝗮𝗴𝘀 will stubbornly refuse to take
+ orders if t̲a̲g̲f̲i̲l̲e̲ exists and its first line contains some‐
+ thing other than a valid tags line. This will save your neck
+ if you mistakenly type "ctags -f *.c", which would otherwise
+ overwrite your first C file with the tags generated by the
+ rest! It will also refuse to accept a multi-character file
+ name which begins with a '-' (dash) character, since this
+ most likely means that you left out the tag file name and
+ this option tried to grab the next option as the file name.
+ If you really want to name your output tag file "-ugly",
+ specify it as "./-ugly". This option must appear before the
+ first file name. If this option is specified more than once,
+ only the last will apply.
+
+
+ -𝐅 Use forward searching patterns (e.g. /pattern/) (default).
+ [Ignored in etags mode]
+
+
+ -𝗵 l̲i̲s̲t̲
+ Specifies a list of file extensions, separated by periods,
+ which are to be interpreted as include (or header) files. To
+ indicate files having no extension, use a period not fol‐
+ lowed by a non-period character (e.g. ".", "..x", ".x.").
+ This option only affects how the scoping of a particular
+ kinds of tags is interpreted (i.e. whether or not they are
+ considered as globally visible or visible only within the
+ file in which they are defined); it does not map the exten‐
+ sion to any particular language. Any tag which is located in
+ a non-include file and cannot be seen (e.g. linked to) from
+ another file is considered to have file-limited (e.g.
+ static) scope. No kind of tag appearing in an include file
+ will be considered to have file-limited scope. If the first
+ character in the list is a plus sign, then the extensions in
+ the list will be appended to the current list; otherwise,
+ the list will replace the current list. See, also, the
+ --𝗳𝗶𝗹𝗲-𝘀𝗰𝗼𝗽𝗲 option. The default list is
+ ".h.H.hh.hpp.hxx.h++.inc.def". To restore the default list,
+ specify -𝗵 d̲e̲f̲a̲u̲l̲t̲. Note that if an extension supplied to
+ this option is not already mapped to a particular language
+ (see 𝐒𝐎𝐔𝐑𝐂𝐄 𝐅𝐈𝐋𝐄𝐒, above), you will also need to use either
+ the --𝗹𝗮𝗻𝗴𝗺𝗮𝗽 or --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲-𝗳𝗼𝗿𝗰𝗲 option.
+
+
+ -𝐈 i̲d̲e̲n̲t̲i̲f̲i̲e̲r̲-̲l̲i̲s̲t̲
+ Specifies a list of identifiers which are to be specially
+ handled while parsing C and C++ source files. This option is
+ specifically provided to handle special cases arising
+ through the use of preprocessor macros. When the identifiers
+ listed are simple identifiers, these identifiers will be ig‐
+ nored during parsing of the source files. If an identifier
+ is suffixed with a '+' character, 𝗰𝘁𝗮𝗴𝘀 will also ignore any
+ parenthesis-enclosed argument list which may immediately
+ follow the identifier in the source files. If two identi‐
+ fiers are separated with the '=' character, the first iden‐
+ tifiers is replaced by the second identifiers for parsing
+ purposes. The list of identifiers may be supplied directly
+ on the command line or read in from a separate file. If the
+ first character of i̲d̲e̲n̲t̲i̲f̲i̲e̲r̲-̲l̲i̲s̲t̲ is '@', '.' or a pathname
+ separator ('/' or '\'), or the first two characters specify
+ a drive letter (e.g. "C:"), the parameter i̲d̲e̲n̲t̲i̲f̲i̲e̲r̲-̲l̲i̲s̲t̲
+ will be interpreted as a filename from which to read a list
+ of identifiers, one per input line. Otherwise, i̲d̲e̲n̲t̲i̲‐̲
+ f̲i̲e̲r̲-̲l̲i̲s̲t̲ is a list of identifiers (or identifier pairs) to
+ be specially handled, each delimited by a either a comma or
+ by white space (in which case the list should be quoted to
+ keep the entire list as one command line argument). Multiple
+ -𝐈 options may be supplied. To clear the list of ignore
+ identifiers, supply a single dash ("-") for i̲d̲e̲n̲t̲i̲f̲i̲e̲r̲-̲l̲i̲s̲t̲.
+
+ This feature is useful when preprocessor macros are used in
+ such a way that they cause syntactic confusion due to their
+ presence. Indeed, this is the best way of working around a
+ number of problems caused by the presence of syntax-busting
+ macros in source files (see 𝐂𝐀𝐕𝐄𝐀𝐓𝐒, below). Some examples
+ will illustrate this point.
+
+ int foo ARGDECL4(void *, ptr, long int, nbytes)
+
+
+ In the above example, the macro "ARGDECL4" would be mistak‐
+ enly interpreted to be the name of the function instead of
+ the correct name of "foo". Specifying -𝐈 A̲R̲G̲D̲E̲C̲L̲4̲ results in
+ the correct behavior.
+
+ /* creates an RCS version string in module */
+ MODULE_VERSION("$Revision: 750 $")
+
+
+ In the above example the macro invocation looks too much
+ like a function definition because it is not followed by a
+ semicolon (indeed, it could even be followed by a global
+ variable definition that would look much like a K&R style
+ function parameter declaration). In fact, this seeming func‐
+ tion definition could possibly even cause the rest of the
+ file to be skipped over while trying to complete the defini‐
+ tion. Specifying -𝐈 M̲O̲D̲U̲L̲E̲_V̲E̲R̲S̲I̲O̲N̲+̲ would avoid such a prob‐
+ lem.
+
+ CLASS Example {
+ // your content here
+ };
+
+
+ The example above uses "CLASS" as a preprocessor macro which
+ expands to something different for each platform. For in‐
+ stance CLASS may be defined as "class __declspec(dllexport)"
+ on Win32 platforms and simply "class" on UNIX. Normally,
+ the absence of the C++ keyword "class" would cause the
+ source file to be incorrectly parsed. Correct behavior can
+ be restored by specifying -𝐈 C̲L̲A̲S̲S̲=̲c̲l̲a̲s̲s̲.
+
+
+ -𝐋 f̲i̲l̲e̲
+ Read from f̲i̲l̲e̲ a list of file names for which tags should be
+ generated. If f̲i̲l̲e̲ is specified as "-", then file names are
+ read from standard input. File names read using this option
+ are processed following file names appearing on the command
+ line. Options are also accepted in this input. If this op‐
+ tion is specified more than once, only the last will apply.
+ 𝐍𝗼𝘁𝗲: f̲i̲l̲e̲ is read in line-oriented mode, where a new line
+ is the only delimiter and non-trailing white space is con‐
+ sidered significant, in order that file names containing
+ spaces may be supplied (however, trailing white space is
+ stripped from lines); this can affect how options are parsed
+ if included in the input.
+
+
+ -𝗻 Equivalent to --𝗲𝘅𝗰𝗺𝗱=n̲u̲m̲b̲e̲r̲.
+
+
+ -𝐍 Equivalent to --𝗲𝘅𝗰𝗺𝗱=p̲a̲t̲t̲e̲r̲n̲.
+
+
+ -𝗼 t̲a̲g̲f̲i̲l̲e̲
+ Equivalent to -𝗳 t̲a̲g̲f̲i̲l̲e̲.
+
+
+ -𝐑 Equivalent to --𝗿𝗲𝗰𝘂𝗿𝘀𝗲.
+
+
+ -𝘂 Equivalent to --𝘀𝗼𝗿𝘁=n̲o̲ (i.e. "unsorted").
+
+
+ -𝐕 Equivalent to --𝘃𝗲𝗿𝗯𝗼𝘀𝗲.
+
+
+ -𝘄 This option is silently ignored for backward-compatibility
+ with the ctags of SVR4 Unix.
+
+
+ -𝘅 Print a tabular, human-readable cross reference (xref) file
+ to standard output instead of generating a tag file. The in‐
+ formation contained in the output includes: the tag name;
+ the kind of tag; the line number, file name, and source line
+ (with extra white space condensed) of the file which defines
+ the tag. No tag file is written and all options affecting
+ tag file output will be ignored. Example applications for
+ this feature are generating a listing of all functions lo‐
+ cated in a source file (e.g. 𝗰𝘁𝗮𝗴𝘀 -𝘅 --𝗰-𝗸𝗶𝗻𝗱𝘀=f̲ f̲i̲l̲e̲), or
+ generating a list of all externally visible global variables
+ located in a source file (e.g. 𝗰𝘁𝗮𝗴𝘀 -𝘅 --𝗰-𝗸𝗶𝗻𝗱𝘀=v̲
+ --𝗳𝗶𝗹𝗲-𝘀𝗰𝗼𝗽𝗲=n̲o̲ f̲i̲l̲e̲). This option must appear before the
+ first file name.
+
+
+ --𝗮𝗽𝗽𝗲𝗻𝗱[=y̲e̲s̲|n̲o̲]
+ Indicates whether tags generated from the specified files
+ should be appended to those already present in the tag file
+ or should replace them. This option is off by default. This
+ option must appear before the first file name.
+
+
+ --𝗲𝘁𝗮𝗴𝘀-𝗶𝗻𝗰𝗹𝘂𝗱𝗲=f̲i̲l̲e̲
+ Include a reference to f̲i̲l̲e̲ in the tag file. This option may
+ be specified as many times as desired. This supports Emacs'
+ capability to use a tag file which "includes" other tag
+ files. [Available only in etags mode]
+
+
+ --𝗲𝘅𝗰𝗹𝘂𝗱𝗲=[p̲a̲t̲t̲e̲r̲n̲]
+ Add p̲a̲t̲t̲e̲r̲n̲ to a list of excluded files and directories.
+ This option may be specified as many times as desired. For
+ each file name considered by 𝗰𝘁𝗮𝗴𝘀, each p̲a̲t̲t̲e̲r̲n̲ specified
+ using this option will be compared against both the complete
+ path (e.g. some/path/base.ext) and the base name (e.g.
+ base.ext) of the file, thus allowing patterns which match a
+ given file name irrespective of its path, or match only a
+ specific path. If appropriate support is available from the
+ runtime library of your C compiler, then p̲a̲t̲t̲e̲r̲n̲ may contain
+ the usual shell wildcards (not regular expressions) common
+ on Unix (be sure to quote the option parameter to protect
+ the wildcards from being expanded by the shell before being
+ passed to 𝗰𝘁𝗮𝗴𝘀; also be aware that wildcards can match the
+ slash character, '/'). You can determine if shell wildcards
+ are available on your platform by examining the output of
+ the --𝘃𝗲𝗿𝘀𝗶𝗼𝗻 option, which will include "+wildcards" in the
+ compiled feature list; otherwise, p̲a̲t̲t̲e̲r̲n̲ is matched against
+ file names using a simple textual comparison.
+
+ If p̲a̲t̲t̲e̲r̲n̲ begins with the character '@', then the rest of
+ the string is interpreted as a file name from which to read
+ exclusion patterns, one per line. If p̲a̲t̲t̲e̲r̲n̲ is empty, the
+ list of excluded patterns is cleared. Note that at program
+ startup, the default exclude list contains "EIFGEN", "SCCS",
+ "RCS", and "CVS", which are names of directories for which
+ it is generally not desirable to descend while processing
+ the --𝗿𝗲𝗰𝘂𝗿𝘀𝗲 option.
+
+
+ --𝗲𝘅𝗰𝗺𝗱=t̲y̲p̲e̲
+ Determines the type of EX command used to locate tags in the
+ source file. [Ignored in etags mode]
+
+ The valid values for t̲y̲p̲e̲ (either the entire word or the
+ first letter is accepted) are:
+
+
+ n̲u̲m̲b̲e̲r̲ Use only line numbers in the tag file for locating
+ tags. This has four advantages:
+ 1. Significantly reduces the size of the resulting
+ tag file.
+ 2. Eliminates failures to find tags because the
+ line defining the tag has changed, causing the
+ pattern match to fail (note that some editors,
+ such as 𝘃𝗶𝗺, are able to recover in many such
+ instances).
+ 3. Eliminates finding identical matching, but in‐
+ correct, source lines (see 𝐁𝐔𝐆𝐒, below).
+ 4. Retains separate entries in the tag file for
+ lines which are identical in content. In p̲a̲t̲‐̲
+ t̲e̲r̲n̲ mode, duplicate entries are dropped be‐
+ cause the search patterns they generate are
+ identical, making the duplicate entries use‐
+ less.
+
+
+ However, this option has one significant drawback:
+ changes to the source files can cause the line num‐
+ bers recorded in the tag file to no longer corre‐
+ spond to the lines in the source file, causing
+ jumps to some tags to miss the target definition by
+ one or more lines. Basically, this option is best
+ used when the source code to which it is applied is
+ not subject to change. Selecting this option type
+ causes the following options to be ignored: -𝐁𝐅.
+
+
+ p̲a̲t̲t̲e̲r̲n̲ Use only search patterns for all tags, rather than
+ the line numbers usually used for macro defini‐
+ tions. This has the advantage of not referencing
+ obsolete line numbers when lines have been added or
+ removed since the tag file was generated.
+
+
+ m̲i̲x̲e̲d̲ In this mode, patterns are generally used with a
+ few exceptions. For C, line numbers are used for
+ macro definition tags. This was the default format
+ generated by the original 𝗰𝘁𝗮𝗴𝘀 and is, therefore,
+ retained as the default for this option. For For‐
+ tran, line numbers are used for common blocks be‐
+ cause their corresponding source lines are gener‐
+ ally identical, making pattern searches useless for
+ finding all matches.
+
+
+ --𝗲𝘅𝘁𝗿𝗮=[̲+̲|̲-̲]̲f̲l̲a̲g̲s̲
+ Specifies whether to include extra tag entries for certain
+ kinds of information. The parameter f̲l̲a̲g̲s̲ is a set of one-
+ letter flags, each representing one kind of extra tag entry
+ to include in the tag file. If f̲l̲a̲g̲s̲ is preceded by either
+ the '+' or '-' character, the effect of each flag is added
+ to, or removed from, those currently enabled; otherwise the
+ flags replace any current settings. The meaning of each flag
+ is as follows:
+
+
+ f̲ Include an entry for the base file name of every
+ source file (e.g. "example.c"), which addresses the
+ first line of the file.
+
+ q̲ Include an extra class-qualified tag entry for each
+ tag which is a member of a class (for languages for
+ which this information is extracted; currently C++,
+ Eiffel, and Java). The actual form of the qualified
+ tag depends upon the language from which the tag was
+ derived (using a form that is most natural for how
+ qualified calls are specified in the language). For
+ C++, it is in the form "class::member"; for Eiffel
+ and Java, it is in the form "class.member". This may
+ allow easier location of a specific tags when multi‐
+ ple occurrences of a tag name occur in the tag file.
+ Note, however, that this could potentially more than
+ double the size of the tag file.
+
+
+ --𝗳𝗶𝗲𝗹𝗱𝘀=[̲+̲|̲-̲]̲f̲l̲a̲g̲s̲
+ Specifies the available extension fields which are to be in‐
+ cluded in the entries of the tag file (see 𝐓𝐀𝐆 𝐅𝐈𝐋𝐄 𝐅𝐎𝐑𝐌𝐀𝐓,
+ below, for more information). The parameter f̲l̲a̲g̲s̲ is a set
+ of one-letter flags, each representing one type of extension
+ field to include, with the following meanings (disabled by
+ default unless indicated):
+
+
+ a̲ Access (or export) of class members
+ f̲ File-restricted scoping [enabled]
+ i̲ Inheritance information
+ k̲ Kind of tag as a single letter [enabled]
+ K̲ Kind of tag as full name
+ l̲ Language of source file containing tag
+ m̲ Implementation information
+ n̲ Line number of tag definition
+ s̲ Scope of tag definition [enabled]
+ S̲ Signature of routine (e.g. prototype or parameter
+ list)
+ z̲ Include the "kind:" key in kind field
+ t̲ Type and name of a variable or typedef as "typeref:"
+ field [enabled]
+
+ Each letter or group of letters may be preceded by either
+ '+' to add it to the default set, or '-' to exclude it. In
+ the absence of any preceding '+' or '-' sign, only those
+ kinds explicitly listed in f̲l̲a̲g̲s̲ will be included in the
+ output (i.e. overriding the default set). This option is ig‐
+ nored if the option --𝗳𝗼𝗿𝗺𝗮𝘁=1̲ has been specified. The de‐
+ fault value of this option is f̲k̲s̲t̲.
+
+
+ --𝗳𝗶𝗹𝗲-𝘀𝗰𝗼𝗽𝗲[=y̲e̲s̲|n̲o̲]
+ Indicates whether tags scoped only for a single file (i.e.
+ tags which cannot be seen outside of the file in which they
+ are defined, such as "static" tags) should be included in
+ the output. See, also, the -𝗵 option. This option is enabled
+ by default.
+
+
+ --𝗳𝗶𝗹𝘁𝗲𝗿[=y̲e̲s̲|n̲o̲]
+ Causes 𝗰𝘁𝗮𝗴𝘀 to behave as a filter, reading source file
+ names from standard input and printing their tags to stan‐
+ dard output on a file-by-file basis. If --𝘀𝗼𝗿𝘁𝗲𝗱 is enabled,
+ tags are sorted only within the source file in which they
+ are defined. File names are read from standard input in
+ line-oriented input mode (see note for -𝐋 option) and only
+ after file names listed on the command line or from any file
+ supplied using the -𝐋 option. When this option is enabled,
+ the options -𝗳, -𝗼, and --𝘁𝗼𝘁𝗮𝗹𝘀 are ignored. This option is
+ quite esoteric and is disabled by default. This option must
+ appear before the first file name.
+
+
+ --𝗳𝗶𝗹𝘁𝗲𝗿-𝘁𝗲𝗿𝗺𝗶𝗻𝗮𝘁𝗼𝗿=s̲t̲r̲i̲n̲g̲
+ Specifies a string to print to standard output following the
+ tags for each file name parsed when the --𝗳𝗶𝗹𝘁𝗲𝗿 option is
+ enabled. This may permit an application reading the output
+ of ctags to determine when the output for each file is fin‐
+ ished. Note that if the file name read is a directory and
+ --𝗿𝗲𝗰𝘂𝗿𝘀𝗲 is enabled, this string will be printed only once
+ at the end of all tags found for by descending the direc‐
+ tory. This string will always be separated from the last tag
+ line for the file by its terminating newline. This option
+ is quite esoteric and is empty by default. This option must
+ appear before the first file name.
+
+
+ --𝗳𝗼𝗿𝗺𝗮𝘁=l̲e̲v̲e̲l̲
+ Change the format of the output tag file. Currently the only
+ valid values for l̲e̲v̲e̲l̲ are 1̲ or 2̲. Level 1 specifies the
+ original tag file format and level 2 specifies a new ex‐
+ tended format containing extension fields (but in a manner
+ which retains backward-compatibility with original 𝘃𝗶(1) im‐
+ plementations). The default level is 2. This option must ap‐
+ pear before the first file name. [Ignored in etags mode]
+
+
+ --𝗵𝗲𝗹𝗽
+ Prints to standard output a detailed usage description, and
+ then exits.
+
+
+ --𝗶𝗳𝟬[=y̲e̲s̲|n̲o̲]
+ Indicates a preference as to whether code within an "#if 0"
+ branch of a preprocessor conditional should be examined for
+ non-macro tags (macro tags are always included). Because the
+ intent of this construct is to disable code, the default
+ value of this option is n̲o̲. Note that this indicates a pref‐
+ erence only and does not guarantee skipping code within an
+ "#if 0" branch, since the fall-back algorithm used to gener‐
+ ate tags when preprocessor conditionals are too complex fol‐
+ lows all branches of a conditional. This option is disabled
+ by default.
+
+
+ --<𝐋𝐀𝐍𝐆>-𝗸𝗶𝗻𝗱𝘀=[̲+̲|̲-̲]̲k̲i̲n̲d̲s̲
+ Specifies a list of language-specific kinds of tags (or
+ kinds) to include in the output file for a particular lan‐
+ guage, where <𝐋𝐀𝐍𝐆> is case-insensitive and is one of the
+ built-in language names (see the --𝗹𝗶𝘀𝘁-𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀 option for
+ a complete list). The parameter k̲i̲n̲d̲s̲ is a group of one-let‐
+ ter flags designating kinds of tags (particular to the lan‐
+ guage) to either include or exclude from the output. The
+ specific sets of flags recognized for each language, their
+ meanings and defaults may be list using the --𝗹𝗶𝘀𝘁-𝗸𝗶𝗻𝗱𝘀 op‐
+ tion. Each letter or group of letters may be preceded by ei‐
+ ther '+' to add it to, or '-' to remove it from, the default
+ set. In the absence of any preceding '+' or '-' sign, only
+ those kinds explicitly listed in k̲i̲n̲d̲s̲ will be included in
+ the output (i.e. overriding the default for the specified
+ language).
+
+ As an example for the C language, in order to add prototypes
+ and external variable declarations to the default set of tag
+ kinds, but exclude macros, use --𝗰-𝗸𝗶𝗻𝗱𝘀=+̲p̲x̲-̲d̲; to include
+ only tags for functions, use --𝗰-𝗸𝗶𝗻𝗱𝘀=f̲.
+
+
+ --𝗹𝗮𝗻𝗴𝗱𝗲𝗳=n̲a̲m̲e̲
+ Defines a new user-defined language, n̲a̲m̲e̲, to be parsed with
+ regular expressions. Once defined, n̲a̲m̲e̲ may be used in other
+ options taking language names. The typical use of this op‐
+ tion is to first define the language, then map file names to
+ it using -̲-̲l̲a̲n̲g̲m̲a̲p̲, then specify regular expressions using
+ -̲-̲r̲e̲g̲e̲x̲-̲<̲L̲A̲N̲G̲>̲ to define how its tags are found.
+
+
+ --𝗹𝗮𝗻𝗴𝗺𝗮𝗽=m̲a̲p̲[̲,̲m̲a̲p̲[̲.̲.̲.̲]̲]̲
+ Controls how file names are mapped to languages (see the
+ --𝗹𝗶𝘀𝘁-𝗺𝗮𝗽𝘀 option). Each comma-separated m̲a̲p̲ consists of
+ the language name (either a built-in or user-defined lan‐
+ guage), a colon, and a list of file extensions and/or file
+ name patterns. A file extension is specified by preceding
+ the extension with a period (e.g. ".c"). A file name pattern
+ is specified by enclosing the pattern in parentheses (e.g.
+ "([Mm]akefile)"). If appropriate support is available from
+ the runtime library of your C compiler, then the file name
+ pattern may contain the usual shell wildcards common on Unix
+ (be sure to quote the option parameter to protect the wild‐
+ cards from being expanded by the shell before being passed
+ to 𝗰𝘁𝗮𝗴𝘀). You can determine if shell wildcards are avail‐
+ able on your platform by examining the output of the --𝘃𝗲𝗿‐
+ 𝘀𝗶𝗼𝗻 option, which will include "+wildcards" in the compiled
+ feature list; otherwise, the file name patterns are matched
+ against file names using a simple textual comparison. When
+ mapping a file extension, it will first be unmapped from any
+ other languages.
+
+ If the first character in a map is a plus sign, then the ex‐
+ tensions and file name patterns in that map will be appended
+ to the current map for that language; otherwise, the map
+ will replace the current map. For example, to specify that
+ only files with extensions of .c and .x are to be treated as
+ C language files, use "--𝗹𝗮𝗻𝗴𝗺𝗮𝗽=c̲:̲.̲c̲.̲x̲"; to also add files
+ with extensions of .j as Java language files, specify
+ "--𝗹𝗮𝗻𝗴𝗺𝗮𝗽=c̲:̲.̲c̲.̲x̲,̲j̲a̲v̲a̲:̲+̲.̲j̲". To map makefiles (e.g. files
+ named either "Makefile", "makefile", or having the extension
+ ".mak") to a language called "make", specify
+ "--𝗹𝗮𝗻𝗴𝗺𝗮𝗽=m̲a̲k̲e̲:̲(̲[̲M̲m̲]̲a̲k̲e̲f̲i̲l̲e̲)̲.̲m̲a̲k̲". To map files having no
+ extension, specify a period not followed by a non-period
+ character (e.g. ".", "..x", ".x."). To clear the mapping for
+ a particular language (thus inhibiting automatic generation
+ of tags for that language), specify an empty extension list
+ (e.g. "--𝗹𝗮𝗻𝗴𝗺𝗮𝗽=f̲o̲r̲t̲r̲a̲n̲:̲"). To restore the default lan‐
+ guage mappings for all a particular language, supply the
+ keyword "default" for the mapping. To specify restore the
+ default language mappings for all languages, specify
+ "--𝗹𝗮𝗻𝗴𝗺𝗮𝗽=d̲e̲f̲a̲u̲l̲t̲". Note that file extensions are tested
+ before file name patterns when inferring the language of a
+ file.
+
+
+ --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲-𝗳𝗼𝗿𝗰𝗲=l̲a̲n̲g̲u̲a̲g̲e̲
+ By default, 𝗰𝘁𝗮𝗴𝘀 automatically selects the language of a
+ source file, ignoring those files whose language cannot be
+ determined (see 𝐒𝐎𝐔𝐑𝐂𝐄 𝐅𝐈𝐋𝐄𝐒, above). This option forces the
+ specified l̲a̲n̲g̲u̲a̲g̲e̲ (case-insensitive; either built-in or
+ user-defined) to be used for every supplied file instead of
+ automatically selecting the language based upon its exten‐
+ sion. In addition, the special value a̲u̲t̲o̲ indicates that the
+ language should be automatically selected (which effectively
+ disables this option).
+
+
+ --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀=[̲+̲|̲-̲]̲l̲i̲s̲t̲
+ Specifies the languages for which tag generation is enabled,
+ with l̲i̲s̲t̲ containing a comma-separated list of language
+ names (case-insensitive; either built-in or user-defined).
+ If the first language of l̲i̲s̲t̲ is not preceded by either a
+ '+' or '-', the current list will be cleared before adding
+ or removing the languages in l̲i̲s̲t̲. Until a '-' is encoun‐
+ tered, each language in the list will be added to the cur‐
+ rent list. As either the '+' or '-' is encountered in the
+ list, the languages following it are added or removed from
+ the current list, respectively. Thus, it becomes simple to
+ replace the current list with a new one, or to add or remove
+ languages from the current list. The actual list of files
+ for which tags will be generated depends upon the language
+ extension mapping in effect (see the --𝗹𝗮𝗻𝗴𝗺𝗮𝗽 option). Note
+ that all languages, including user-defined languages are en‐
+ abled unless explicitly disabled using this option. Language
+ names included in l̲i̲s̲t̲ may be any built-in language or one
+ previously defined with --𝗹𝗮𝗻𝗴𝗱𝗲𝗳. The default is "all",
+ which is also accepted as a valid argument. See the
+ --𝗹𝗶𝘀𝘁-𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀 option for a complete list of the built-in
+ language names.
+
+
+ --𝗹𝗶𝗰𝗲𝗻𝘀𝗲
+ Prints a summary of the software license to standard output,
+ and then exits.
+
+
+ --𝗹𝗶𝗻𝗲-𝗱𝗶𝗿𝗲𝗰𝘁𝗶𝘃𝗲𝘀[=y̲e̲s̲|n̲o̲]
+ Specifies whether "#line" directives should be recognized.
+ These are present in the output of preprocessors and contain
+ the line number, and possibly the file name, of the original
+ source file(s) from which the preprocessor output file was
+ generated. When enabled, this option will cause 𝗰𝘁𝗮𝗴𝘀 to
+ generate tag entries marked with the file names and line
+ numbers of their locations original source file(s), instead
+ of their actual locations in the preprocessor output. The
+ actual file names placed into the tag file will have the
+ same leading path components as the preprocessor output
+ file, since it is assumed that the original source files are
+ located relative to the preprocessor output file (unless, of
+ course, the #line directive specifies an absolute path).
+ This option is off by default. 𝐍𝗼𝘁𝗲: This option is gener‐
+ ally only useful when used together with the --𝗲𝘅𝗰𝗺𝗱=n̲u̲m̲b̲e̲r̲
+ (-𝗻) option. Also, you may have to use either the --𝗹𝗮𝗻𝗴𝗺𝗮𝗽
+ or --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲-𝗳𝗼𝗿𝗰𝗲 option if the extension of the pre‐
+ processor output file is not known to 𝗰𝘁𝗮𝗴𝘀.
+
+
+ --𝗹𝗶𝗻𝗸𝘀[=y̲e̲s̲|n̲o̲]
+ Indicates whether symbolic links (if supported) should be
+ followed. When disabled, symbolic links are ignored. This
+ option is on by default.
+
+
+ --𝗹𝗶𝘀𝘁-𝗸𝗶𝗻𝗱𝘀[=l̲a̲n̲g̲u̲a̲g̲e̲|a̲l̲l̲]
+ Lists the tag kinds recognized for either the specified lan‐
+ guage or all languages, and then exits. Each kind of tag
+ recorded in the tag file is represented by a one-letter
+ flag, which is also used to filter the tags placed into the
+ output through use of the --<𝐋𝐀𝐍𝐆>-𝗸𝗶𝗻𝗱𝘀 option. Note that
+ some languages and/or tag kinds may be implemented using
+ regular expressions and may not be available if regex sup‐
+ port is not compiled into 𝗰𝘁𝗮𝗴𝘀 (see the --𝗿𝗲𝗴𝗲𝘅-<𝐋𝐀𝐍𝐆> op‐
+ tion). Each kind listed is enabled unless followed by
+ "[off]".
+
+
+ --𝗹𝗶𝘀𝘁-𝗺𝗮𝗽𝘀[=l̲a̲n̲g̲u̲a̲g̲e̲|a̲l̲l̲]
+ Lists the file extensions and file name patterns which asso‐
+ ciate a file name with a language for either the specified
+ language or all languages, and then exits. See the --𝗹𝗮𝗻𝗴𝗺𝗮𝗽
+ option, and 𝐒𝐎𝐔𝐑𝐂𝐄 𝐅𝐈𝐋𝐄𝐒, above.
+
+
+ --𝗹𝗶𝘀𝘁-𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀
+ Lists the names of the languages understood by 𝗰𝘁𝗮𝗴𝘀, and
+ then exits. These language names are case insensitive and
+ may be used in the --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲-𝗳𝗼𝗿𝗰𝗲, --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀,
+ --<𝐋𝐀𝐍𝐆>-𝗸𝗶𝗻𝗱𝘀, and --𝗿𝗲𝗴𝗲𝘅-<𝐋𝐀𝐍𝐆> options.
+
+
+ --𝗼𝗽𝘁𝗶𝗼𝗻𝘀=f̲i̲l̲e̲
+ Read additional options from f̲i̲l̲e̲. The file should contain
+ one option per line. As a special case, if --𝗼𝗽𝘁𝗶𝗼𝗻𝘀=N̲O̲N̲E̲ is
+ specified as the first option on the command line, it will
+ disable the automatic reading of any configuration options
+ from either a file or the environment (see 𝐅𝐈𝐋𝐄𝐒).
+
+
+ --𝗿𝗲𝗰𝘂𝗿𝘀𝗲[=y̲e̲s̲|n̲o̲]
+ Recurse into directories encountered in the list of supplied
+ files. If the list of supplied files is empty and no file
+ list is specified with the -𝐋 option, then the current di‐
+ rectory (i.e. ".") is assumed. Symbolic links are followed.
+ If you don't like these behaviors, either explicitly specify
+ the files or pipe the output of 𝗳𝗶𝗻𝗱(1) into 𝗰𝘁𝗮𝗴𝘀 -𝐋- in‐
+ stead. 𝐍𝗼𝘁𝗲: This option is not supported on all platforms
+ at present. It is available if the output of the --𝗵𝗲𝗹𝗽 op‐
+ tion includes this option. See, also, the --𝗲𝘅𝗰𝗹𝘂𝗱𝗲 to
+ limit recursion.
+
+
+ --𝗿𝗲𝗴𝗲𝘅-<𝐋𝐀𝐍𝐆>=/̲r̲e̲g̲e̲x̲p̲/̲r̲e̲p̲l̲a̲c̲e̲m̲e̲n̲t̲/̲[̲k̲i̲n̲d̲-̲s̲p̲e̲c̲/̲]̲[̲f̲l̲a̲g̲s̲]̲
+ The /̲r̲e̲g̲e̲x̲p̲/̲r̲e̲p̲l̲a̲c̲e̲m̲e̲n̲t̲/̲ pair define a regular expression
+ replacement pattern, similar in style to 𝘀𝗲𝗱 substitution
+ commands, with which to generate tags from source files
+ mapped to the named language, <𝐋𝐀𝐍𝐆>, (case-insensitive; ei‐
+ ther a built-in or user-defined language). The regular ex‐
+ pression, r̲e̲g̲e̲x̲p̲, defines an extended regular expression
+ (roughly that used by 𝗲𝗴𝗿𝗲𝗽(1)), which is used to locate a
+ single source line containing a tag and may specify tab
+ characters using \t. When a matching line is found, a tag
+ will be generated for the name defined by r̲e̲p̲l̲a̲c̲e̲m̲e̲n̲t̲, which
+ generally will contain the special back-references \1
+ through \9 to refer to matching sub-expression groups within
+ r̲e̲g̲e̲x̲p̲. The '/' separator characters shown in the parameter
+ to the option can actually be replaced by any character.
+ Note that whichever separator character is used will have to
+ be escaped with a backslash ('\') character wherever it is
+ used in the parameter as something other than a separator.
+ The regular expression defined by this option is added to
+ the current list of regular expressions for the specified
+ language unless the parameter is omitted, in which case the
+ current list is cleared.
+
+ Unless modified by f̲l̲a̲g̲s̲, r̲e̲g̲e̲x̲p̲ is interpreted as a Posix
+ extended regular expression. The r̲e̲p̲l̲a̲c̲e̲m̲e̲n̲t̲ should expand
+ for all matching lines to a non-empty string of characters,
+ or a warning message will be reported. An optional kind
+ specifier for tags matching r̲e̲g̲e̲x̲p̲ may follow r̲e̲p̲l̲a̲c̲e̲m̲e̲n̲t̲,
+ which will determine what kind of tag is reported in the
+ "kind" extension field (see 𝐓𝐀𝐆 𝐅𝐈𝐋𝐄 𝐅𝐎𝐑𝐌𝐀𝐓, below). The
+ full form of k̲i̲n̲d̲-̲s̲p̲e̲c̲ is in the form of a single letter, a
+ comma, a name (without spaces), a comma, a description, fol‐
+ lowed by a separator, which specify the short and long forms
+ of the kind value and its textual description (displayed us‐
+ ing --𝗹𝗶𝘀𝘁-𝗸𝗶𝗻𝗱𝘀). Either the kind name and/or the descrip‐
+ tion may be omitted. If k̲i̲n̲d̲-̲s̲p̲e̲c̲ is omitted, it defaults to
+ "r̲,̲r̲e̲g̲e̲x̲". Finally, f̲l̲a̲g̲s̲ are one or more single-letter
+ characters having the following effect upon the interpreta‐
+ tion of r̲e̲g̲e̲x̲p̲:
+
+
+ b̲ The pattern is interpreted as a Posix basic regular
+ expression.
+
+ e̲ The pattern is interpreted as a Posix extended regu‐
+ lar expression (default).
+
+ i̲ The regular expression is to be applied in a case-in‐
+ sensitive manner.
+
+ Note that this option is available only if 𝗰𝘁𝗮𝗴𝘀 was com‐
+ piled with support for regular expressions, which depends
+ upon your platform. You can determine if support for regular
+ expressions is compiled in by examining the output of the
+ --𝘃𝗲𝗿𝘀𝗶𝗼𝗻 option, which will include "+regex" in the com‐
+ piled feature list.
+
+ For more information on the regular expressions used by
+ 𝗰𝘁𝗮𝗴𝘀, see either the 𝗿𝗲𝗴𝗲𝘅(𝟱,𝟳) man page, or the GNU info
+ documentation for regex (e.g. "info regex").
+
+
+ --𝘀𝗼𝗿𝘁[=y̲e̲s̲|n̲o̲|f̲o̲l̲d̲c̲a̲s̲e̲]
+ Indicates whether the tag file should be sorted on the tag
+ name (default is y̲e̲s̲). Note that the original 𝘃𝗶(1) required
+ sorted tags. The f̲o̲l̲d̲c̲a̲s̲e̲ value specifies case insensitive
+ (or case-folded) sorting. Fast binary searches of tag files
+ sorted with case-folding will require special support from
+ tools using tag files, such as that found in the ctags read‐
+ tags library, or Vim version 6.2 or higher (using "set ig‐
+ norecase"). This option must appear before the first file
+ name. [Ignored in etags mode]
+
+
+ --𝘁𝗮𝗴-𝗿𝗲𝗹𝗮𝘁𝗶𝘃𝗲[=y̲e̲s̲|n̲o̲]
+ Indicates that the file paths recorded in the tag file
+ should be relative to the directory containing the tag file,
+ rather than relative to the current directory, unless the
+ files supplied on the command line are specified with abso‐
+ lute paths. This option must appear before the first file
+ name. The default is y̲e̲s̲ when running in etags mode (see the
+ -𝗲 option), n̲o̲ otherwise.
+
+
+ --𝘁𝗼𝘁𝗮𝗹𝘀[=y̲e̲s̲|n̲o̲]
+ Prints statistics about the source files read and the tag
+ file written during the current invocation of 𝗰𝘁𝗮𝗴𝘀. This
+ option is off by default. This option must appear before
+ the first file name.
+
+
+ --𝘃𝗲𝗿𝗯𝗼𝘀𝗲[=y̲e̲s̲|n̲o̲]
+ Enable verbose mode. This prints out information on option
+ processing and a brief message describing what action is be‐
+ ing taken for each file considered by 𝗰𝘁𝗮𝗴𝘀. Normally, 𝗰𝘁𝗮𝗴𝘀
+ does not read command line arguments until after options are
+ read from the configuration files (see 𝐅𝐈𝐋𝐄𝐒, below) and the
+ 𝐂𝐓𝐀𝐆𝐒 environment variable. However, if this option is the
+ first argument on the command line, it will take effect be‐
+ fore any options are read from these sources. The default is
+ n̲o̲.
+
+
+ --𝘃𝗲𝗿𝘀𝗶𝗼𝗻
+ Prints a version identifier for 𝗰𝘁𝗮𝗴𝘀 to standard output,
+ and then exits. This is guaranteed to always contain the
+ string "Exuberant Ctags".
+
+
+
+𝐎𝐏𝐄𝐑𝐀𝐓𝐈𝐎𝐍𝐀𝐋 𝐃𝐄𝐓𝐀𝐈𝐋𝐒
+ As 𝗰𝘁𝗮𝗴𝘀 considers each file name in turn, it tries to determine
+ the language of the file by applying the following three tests in
+ order: if the file extension has been mapped to a language, if
+ the file name matches a shell pattern mapped to a language, and
+ finally if the file is executable and its first line specifies an
+ interpreter using the Unix-style "#!" specification (if supported
+ on the platform). If a language was identified, the file is
+ opened and then the appropriate language parser is called to op‐
+ erate on the currently open file. The parser parses through the
+ file and adds an entry to the tag file for each language object
+ it is written to handle. See 𝐓𝐀𝐆 𝐅𝐈𝐋𝐄 𝐅𝐎𝐑𝐌𝐀𝐓, below, for details
+ on these entries.
+
+ This implementation of 𝗰𝘁𝗮𝗴𝘀 imposes no formatting requirements
+ on C code as do legacy implementations. Older implementations of
+ ctags tended to rely upon certain formatting assumptions in order
+ to help it resolve coding dilemmas caused by preprocessor condi‐
+ tionals.
+
+ In general, 𝗰𝘁𝗮𝗴𝘀 tries to be smart about conditional preproces‐
+ sor directives. If a preprocessor conditional is encountered
+ within a statement which defines a tag, 𝗰𝘁𝗮𝗴𝘀 follows only the
+ first branch of that conditional (except in the special case of
+ "#if 0", in which case it follows only the last branch). The rea‐
+ son for this is that failing to pursue only one branch can result
+ in ambiguous syntax, as in the following example:
+
+ #ifdef TWO_ALTERNATIVES
+ struct {
+ #else
+ union {
+ #endif
+ short a;
+ long b;
+ }
+
+ Both branches cannot be followed, or braces become unbalanced and
+ 𝗰𝘁𝗮𝗴𝘀 would be unable to make sense of the syntax.
+
+ If the application of this heuristic fails to properly parse a
+ file, generally due to complicated and inconsistent pairing
+ within the conditionals, 𝗰𝘁𝗮𝗴𝘀 will retry the file using a dif‐
+ ferent heuristic which does not selectively follow conditional
+ preprocessor branches, but instead falls back to relying upon a
+ closing brace ("}") in column 1 as indicating the end of a block
+ once any brace imbalance results from following a #if conditional
+ branch.
+
+ 𝐂𝘁𝗮𝗴𝘀 will also try to specially handle arguments lists enclosed
+ in double sets of parentheses in order to accept the following
+ conditional construct:
+
+ extern void foo __ARGS((int one, char two));
+
+ Any name immediately preceding the "((" will be automatically ig‐
+ nored and the previous name will be used.
+
+ C++ operator definitions are specially handled. In order for con‐
+ sistency with all types of operators (overloaded and conversion),
+ the operator name in the tag file will always be preceded by the
+ string "operator " (i.e. even if the actual operator definition
+ was written as "operator<<").
+
+ After creating or appending to the tag file, it is sorted by the
+ tag name, removing identical tag lines.
+
+
+
+𝐓𝐀𝐆 𝐅𝐈𝐋𝐄 𝐅𝐎𝐑𝐌𝐀𝐓
+ When not running in etags mode, each entry in the tag file con‐
+ sists of a separate line, each looking like this in the most gen‐
+ eral case:
+
+ tag_namefile_nameex_cmd;"extension_fields
+
+ The fields and separators of these lines are specified as fol‐
+ lows:
+
+ 1. tag name
+ 2. single tab character
+ 3. name of the file in which the object associated with the
+ tag is located
+ 4. single tab character
+ 5. EX command used to locate the tag within the file; gener‐
+ ally a search pattern (either /pattern/ or ?pattern?) or
+ line number (see --𝗲𝘅𝗰𝗺𝗱). Tag file format 2 (see --𝗳𝗼𝗿‐
+ 𝗺𝗮𝘁) extends this EX command under certain circumstances
+ to include a set of extension fields (described below)
+ embedded in an EX comment immediately appended to the EX
+ command, which leaves it backward-compatible with origi‐
+ nal 𝘃𝗶(1) implementations.
+
+ A few special tags are written into the tag file for internal
+ purposes. These tags are composed in such a way that they always
+ sort to the top of the file. Therefore, the first two characters
+ of these tags are used a magic number to detect a tag file for
+ purposes of determining whether a valid tag file is being over‐
+ written rather than a source file.
+
+ Note that the name of each source file will be recorded in the
+ tag file exactly as it appears on the command line. Therefore, if
+ the path you specified on the command line was relative to the
+ current directory, then it will be recorded in that same manner
+ in the tag file. See, however, the --𝘁𝗮𝗴-𝗿𝗲𝗹𝗮𝘁𝗶𝘃𝗲 option for how
+ this behavior can be modified.
+
+ Extension fields are tab-separated key-value pairs appended to
+ the end of the EX command as a comment, as described above. These
+ key value pairs appear in the general form "k̲e̲y̲:v̲a̲l̲u̲e̲". Their
+ presence in the lines of the tag file are controlled by the
+ --𝗳𝗶𝗲𝗹𝗱𝘀 option. The possible keys and the meaning of their val‐
+ ues are as follows:
+
+
+ a̲c̲c̲e̲s̲s̲ Indicates the visibility of this class member, where
+ v̲a̲l̲u̲e̲ is specific to the language.
+
+
+ f̲i̲l̲e̲ Indicates that the tag has file-limited visibility.
+ This key has no corresponding value.
+
+
+ k̲i̲n̲d̲ Indicates the type, or kind, of tag. Its value is ei‐
+ ther one of the corresponding one-letter flags de‐
+ scribed under the various --<𝐋𝐀𝐍𝐆>-𝗸𝗶𝗻𝗱𝘀 options
+ above, or a full name. It is permitted (and is, in
+ fact, the default) for the key portion of this field
+ to be omitted. The optional behaviors are controlled
+ with the --𝗳𝗶𝗲𝗹𝗱𝘀 option.
+
+
+ i̲m̲p̲l̲e̲m̲e̲n̲t̲a̲t̲i̲o̲n̲
+ When present, this indicates a limited implementation
+ (abstract vs. concrete) of a routine or class, where
+ v̲a̲l̲u̲e̲ is specific to the language ("virtual" or "pure
+ virtual" for C++; "abstract" for Java).
+
+
+ i̲n̲h̲e̲r̲i̲t̲s̲ When present, v̲a̲l̲u̲e̲. is a comma-separated list of
+ classes from which this class is derived (i.e. inher‐
+ its from).
+
+
+ s̲i̲g̲n̲a̲t̲u̲r̲e̲ When present, v̲a̲l̲u̲e̲ is a language-dependent represen‐
+ tation of the signature of a routine. A routine sig‐
+ nature in its complete form specifies the return type
+ of a routine and its formal argument list. This ex‐
+ tension field is presently supported only for C-based
+ languages and does not include the return type.
+
+
+ In addition, information on the scope of the tag definition may
+ be available, with the key portion equal to some language-depen‐
+ dent construct name and its value the name declared for that con‐
+ struct in the program. This scope entry indicates the scope in
+ which the tag was found. For example, a tag generated for a C
+ structure member would have a scope looking like "struct:myS‐
+ truct".
+
+
+
+𝐇𝐎𝐖 𝐓𝐎 𝐔𝐒𝐄 𝐖𝐈𝐓𝐇 𝐕𝐈
+ Vi will, by default, expect a tag file by the name "tags" in the
+ current directory. Once the tag file is built, the following com‐
+ mands exercise the tag indexing feature:
+
+ 𝘃𝗶 -𝘁 𝘁𝗮𝗴 Start vi and position the cursor at the file and line
+ where "tag" is defined.
+
+ :𝘁𝗮 𝘁𝗮𝗴 Find a tag.
+
+ 𝐂𝘁𝗿𝗹-] Find the tag under the cursor.
+
+ 𝐂𝘁𝗿𝗹-𝐓 Return to previous location before jump to tag (not
+ widely implemented).
+
+
+
+𝐇𝐎𝐖 𝐓𝐎 𝐔𝐒𝐄 𝐖𝐈𝐓𝐇 𝐆𝐍𝐔 𝐄𝐌𝐀𝐂𝐒
+ Emacs will, by default, expect a tag file by the name "TAGS" in
+ the current directory. Once the tag file is built, the following
+ commands exercise the tag indexing feature:
+
+ 𝐌-𝘅 𝘃𝗶𝘀𝗶𝘁-𝘁𝗮𝗴𝘀-𝘁𝗮𝗯𝗹𝗲 <𝐑𝐄𝐓> 𝐅𝐈𝐋𝐄 <𝐑𝐄𝐓>
+ Select the tag file, "FILE", to use.
+
+ 𝐌-. [𝐓𝐀𝐆] <𝐑𝐄𝐓>
+ Find the first definition of TAG. The default tag is
+ the identifier under the cursor.
+
+ 𝐌-* Pop back to where you previously invoked "M-.".
+
+ 𝐂-𝘂 𝐌-. Find the next definition for the last tag.
+
+
+ For more commands, see the T̲a̲g̲s̲ topic in the Emacs info document.
+
+
+
+𝐇𝐎𝐖 𝐓𝐎 𝐔𝐒𝐄 𝐖𝐈𝐓𝐇 𝐍𝐄𝐃𝐈𝐓
+ NEdit version 5.1 and later can handle the new extended tag file
+ format (see --𝗳𝗼𝗿𝗺𝗮𝘁). To make NEdit use the tag file, select
+ "File->Load Tags File". To jump to the definition for a tag,
+ highlight the word, then press Ctrl-D. NEdit 5.1 can can read
+ multiple tag files from different directories. Setting the X re‐
+ source nedit.tagFile to the name of a tag file instructs NEdit to
+ automatically load that tag file at startup time.
+
+
+
+𝐂𝐀𝐕𝐄𝐀𝐓𝐒
+ Because 𝗰𝘁𝗮𝗴𝘀 is neither a preprocessor nor a compiler, use of
+ preprocessor macros can fool 𝗰𝘁𝗮𝗴𝘀 into either missing tags or
+ improperly generating inappropriate tags. Although 𝗰𝘁𝗮𝗴𝘀 has been
+ designed to handle certain common cases, this is the single big‐
+ gest cause of reported problems. In particular, the use of pre‐
+ processor constructs which alter the textual syntax of C can fool
+ 𝗰𝘁𝗮𝗴𝘀. You can work around many such problems by using the -𝐈 op‐
+ tion.
+
+ Note that since 𝗰𝘁𝗮𝗴𝘀 generates patterns for locating tags (see
+ the --𝗲𝘅𝗰𝗺𝗱 option), it is entirely possible that the wrong line
+ may be found by your editor if there exists another source line
+ which is identical to the line containing the tag. The following
+ example demonstrates this condition:
+
+ int variable;
+
+ /* ... */
+ void foo(variable)
+ int variable;
+ {
+ /* ... */
+ }
+
+ Depending upon which editor you use and where in the code you
+ happen to be, it is possible that the search pattern may locate
+ the local parameter declaration in foo() before it finds the ac‐
+ tual global variable definition, since the lines (and therefore
+ their search patterns are identical). This can be avoided by use
+ of the --𝗲𝘅𝗰𝗺𝗱=n̲ option.
+
+
+
+𝐁𝐔𝐆𝐒
+ 𝐂𝘁𝗮𝗴𝘀 has more options than 𝗹𝘀(1).
+
+ When parsing a C++ member function definition (e.g. "class‐
+ Name::function"), 𝗰𝘁𝗮𝗴𝘀 cannot determine whether the scope speci‐
+ fier is a class name or a namespace specifier and always lists it
+ as a class name in the scope portion of the extension fields.
+ Also, if a C++ function is defined outside of the class declara‐
+ tion (the usual case), the access specification (i.e. public,
+ protected, or private) and implementation information (e.g. vir‐
+ tual, pure virtual) contained in the function declaration are not
+ known when the tag is generated for the function definition. It
+ will, however be available for prototypes (e.g --𝗰++-𝗸𝗶𝗻𝗱𝘀=+̲p̲).
+
+ No qualified tags are generated for language objects inherited
+ into a class.
+
+
+
+𝐄𝐍𝐕𝐈𝐑𝐎𝐍𝐌𝐄𝐍𝐓 𝐕𝐀𝐑𝐈𝐀𝐁𝐋𝐄𝐒
+ 𝐂𝐓𝐀𝐆𝐒 If this environment variable exists, it will be expected
+ to contain a set of default options which are read when
+ 𝗰𝘁𝗮𝗴𝘀 starts, after the configuration files listed in
+ 𝐅𝐈𝐋𝐄𝐒, below, are read, but before any command line op‐
+ tions are read. Options appearing on the command line
+ will override options specified in this variable. Only
+ options will be read from this variable. Note that all
+ white space in this variable is considered a separator,
+ making it impossible to pass an option parameter contain‐
+ ing an embedded space. If this is a problem, use a con‐
+ figuration file instead.
+
+
+ 𝐄𝐓𝐀𝐆𝐒 Similar to the 𝐂𝐓𝐀𝐆𝐒 variable above, this variable, if
+ found, will be read when 𝗲𝘁𝗮𝗴𝘀 starts. If this variable
+ is not found, 𝗲𝘁𝗮𝗴𝘀 will try to use 𝐂𝐓𝐀𝐆𝐒 instead.
+
+
+ 𝐓𝐌𝐏𝐃𝐈𝐑 On Unix-like hosts where mkstemp() is available, the
+ value of this variable specifies the directory in which
+ to place temporary files. This can be useful if the size
+ of a temporary file becomes too large to fit on the par‐
+ tition holding the default temporary directory defined at
+ compilation time. 𝗰𝘁𝗮𝗴𝘀 creates temporary files only if
+ either (1) an emacs-style tag file is being generated,
+ (2) the tag file is being sent to standard output, or (3)
+ the program was compiled to use an internal sort algo‐
+ rithm to sort the tag files instead of the the sort util‐
+ ity of the operating system. If the sort utility of the
+ operating system is being used, it will generally observe
+ this variable also. Note that if 𝗰𝘁𝗮𝗴𝘀 is setuid, the
+ value of TMPDIR will be ignored.
+
+
+
+𝐅𝐈𝐋𝐄𝐒
+ /̲c̲t̲a̲g̲s̲.̲c̲n̲f̲ (̲o̲n̲ M̲S̲D̲O̲S̲,̲ M̲S̲W̲i̲n̲d̲o̲w̲s̲ o̲n̲l̲y̲)̲
+ /̲e̲t̲c̲/̲c̲t̲a̲g̲s̲.̲c̲o̲n̲f̲
+ /̲u̲s̲r̲/̲l̲o̲c̲a̲l̲/̲e̲t̲c̲/̲c̲t̲a̲g̲s̲.̲c̲o̲n̲f̲
+ $̲H̲O̲M̲E̲/̲.̲c̲t̲a̲g̲s̲
+ $̲H̲O̲M̲E̲/̲c̲t̲a̲g̲s̲.̲c̲n̲f̲ (̲o̲n̲ M̲S̲D̲O̲S̲,̲ M̲S̲W̲i̲n̲d̲o̲w̲s̲ o̲n̲l̲y̲)̲
+ .̲c̲t̲a̲g̲s̲
+ c̲t̲a̲g̲s̲.̲c̲n̲f̲ (̲o̲n̲ M̲S̲D̲O̲S̲,̲ M̲S̲W̲i̲n̲d̲o̲w̲s̲ o̲n̲l̲y̲)̲
+ If any of these configuration files exist, each will be
+ expected to contain a set of default options which are
+ read in the order listed when 𝗰𝘁𝗮𝗴𝘀 starts, but before the
+ 𝐂𝐓𝐀𝐆𝐒 environment variable is read or any command line op‐
+ tions are read. This makes it possible to set up site-
+ wide, personal or project-level defaults. It is possible
+ to compile 𝗰𝘁𝗮𝗴𝘀 to read an additional configuration file
+ before any of those shown above, which will be indicated
+ if the output produced by the --𝘃𝗲𝗿𝘀𝗶𝗼𝗻 option lists the
+ "custom-conf" feature. Options appearing in the 𝐂𝐓𝐀𝐆𝐒 en‐
+ vironment variable or on the command line will override
+ options specified in these files. Only options will be
+ read from these files. Note that the option files are read
+ in line-oriented mode in which spaces are significant
+ (since shell quoting is not possible). Each line of the
+ file is read as one command line parameter (as if it were
+ quoted with single quotes). Therefore, use new lines to
+ indicate separate command-line arguments.
+
+
+ t̲a̲g̲s̲ The default tag file created by 𝗰𝘁𝗮𝗴𝘀.
+
+ T̲A̲G̲S̲ The default tag file created by 𝗲𝘁𝗮𝗴𝘀.
+
+
+𝐒𝐄𝐄 𝐀𝐋𝐒𝐎
+ The official Exuberant Ctags web site at:
+
+ http://ctags.sourceforge.net
+
+ Also 𝗲𝘅(1), 𝘃𝗶(1), 𝗲𝗹𝘃𝗶𝘀, or, better yet, 𝘃𝗶𝗺, the official edi‐
+ tor of 𝗰𝘁𝗮𝗴𝘀. For more information on 𝘃𝗶𝗺, see the VIM Pages web
+ site at:
+
+ http://www.vim.org/
+
+
+
+𝐀𝐔𝐓𝐇𝐎𝐑
+ Darren Hiebert
+ http://DarrenHiebert.com/
+
+
+
+𝐌𝐎𝐓𝐈𝐕𝐀𝐓𝐈𝐎𝐍
+ "Think ye at all times of rendering some service to every member
+ of the human race."
+
+ "All effort and exertion put forth by man from the fullness of
+ his heart is worship, if it is prompted by the highest motives
+ and the will to do service to humanity."
+
+ -- From the Baha'i Writings
+
+
+
+𝐂𝐑𝐄𝐃𝐈𝐓𝐒
+ This version of 𝗰𝘁𝗮𝗴𝘀 was originally derived from and inspired by
+ the ctags program by Steve Kirkendall that
+ comes with the Elvis vi clone (though virtually none of the orig‐
+ inal code remains).
+
+ Credit is also due Bram Moolenaar , the author of
+ 𝘃𝗶𝗺, who has devoted so much of his time and energy both to de‐
+ veloping the editor as a service to others, and to helping the
+ orphans of Uganda.
+
+ The section entitled "HOW TO USE WITH GNU EMACS" was shamelessly
+ stolen from the info page for GNU 𝗲𝘁𝗮𝗴𝘀.
+
+
+
+Darren Hiebert Version 5.9~svn20110310 CTAGS(1)
diff --git a/third_party/ctags/ant.c b/third_party/ctags/ant.c
new file mode 100644
index 000000000..da8d3c29e
--- /dev/null
+++ b/third_party/ctags/ant.c
@@ -0,0 +1,35 @@
+/*
+ * $Id$
+ *
+ * Copyright (c) 2008, David Fishburn
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for Ant language files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parse.h"
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void installAntRegex(const langType language) {
+ addTagRegex(language, "^[ \t]*<[ \t]*project[^>]+name=\"([^\"]+)\".*", "\\1",
+ "p,project,projects", NULL);
+ addTagRegex(language, "^[ \t]*<[ \t]*target[^>]+name=\"([^\"]+)\".*", "\\1",
+ "t,target,targets", NULL);
+}
+
+extern parserDefinition* AntParser() {
+ static const char* const extensions[] = {"build.xml", NULL};
+ parserDefinition* const def = parserNew("Ant");
+ def->extensions = extensions;
+ def->initialize = installAntRegex;
+ def->regex = TRUE;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/args.c b/third_party/ctags/args.c
new file mode 100644
index 000000000..37cec32a5
--- /dev/null
+++ b/third_party/ctags/args.c
@@ -0,0 +1,238 @@
+/*
+ * $Id: args.c 536 2007-06-02 06:09:00Z elliotth $
+ *
+ * Copyright (c) 1999-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for reading command line arguments.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/calls/calls.h"
+#include "libc/str/str.h"
+#include "third_party/ctags/args.h"
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/routines.h"
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static char* nextStringArg(const char** const next) {
+ char* result = NULL;
+ const char* start;
+
+ Assert(*next != NULL);
+ for (start = *next; isspace((int)*start); ++start)
+ ;
+ if (*start == '\0')
+ *next = start;
+ else {
+ size_t length;
+ const char* end;
+
+ for (end = start; *end != '\0' && !isspace((int)*end); ++end)
+ ;
+ length = end - start;
+ Assert(length > 0);
+ result = xMalloc(length + 1, char);
+ strncpy(result, start, length);
+ result[length] = '\0';
+ *next = end;
+ }
+ return result;
+}
+
+static char* nextStringLine(const char** const next) {
+ char* result = NULL;
+ size_t length;
+ const char* end;
+
+ Assert(*next != NULL);
+ for (end = *next; *end != '\n' && *end != '\0'; ++end)
+ ;
+ length = end - *next;
+ if (length > 0) {
+ result = xMalloc(length + 1, char);
+ strncpy(result, *next, length);
+ result[length] = '\0';
+ }
+ if (*end == '\n')
+ ++end;
+ else if (*end == '\r') {
+ ++end;
+ if (*end == '\n') ++end;
+ }
+ *next = end;
+ return result;
+}
+
+static char* nextString(const Arguments* const current,
+ const char** const next) {
+ char* result;
+ if (current->lineMode)
+ result = nextStringLine(next);
+ else
+ result = nextStringArg(next);
+ return result;
+}
+
+static char* nextFileArg(FILE* const fp) {
+ char* result = NULL;
+ Assert(fp != NULL);
+ if (!feof(fp)) {
+ vString* vs = vStringNew();
+ int c;
+ do
+ c = fgetc(fp);
+ while (isspace(c));
+
+ if (c != EOF) {
+ do {
+ vStringPut(vs, c);
+ c = fgetc(fp);
+ } while (c != EOF && !isspace(c));
+ vStringTerminate(vs);
+ Assert(vStringLength(vs) > 0);
+ result = xMalloc(vStringLength(vs) + 1, char);
+ strcpy(result, vStringValue(vs));
+ }
+ vStringDelete(vs);
+ }
+ return result;
+}
+
+static char* nextFileLine(FILE* const fp) {
+ char* result = NULL;
+ if (!feof(fp)) {
+ vString* vs = vStringNew();
+ int c;
+
+ Assert(fp != NULL);
+ c = fgetc(fp);
+ while (c != EOF) {
+ if (c != '\n' && c != '\r')
+ vStringPut(vs, c);
+ else if (vStringLength(vs) > 0)
+ break;
+ c = fgetc(fp);
+ }
+ if (c != EOF || vStringLength(vs) > 0) {
+ if (c == '\r') {
+ c = fgetc(fp);
+ if (c != '\n') c = ungetc(c, fp);
+ }
+ vStringTerminate(vs);
+ vStringStripTrailing(vs);
+ result = xMalloc(vStringLength(vs) + 1, char);
+ strcpy(result, vStringValue(vs));
+ }
+ vStringDelete(vs);
+ }
+ return result;
+}
+
+static char* nextFileString(const Arguments* const current, FILE* const fp) {
+ char* result;
+ if (current->lineMode)
+ result = nextFileLine(fp);
+ else
+ result = nextFileArg(fp);
+ return result;
+}
+
+extern Arguments* argNewFromString(const char* const string) {
+ Arguments* result = xMalloc(1, Arguments);
+ memset(result, 0, sizeof(Arguments));
+ result->type = ARG_STRING;
+ result->u.stringArgs.string = string;
+ result->u.stringArgs.item = string;
+ result->u.stringArgs.next = string;
+ result->item = nextString(result, &result->u.stringArgs.next);
+ return result;
+}
+
+extern Arguments* argNewFromArgv(char* const* const argv) {
+ Arguments* result = xMalloc(1, Arguments);
+ memset(result, 0, sizeof(Arguments));
+ result->type = ARG_ARGV;
+ result->u.argvArgs.argv = argv;
+ result->u.argvArgs.item = result->u.argvArgs.argv;
+ result->item = *result->u.argvArgs.item;
+ return result;
+}
+
+extern Arguments* argNewFromFile(FILE* const fp) {
+ Arguments* result = xMalloc(1, Arguments);
+ memset(result, 0, sizeof(Arguments));
+ result->type = ARG_FILE;
+ result->u.fileArgs.fp = fp;
+ result->item = nextFileString(result, result->u.fileArgs.fp);
+ return result;
+}
+
+extern Arguments* argNewFromLineFile(FILE* const fp) {
+ Arguments* result = xMalloc(1, Arguments);
+ memset(result, 0, sizeof(Arguments));
+ result->type = ARG_FILE;
+ result->lineMode = TRUE;
+ result->u.fileArgs.fp = fp;
+ result->item = nextFileString(result, result->u.fileArgs.fp);
+ return result;
+}
+
+extern char* argItem(const Arguments* const current) {
+ Assert(current != NULL);
+ Assert(!argOff(current));
+ return current->item;
+}
+
+extern boolean argOff(const Arguments* const current) {
+ Assert(current != NULL);
+ return (boolean)(current->item == NULL);
+}
+
+extern void argSetWordMode(Arguments* const current) {
+ Assert(current != NULL);
+ current->lineMode = FALSE;
+}
+
+extern void argSetLineMode(Arguments* const current) {
+ Assert(current != NULL);
+ current->lineMode = TRUE;
+}
+
+extern void argForth(Arguments* const current) {
+ Assert(current != NULL);
+ Assert(!argOff(current));
+ switch (current->type) {
+ case ARG_STRING:
+ if (current->item != NULL) eFree(current->item);
+ current->u.stringArgs.item = current->u.stringArgs.next;
+ current->item = nextString(current, ¤t->u.stringArgs.next);
+ break;
+ case ARG_ARGV:
+ ++current->u.argvArgs.item;
+ current->item = *current->u.argvArgs.item;
+ break;
+ case ARG_FILE:
+ if (current->item != NULL) eFree(current->item);
+ current->item = nextFileString(current, current->u.fileArgs.fp);
+ break;
+ default:
+ Assert("Invalid argument type" == NULL);
+ break;
+ }
+}
+
+extern void argDelete(Arguments* const current) {
+ Assert(current != NULL);
+ if (current->type == ARG_STRING && current->item != NULL)
+ eFree(current->item);
+ memset(current, 0, sizeof(Arguments));
+ eFree(current);
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/args.h b/third_party/ctags/args.h
new file mode 100644
index 000000000..5388faad3
--- /dev/null
+++ b/third_party/ctags/args.h
@@ -0,0 +1,47 @@
+#ifndef _ARGS_H
+#define _ARGS_H
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/stdio/stdio.h"
+
+/*
+ * DATA DECLARATIONS
+ */
+
+typedef enum { ARG_NONE, ARG_STRING, ARG_ARGV, ARG_FILE } argType;
+
+typedef struct sArgs {
+ argType type;
+ union {
+ struct sStringArgs {
+ const char* string;
+ const char* next;
+ const char* item;
+ } stringArgs;
+ struct sArgvArgs {
+ char* const* argv;
+ char* const* item;
+ } argvArgs;
+ struct sFileArgs {
+ FILE* fp;
+ } fileArgs;
+ } u;
+ char* item;
+ boolean lineMode;
+} Arguments;
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+extern Arguments* argNewFromString(const char* const string);
+extern Arguments* argNewFromArgv(char* const* const argv);
+extern Arguments* argNewFromFile(FILE* const fp);
+extern Arguments* argNewFromLineFile(FILE* const fp);
+extern char* argItem(const Arguments* const current);
+extern boolean argOff(const Arguments* const current);
+extern void argSetWordMode(Arguments* const current);
+extern void argSetLineMode(Arguments* const current);
+extern void argForth(Arguments* const current);
+extern void argDelete(Arguments* const current);
+
+#endif /* _ARGS_H */
diff --git a/third_party/ctags/asm.c b/third_party/ctags/asm.c
new file mode 100644
index 000000000..e60328d0f
--- /dev/null
+++ b/third_party/ctags/asm.c
@@ -0,0 +1,298 @@
+/*
+ * $Id: asm.c 536 2007-06-02 06:09:00Z elliotth $
+ *
+ * Copyright (c) 2000-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for assembly language
+ * files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DECLARATIONS
+ */
+typedef enum { K_NONE = -1, K_DEFINE, K_LABEL, K_MACRO, K_TYPE } AsmKind;
+
+typedef enum {
+ OP_UNDEFINED = -1,
+ OP_ALIGN,
+ OP_COLON_EQUAL,
+ OP_END,
+ OP_ENDM,
+ OP_ENDMACRO,
+ OP_ENDP,
+ OP_ENDS,
+ OP_EQU,
+ OP_EQUAL,
+ OP_LABEL,
+ OP_MACRO,
+ OP_PROC,
+ OP_RECORD,
+ OP_SECTIONS,
+ OP_SET,
+ OP_STRUCT,
+ OP_LAST
+} opKeyword;
+
+typedef struct {
+ const char *operator;
+ opKeyword keyword;
+} asmKeyword;
+
+typedef struct {
+ opKeyword keyword;
+ AsmKind kind;
+} opKind;
+
+/*
+ * DATA DEFINITIONS
+ */
+static langType Lang_asm;
+
+static kindOption AsmKinds[] = {
+ {TRUE, 'd', "define", "defines"},
+ {TRUE, 'l', "label", "labels"},
+ {TRUE, 'm', "macro", "macros"},
+ {TRUE, 't', "type", "types (structs and records)"}};
+
+static const asmKeyword AsmKeywords[] = {
+ {"align", OP_ALIGN}, {"endmacro", OP_ENDMACRO}, {"endm", OP_ENDM},
+ {"end", OP_END}, {"endp", OP_ENDP}, {"ends", OP_ENDS},
+ {"equ", OP_EQU}, {"label", OP_LABEL}, {"macro", OP_MACRO},
+ {":=", OP_COLON_EQUAL}, {"=", OP_EQUAL}, {"proc", OP_PROC},
+ {"record", OP_RECORD}, {"sections", OP_SECTIONS}, {"set", OP_SET},
+ {"struct", OP_STRUCT}};
+
+static const opKind OpKinds[] = {
+ /* must be ordered same as opKeyword enumeration */
+ {OP_ALIGN, K_NONE}, {OP_COLON_EQUAL, K_DEFINE}, {OP_END, K_NONE},
+ {OP_ENDM, K_NONE}, {OP_ENDMACRO, K_NONE}, {OP_ENDP, K_NONE},
+ {OP_ENDS, K_NONE}, {OP_EQU, K_DEFINE}, {OP_EQUAL, K_DEFINE},
+ {OP_LABEL, K_LABEL}, {OP_MACRO, K_MACRO}, {OP_PROC, K_LABEL},
+ {OP_RECORD, K_TYPE}, {OP_SECTIONS, K_NONE}, {OP_SET, K_DEFINE},
+ {OP_STRUCT, K_TYPE}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+static void buildAsmKeywordHash(void) {
+ const size_t count = sizeof(AsmKeywords) / sizeof(AsmKeywords[0]);
+ size_t i;
+ for (i = 0; i < count; ++i) {
+ const asmKeyword *const p = AsmKeywords + i;
+ addKeyword(p->operator, Lang_asm,(int) p->keyword);
+ }
+}
+
+static opKeyword analyzeOperator(const vString *const op) {
+ vString *keyword = vStringNew();
+ opKeyword result;
+
+ vStringCopyToLower(keyword, op);
+ result = (opKeyword)lookupKeyword(vStringValue(keyword), Lang_asm);
+ vStringDelete(keyword);
+ return result;
+}
+
+static boolean isInitialSymbolCharacter(int c) {
+ return (boolean)(c != '\0' && (isalpha(c) || strchr("_$", c) != NULL));
+}
+
+static boolean isSymbolCharacter(int c) {
+ /* '?' character is allowed in AMD 29K family */
+ return (boolean)(c != '\0' && (isalnum(c) || strchr("_$?", c) != NULL));
+}
+
+static boolean readPreProc(const unsigned char *const line) {
+ boolean result;
+ const unsigned char *cp = line;
+ vString *name = vStringNew();
+ while (isSymbolCharacter((int)*cp)) {
+ vStringPut(name, *cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ result = (boolean)(strcmp(vStringValue(name), "define") == 0);
+ if (result) {
+ while (isspace((int)*cp)) ++cp;
+ vStringClear(name);
+ while (isSymbolCharacter((int)*cp)) {
+ vStringPut(name, *cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ makeSimpleTag(name, AsmKinds, K_DEFINE);
+ }
+ vStringDelete(name);
+ return result;
+}
+
+static AsmKind operatorKind(const vString *const operator,
+ boolean * const found) {
+ AsmKind result = K_NONE;
+ const opKeyword kw = analyzeOperator(operator);
+ *found = (boolean)(kw != OP_UNDEFINED);
+ if (*found) {
+ result = OpKinds[kw].kind;
+ Assert(OpKinds[kw].keyword == kw);
+ }
+ return result;
+}
+
+/* We must check for "DB", "DB.L", "DCB.W" (68000)
+ */
+static boolean isDefineOperator(const vString *const operator) {
+ const unsigned char *const op = (unsigned char *)vStringValue(operator);
+ const size_t length = vStringLength(operator);
+ const boolean result =
+ (boolean)(length > 0 && toupper((int)*op) == 'D' &&
+ (length == 2 || (length == 4 && (int)op[2] == '.') ||
+ (length == 5 && (int)op[3] == '.')));
+ return result;
+}
+
+static void makeAsmTag(const vString *const name, const vString *const operator,
+ const boolean labelCandidate,
+ const boolean nameFollows) {
+ if (vStringLength(name) > 0) {
+ boolean found;
+ const AsmKind kind = operatorKind(operator, & found);
+ if (found) {
+ if (kind != K_NONE) makeSimpleTag(name, AsmKinds, kind);
+ } else if (isDefineOperator(operator)) {
+ if (!nameFollows) makeSimpleTag(name, AsmKinds, K_DEFINE);
+ } else if (labelCandidate) {
+ operatorKind(name, &found);
+ if (!found) makeSimpleTag(name, AsmKinds, K_LABEL);
+ }
+ }
+}
+
+static const unsigned char *readSymbol(const unsigned char *const start,
+ vString *const sym) {
+ const unsigned char *cp = start;
+ vStringClear(sym);
+ if (isInitialSymbolCharacter((int)*cp)) {
+ while (isSymbolCharacter((int)*cp)) {
+ vStringPut(sym, *cp);
+ ++cp;
+ }
+ vStringTerminate(sym);
+ }
+ return cp;
+}
+
+static const unsigned char *readOperator(const unsigned char *const start,
+ vString *const operator) {
+ const unsigned char *cp = start;
+ vStringClear(operator);
+ while (*cp != '\0' && !isspace((int)*cp)) {
+ vStringPut(operator, * cp);
+ ++cp;
+ }
+ vStringTerminate(operator);
+ return cp;
+}
+
+static void findAsmTags(void) {
+ vString *name = vStringNew();
+ vString *operator= vStringNew();
+ const unsigned char *line;
+ boolean inCComment = FALSE;
+
+ while ((line = fileReadLine()) != NULL) {
+ const unsigned char *cp = line;
+ boolean labelCandidate = (boolean)(!isspace((int)*cp));
+ boolean nameFollows = FALSE;
+ const boolean isComment =
+ (boolean)(*cp != '\0' && strchr(";*@", *cp) != NULL);
+
+ /* skip comments */
+ if (strncmp((const char *)cp, "/*", (size_t)2) == 0) {
+ inCComment = TRUE;
+ cp += 2;
+ }
+ if (inCComment) {
+ do {
+ if (strncmp((const char *)cp, "*/", (size_t)2) == 0) {
+ inCComment = FALSE;
+ cp += 2;
+ break;
+ }
+ ++cp;
+ } while (*cp != '\0');
+ }
+ if (isComment || inCComment) continue;
+
+ /* read preprocessor defines */
+ if (*cp == '#') {
+ ++cp;
+ readPreProc(cp);
+ continue;
+ }
+
+ /* skip white space */
+ while (isspace((int)*cp)) ++cp;
+
+ /* read symbol */
+ cp = readSymbol(cp, name);
+ if (vStringLength(name) > 0 && *cp == ':') {
+ labelCandidate = TRUE;
+ ++cp;
+ }
+
+ if (!isspace((int)*cp) && *cp != '\0') continue;
+
+ /* skip white space */
+ while (isspace((int)*cp)) ++cp;
+
+ /* skip leading dot */
+#if 0
+ if (*cp == '.')
+ ++cp;
+#endif
+
+ cp = readOperator(cp, operator);
+
+ /* attempt second read of symbol */
+ if (vStringLength(name) == 0) {
+ while (isspace((int)*cp)) ++cp;
+ cp = readSymbol(cp, name);
+ nameFollows = TRUE;
+ }
+ makeAsmTag(name, operator, labelCandidate, nameFollows);
+ }
+ vStringDelete(name);
+ vStringDelete(operator);
+}
+
+static void initialize(const langType language) {
+ Lang_asm = language;
+ buildAsmKeywordHash();
+}
+
+extern parserDefinition *AsmParser(void) {
+ static const char *const extensions[] = {"asm", "ASM", "s", "S", NULL};
+ static const char *const patterns[] = {
+ "*.A51", "*.29[kK]", "*.[68][68][kKsSxX]", "*.[xX][68][68]", NULL};
+ parserDefinition *def = parserNew("Asm");
+ def->kinds = AsmKinds;
+ def->kindCount = KIND_COUNT(AsmKinds);
+ def->extensions = extensions;
+ def->patterns = patterns;
+ def->parser = findAsmTags;
+ def->initialize = initialize;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/asp.c b/third_party/ctags/asp.c
new file mode 100644
index 000000000..fd72494d5
--- /dev/null
+++ b/third_party/ctags/asp.c
@@ -0,0 +1,255 @@
+/*
+ * $Id: asp.c 711 2009-07-04 16:52:11Z dhiebert $
+ *
+ * Copyright (c) 2000, Patrick Dehne
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for the ASP (Active
+ * Server Pages) web page scripting language.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum { K_CONST, K_CLASS, K_FUNCTION, K_SUB, K_DIM } aspKind;
+
+static kindOption AspKinds[] = {{TRUE, 'd', "constant", "constants"},
+ {TRUE, 'c', "class", "classes"},
+ {TRUE, 'f', "function", "functions"},
+ {TRUE, 's', "subroutine", "subroutines"},
+ {TRUE, 'v', "variable", "variables"}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void findAspTags(void) {
+ vString *name = vStringNew();
+ const unsigned char *line;
+
+ while ((line = fileReadLine()) != NULL) {
+ const unsigned char *cp = line;
+
+ while (*cp != '\0') {
+ /* jump over whitespace */
+ while (isspace((int)*cp)) cp++;
+
+ /* jump over strings */
+ if (*cp == '"') {
+ cp++;
+ while (*cp != '"' && *cp != '\0') cp++;
+ }
+
+ /* jump over comments */
+ else if (*cp == '\'')
+ break;
+
+ /* jump over end function/sub lines */
+ else if (strncasecmp((const char *)cp, "end", (size_t)3) == 0) {
+ cp += 3;
+ if (isspace((int)*cp)) {
+ while (isspace((int)*cp)) ++cp;
+
+ if (strncasecmp((const char *)cp, "function", (size_t)8) == 0) {
+ cp += 8;
+ break;
+ }
+
+ else if (strncasecmp((const char *)cp, "sub", (size_t)3) == 0) {
+ cp += 3;
+ break;
+ }
+ }
+ }
+
+ /* jump over exit function/sub lines */
+ else if (strncasecmp((const char *)cp, "exit", (size_t)4) == 0) {
+ cp += 4;
+ if (isspace((int)*cp)) {
+ while (isspace((int)*cp)) ++cp;
+
+ if (strncasecmp((const char *)cp, "function", (size_t)8) == 0) {
+ cp += 8;
+ break;
+ }
+
+ else if (strncasecmp((const char *)cp, "sub", (size_t)3) == 0) {
+ cp += 3;
+ break;
+ }
+ }
+ }
+
+ /* class member? */
+ else if (strncasecmp((const char *)cp, "public", (size_t)6) == 0) {
+ cp += 6;
+ if (isspace((int)*cp)) {
+ while (isspace((int)*cp)) ++cp;
+ if (strncasecmp((const char *)cp, "function", (size_t)8) == 0) {
+ cp += 8;
+ while (isspace((int)*cp)) ++cp;
+ while (isalnum((int)*cp) || *cp == '_') {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ makeSimpleTag(name, AspKinds, K_FUNCTION);
+ vStringClear(name);
+ } else if (strncasecmp((const char *)cp, "sub", (size_t)3) == 0) {
+ cp += 3;
+ while (isspace((int)*cp)) ++cp;
+ while (isalnum((int)*cp) || *cp == '_') {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ makeSimpleTag(name, AspKinds, K_SUB);
+ vStringClear(name);
+ } else {
+ while (isalnum((int)*cp) || *cp == '_') {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ makeSimpleTag(name, AspKinds, K_DIM);
+ vStringClear(name);
+ }
+ }
+ } else if (strncasecmp((const char *)cp, "private", (size_t)7) == 0) {
+ cp += 7;
+ if (isspace((int)*cp)) {
+ while (isspace((int)*cp)) ++cp;
+ if (strncasecmp((const char *)cp, "function", (size_t)8) == 0) {
+ cp += 8;
+ while (isspace((int)*cp)) ++cp;
+ while (isalnum((int)*cp) || *cp == '_') {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ makeSimpleTag(name, AspKinds, K_FUNCTION);
+ vStringClear(name);
+ } else if (strncasecmp((const char *)cp, "sub", (size_t)3) == 0) {
+ cp += 3;
+ while (isspace((int)*cp)) ++cp;
+ while (isalnum((int)*cp) || *cp == '_') {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ makeSimpleTag(name, AspKinds, K_SUB);
+ vStringClear(name);
+ } else {
+ while (isalnum((int)*cp) || *cp == '_') {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ makeSimpleTag(name, AspKinds, K_DIM);
+ vStringClear(name);
+ }
+ }
+ }
+
+ /* function? */
+ else if (strncasecmp((const char *)cp, "function", (size_t)8) == 0) {
+ cp += 8;
+
+ if (isspace((int)*cp)) {
+ while (isspace((int)*cp)) ++cp;
+ while (isalnum((int)*cp) || *cp == '_') {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ makeSimpleTag(name, AspKinds, K_FUNCTION);
+ vStringClear(name);
+ }
+ }
+
+ /* sub? */
+ else if (strncasecmp((const char *)cp, "sub", (size_t)3) == 0) {
+ cp += 3;
+ if (isspace((int)*cp)) {
+ while (isspace((int)*cp)) ++cp;
+ while (isalnum((int)*cp) || *cp == '_') {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ makeSimpleTag(name, AspKinds, K_SUB);
+ vStringClear(name);
+ }
+ }
+
+ /* dim variable? */
+ else if (strncasecmp((const char *)cp, "dim", (size_t)3) == 0) {
+ cp += 3;
+ if (isspace((int)*cp)) {
+ while (isspace((int)*cp)) ++cp;
+ while (isalnum((int)*cp) || *cp == '_') {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ makeSimpleTag(name, AspKinds, K_DIM);
+ vStringClear(name);
+ }
+ }
+
+ /* class declaration? */
+ else if (strncasecmp((const char *)cp, "class", (size_t)5) == 0) {
+ cp += 5;
+ if (isspace((int)*cp)) {
+ while (isspace((int)*cp)) ++cp;
+ while (isalnum((int)*cp) || *cp == '_') {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ makeSimpleTag(name, AspKinds, K_CLASS);
+ vStringClear(name);
+ }
+ }
+
+ /* const declaration? */
+ else if (strncasecmp((const char *)cp, "const", (size_t)5) == 0) {
+ cp += 5;
+ if (isspace((int)*cp)) {
+ while (isspace((int)*cp)) ++cp;
+ while (isalnum((int)*cp) || *cp == '_') {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ makeSimpleTag(name, AspKinds, K_CONST);
+ vStringClear(name);
+ }
+ }
+
+ /* nothing relevant */
+ else if (*cp != '\0')
+ cp++;
+ }
+ }
+ vStringDelete(name);
+}
+
+extern parserDefinition *AspParser(void) {
+ static const char *const extensions[] = {"asp", "asa", NULL};
+ parserDefinition *def = parserNew("Asp");
+ def->kinds = AspKinds;
+ def->kindCount = KIND_COUNT(AspKinds);
+ def->extensions = extensions;
+ def->parser = findAspTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/awk.c b/third_party/ctags/awk.c
new file mode 100644
index 000000000..a44a6d94a
--- /dev/null
+++ b/third_party/ctags/awk.c
@@ -0,0 +1,63 @@
+/*
+ * $Id: awk.c 443 2006-05-30 04:37:13Z darren $
+ *
+ * Copyright (c) 2000-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for AWK functions.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/str/str.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum eAwkKinds { K_FUNCTION } awkKind;
+
+static kindOption AwkKinds[] = {{TRUE, 'f', "function", "functions"}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void findAwkTags(void) {
+ vString *name = vStringNew();
+ const unsigned char *line;
+
+ while ((line = fileReadLine()) != NULL) {
+ if (strncmp((const char *)line, "function", (size_t)8) == 0 &&
+ isspace((int)line[8])) {
+ const unsigned char *cp = line + 8;
+
+ while (isspace((int)*cp)) ++cp;
+ while (isalnum((int)*cp) || *cp == '_') {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ while (isspace((int)*cp)) ++cp;
+ if (*cp == '(') makeSimpleTag(name, AwkKinds, K_FUNCTION);
+ vStringClear(name);
+ if (*cp != '\0') ++cp;
+ }
+ }
+ vStringDelete(name);
+}
+
+extern parserDefinition *AwkParser() {
+ static const char *const extensions[] = {"awk", "gawk", "mawk", NULL};
+ parserDefinition *def = parserNew("Awk");
+ def->kinds = AwkKinds;
+ def->kindCount = KIND_COUNT(AwkKinds);
+ def->extensions = extensions;
+ def->parser = findAwkTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/basic.c b/third_party/ctags/basic.c
new file mode 100644
index 000000000..ba8271254
--- /dev/null
+++ b/third_party/ctags/basic.c
@@ -0,0 +1,175 @@
+/*
+ * $Id:$
+ *
+ * Copyright (c) 2000-2006, Darren Hiebert, Elias Pschernig
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for BlitzBasic
+ * (BlitzMax), PureBasic and FreeBasic language files. For now, this is kept
+ * quite simple - but feel free to ask for more things added any time -
+ * patches are of course most welcome.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum {
+ K_CONST,
+ K_FUNCTION,
+ K_LABEL,
+ K_TYPE,
+ K_VARIABLE,
+ K_ENUM
+} BasicKind;
+
+typedef struct {
+ char const *token;
+ BasicKind kind;
+ int skip;
+} KeyWord;
+
+static kindOption BasicKinds[] = {
+ {TRUE, 'c', "constant", "constants"}, {TRUE, 'f', "function", "functions"},
+ {TRUE, 'l', "label", "labels"}, {TRUE, 't', "type", "types"},
+ {TRUE, 'v', "variable", "variables"}, {TRUE, 'g', "enum", "enumerations"},
+};
+
+static KeyWord blitzbasic_keywords[] = {
+ {"const", K_CONST, 0}, {"global", K_VARIABLE, 0},
+ {"dim", K_VARIABLE, 0}, {"function", K_FUNCTION, 0},
+ {"type", K_TYPE, 0}, {NULL, 0, 0},
+};
+
+static KeyWord purebasic_keywords[] = {
+ {"newlist", K_VARIABLE, 0},
+ {"global", K_VARIABLE, 0},
+ {"dim", K_VARIABLE, 0},
+ {"procedure", K_FUNCTION, 0},
+ {"interface", K_TYPE, 0},
+ {"structure", K_TYPE, 0},
+ {NULL, 0, 0},
+};
+
+static KeyWord freebasic_keywords[] = {
+ {"const", K_CONST, 0},
+ {"dim as", K_VARIABLE, 1},
+ {"dim", K_VARIABLE, 0},
+ {"common", K_VARIABLE, 0},
+ {"function", K_FUNCTION, 0},
+ {"sub", K_FUNCTION, 0},
+ {"private sub", K_FUNCTION, 0},
+ {"public sub", K_FUNCTION, 0},
+ {"private function", K_FUNCTION, 0},
+ {"public function", K_FUNCTION, 0},
+ {"type", K_TYPE, 0},
+ {"enum", K_ENUM, 0},
+ {NULL, 0, 0},
+};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+/* Match the name of a tag (function, variable, type, ...) starting at pos. */
+static char const *extract_name(char const *pos, vString *name) {
+ while (isspace(*pos)) pos++;
+ vStringClear(name);
+ for (; *pos && !isspace(*pos) && *pos != '(' && *pos != ','; pos++)
+ vStringPut(name, *pos);
+ vStringTerminate(name);
+ return pos;
+}
+
+/* Match a keyword starting at p (case insensitive). */
+static int match_keyword(const char *p, KeyWord const *kw) {
+ vString *name;
+ size_t i;
+ int j;
+ for (i = 0; i < strlen(kw->token); i++) {
+ if (tolower(p[i]) != kw->token[i]) return 0;
+ }
+ name = vStringNew();
+ p += i;
+ for (j = 0; j < 1 + kw->skip; j++) {
+ p = extract_name(p, name);
+ }
+ makeSimpleTag(name, BasicKinds, kw->kind);
+ vStringDelete(name);
+ return 1;
+}
+
+/* Match a "label:" style label. */
+static void match_colon_label(char const *p) {
+ char const *end = p + strlen(p) - 1;
+ while (isspace(*end)) end--;
+ if (*end == ':') {
+ vString *name = vStringNew();
+ vStringNCatS(name, p, end - p);
+ makeSimpleTag(name, BasicKinds, K_LABEL);
+ vStringDelete(name);
+ }
+}
+
+/* Match a ".label" style label. */
+static void match_dot_label(char const *p) {
+ if (*p == '.') {
+ vString *name = vStringNew();
+ extract_name(p + 1, name);
+ makeSimpleTag(name, BasicKinds, K_LABEL);
+ vStringDelete(name);
+ }
+}
+
+static void findBasicTags(void) {
+ const char *line;
+ const char *extension = fileExtension(vStringValue(File.name));
+ KeyWord *keywords;
+
+ if (strcmp(extension, "bb") == 0)
+ keywords = blitzbasic_keywords;
+ else if (strcmp(extension, "pb") == 0)
+ keywords = purebasic_keywords;
+ else
+ keywords = freebasic_keywords;
+
+ while ((line = (const char *)fileReadLine()) != NULL) {
+ const char *p = line;
+ KeyWord const *kw;
+
+ while (isspace(*p)) p++;
+
+ /* Empty line? */
+ if (!*p) continue;
+
+ /* In Basic, keywords always are at the start of the line. */
+ for (kw = keywords; kw->token; kw++)
+ if (match_keyword(p, kw)) break;
+
+ /* Is it a label? */
+ if (strcmp(extension, "bb") == 0)
+ match_dot_label(p);
+ else
+ match_colon_label(p);
+ }
+}
+
+parserDefinition *BasicParser(void) {
+ static char const *extensions[] = {"bas", "bi", "bb", "pb", NULL};
+ parserDefinition *def = parserNew("Basic");
+ def->kinds = BasicKinds;
+ def->kindCount = KIND_COUNT(BasicKinds);
+ def->extensions = extensions;
+ def->parser = findBasicTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/beta.c b/third_party/ctags/beta.c
new file mode 100644
index 000000000..e7466c191
--- /dev/null
+++ b/third_party/ctags/beta.c
@@ -0,0 +1,273 @@
+/*
+ * $Id: beta.c 536 2007-06-02 06:09:00Z elliotth $
+ *
+ * Copyright (c) 1999-2000, Mjlner Informatics
+ *
+ * Written by Erik Corry
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for BETA language
+ * files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/calls/calls.h"
+#include "libc/str/str.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * MACROS
+ */
+#define isbident(c) (identarray[(unsigned char)(c)])
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum { K_FRAGMENT, K_PATTERN, K_SLOT, K_VIRTUAL } betaKind;
+
+static kindOption BetaKinds[] = {
+ {TRUE, 'f', "fragment", "fragment definitions"},
+ {FALSE, 'p', "pattern", "all patterns"},
+ {TRUE, 's', "slot", "slots (fragment uses)"},
+ {TRUE, 'v', "virtual", "patterns (virtual or rebound)"}};
+
+/* clang-format off */
+/* [A-Z_a-z0-9] */
+static const char identarray[256] ={
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 32-47 !"#$%&'()*+'-./ */
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 0123456789:;<=>? */
+0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 @ABCDEFGHIJKLMNO */
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 PQRSTUVWXYZ [\]^_ */
+0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 `abcdefghijklmno */
+1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 pqrstuvwxyz{|}~ */
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 128- */
+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}; /* -255 */
+/* clang-format on */
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void makeBetaTag(const char* const name, const betaKind kind) {
+ if (BetaKinds[kind].enabled) {
+ tagEntryInfo e;
+ initTagEntry(&e, name);
+ e.kindName = BetaKinds[kind].name;
+ e.kind = BetaKinds[kind].letter;
+ makeTagEntry(&e);
+ }
+}
+
+static void findBetaTags(void) {
+ vString* line = vStringNew();
+ boolean incomment = FALSE;
+ boolean inquote = FALSE;
+ boolean dovirtuals = BetaKinds[K_VIRTUAL].enabled;
+ boolean dopatterns = BetaKinds[K_PATTERN].enabled;
+
+ do {
+ boolean foundfragmenthere = FALSE;
+ /* find fragment definition (line that starts and ends with --) */
+ int last;
+ int first;
+ int c;
+
+ vStringClear(line);
+
+ while ((c = fileGetc()) != EOF && c != '\n' && c != '\r')
+ vStringPut(line, c);
+
+ vStringTerminate(line);
+
+ last = vStringLength(line) - 1;
+ first = 0;
+ /* skip white space at start and end of line */
+ while (last && isspace((int)vStringChar(line, last))) last--;
+ while (first < last && isspace((int)vStringChar(line, first))) first++;
+ /* if line still has a reasonable length and ... */
+ if (last - first > 4 && (vStringChar(line, first) == '-' &&
+ vStringChar(line, first + 1) == '-' &&
+ vStringChar(line, last) == '-' &&
+ vStringChar(line, last - 1) == '-')) {
+ if (!incomment && !inquote) {
+ foundfragmenthere = TRUE;
+ /* skip past -- and whitespace. Also skip back past 'dopart'
+ or 'attributes' to the :. We have to do this because there
+ is no sensible way to include whitespace in a ctags token
+ so the conventional space after the ':' would mess us up */
+ last -= 2;
+ first += 2;
+ while (last && vStringChar(line, last) != ':') last--;
+ while (last && (isspace((int)vStringChar(line, last - 1)))) last--;
+ while (first < last && (isspace((int)vStringChar(line, first)) ||
+ vStringChar(line, first) == '-'))
+ first++;
+ /* If there's anything left it is a fragment title */
+ if (first < last - 1) {
+ vStringChar(line, last) = 0;
+ if (strcasecmp("LIB", vStringValue(line) + first) &&
+ strcasecmp("PROGRAM", vStringValue(line) + first)) {
+ makeBetaTag(vStringValue(line) + first, K_FRAGMENT);
+ }
+ }
+ }
+ } else {
+ int pos = 0;
+ int len = vStringLength(line);
+ if (inquote) goto stringtext;
+ if (incomment) goto commenttext;
+ programtext:
+ for (; pos < len; pos++) {
+ if (vStringChar(line, pos) == '\'') {
+ pos++;
+ inquote = TRUE;
+ goto stringtext;
+ }
+ if (vStringChar(line, pos) == '{') {
+ pos++;
+ incomment = TRUE;
+ goto commenttext;
+ }
+ if (vStringChar(line, pos) == '(' && pos < len - 1 &&
+ vStringChar(line, pos + 1) == '*') {
+ pos += 2;
+ incomment = TRUE;
+ goto commenttext;
+ }
+ /*
+ * SLOT definition looks like this:
+ * <>
+ * or
+ * <>
+ */
+ if (!foundfragmenthere && vStringChar(line, pos) == '<' &&
+ pos + 1 < len && vStringChar(line, pos + 1) == '<' &&
+ strstr(vStringValue(line) + pos, ">>")) {
+ /* Found slot name, get start and end */
+ int eoname;
+ char c2;
+ pos += 2; /* skip past << */
+ /* skip past space before SLOT */
+ while (pos < len && isspace((int)vStringChar(line, pos))) pos++;
+ /* skip past SLOT */
+ if (pos + 4 <= len &&
+ !strncasecmp(vStringValue(line) + pos, "SLOT", (size_t)4))
+ pos += 4;
+ /* skip past space after SLOT */
+ while (pos < len && isspace((int)vStringChar(line, pos))) pos++;
+ eoname = pos;
+ /* skip to end of name */
+ while (eoname < len && (c2 = vStringChar(line, eoname)) != '>' &&
+ c2 != ':' && !isspace((int)c2))
+ eoname++;
+ if (eoname < len) {
+ vStringChar(line, eoname) = 0;
+ if (strcasecmp("LIB", vStringValue(line) + pos) &&
+ strcasecmp("PROGRAM", vStringValue(line) + pos) &&
+ strcasecmp("SLOT", vStringValue(line) + pos)) {
+ makeBetaTag(vStringValue(line) + pos, K_SLOT);
+ }
+ }
+ if (eoname + 1 < len) {
+ pos = eoname + 1;
+ } else {
+ pos = len;
+ continue;
+ }
+ }
+ /* Only patterns that are virtual, extensions of virtuals or
+ * final bindings are normally included so as not to overload
+ * totally.
+ * That means one of the forms name:: name:< or name::<
+ */
+ if (!foundfragmenthere && vStringChar(line, pos) == ':' &&
+ (dopatterns ||
+ (dovirtuals && (vStringChar(line, pos + 1) == ':' ||
+ vStringChar(line, pos + 1) == '<')))) {
+ /* Found pattern name, get start and end */
+ int eoname = pos;
+ int soname;
+ while (eoname && isspace((int)vStringChar(line, eoname - 1)))
+ eoname--;
+ foundanothername:
+ /* terminate right after name */
+ vStringChar(line, eoname) = 0;
+ soname = eoname;
+ while (soname && isbident(vStringChar(line, soname - 1))) {
+ soname--;
+ }
+ if (soname != eoname) {
+ makeBetaTag(vStringValue(line) + soname, K_PATTERN);
+ /* scan back past white space */
+ while (soname && isspace((int)vStringChar(line, soname - 1)))
+ soname--;
+ if (soname && vStringChar(line, soname - 1) == ',') {
+ /* we found a new pattern name before comma */
+ eoname = soname;
+ goto foundanothername;
+ }
+ }
+ }
+ }
+ goto endofline;
+ commenttext:
+ for (; pos < len; pos++) {
+ if (vStringChar(line, pos) == '*' && pos < len - 1 &&
+ vStringChar(line, pos + 1) == ')') {
+ pos += 2;
+ incomment = FALSE;
+ goto programtext;
+ }
+ if (vStringChar(line, pos) == '}') {
+ pos++;
+ incomment = FALSE;
+ goto programtext;
+ }
+ }
+ goto endofline;
+ stringtext:
+ for (; pos < len; pos++) {
+ if (vStringChar(line, pos) == '\\') {
+ if (pos < len - 1) pos++;
+ } else if (vStringChar(line, pos) == '\'') {
+ pos++;
+ /* support obsolete '' syntax */
+ if (pos < len && vStringChar(line, pos) == '\'') {
+ continue;
+ }
+ inquote = FALSE;
+ goto programtext;
+ }
+ }
+ }
+ endofline:
+ inquote = FALSE; /* This shouldn't really make a difference */
+ } while (!feof(File.fp));
+ vStringDelete(line);
+}
+
+extern parserDefinition* BetaParser(void) {
+ static const char* const extensions[] = {"bet", NULL};
+ parserDefinition* def = parserNew("BETA");
+ def->kinds = BetaKinds;
+ def->kindCount = KIND_COUNT(BetaKinds);
+ def->extensions = extensions;
+ def->parser = findBetaTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/c.c b/third_party/ctags/c.c
new file mode 100644
index 000000000..84ebb35f6
--- /dev/null
+++ b/third_party/ctags/c.c
@@ -0,0 +1,2966 @@
+/*
+ * $Id: c.c 689 2008-12-13 21:17:36Z elliotth $
+ *
+ * Copyright (c) 1996-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for parsing and scanning C, C++ and Java
+ * source files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/fmt/fmt.h"
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/get.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+
+/*
+ * MACROS
+ */
+
+#define activeToken(st) ((st)->token[(int)(st)->tokenIndex])
+#define parentDecl(st) \
+ ((st)->parent == NULL ? DECL_NONE : (st)->parent->declaration)
+#define isType(token, t) (boolean)((token)->type == (t))
+#define insideEnumBody(st) \
+ ((st)->parent == NULL ? FALSE \
+ : (boolean)((st)->parent->declaration == DECL_ENUM))
+#define isExternCDecl(st, c) \
+ (boolean)((c) == STRING_SYMBOL && !(st)->haveQualifyingName && \
+ (st)->scope == SCOPE_EXTERN)
+
+#define isOneOf(c, s) (boolean)(strchr((s), (c)) != NULL)
+
+#define isHighChar(c) ((c) != EOF && (unsigned char)(c) >= 0xc0)
+
+/*
+ * DATA DECLARATIONS
+ */
+
+enum { NumTokens = 3 };
+
+typedef enum eException {
+ ExceptionNone,
+ ExceptionEOF,
+ ExceptionFormattingError,
+ ExceptionBraceFormattingError
+} exception_t;
+
+/* Used to specify type of keyword.
+ */
+typedef enum eKeywordId {
+ KEYWORD_NONE = -1,
+ KEYWORD_ATTRIBUTE,
+ KEYWORD_ABSTRACT,
+ KEYWORD_BOOLEAN,
+ KEYWORD_BYTE,
+ KEYWORD_BAD_STATE,
+ KEYWORD_BAD_TRANS,
+ KEYWORD_BIND,
+ KEYWORD_BIND_VAR,
+ KEYWORD_BIT,
+ KEYWORD_CASE,
+ KEYWORD_CATCH,
+ KEYWORD_CHAR,
+ KEYWORD_CLASS,
+ KEYWORD_CONST,
+ KEYWORD_CONSTRAINT,
+ KEYWORD_COVERAGE_BLOCK,
+ KEYWORD_COVERAGE_DEF,
+ KEYWORD_DEFAULT,
+ KEYWORD_DELEGATE,
+ KEYWORD_DELETE,
+ KEYWORD_DO,
+ KEYWORD_DOUBLE,
+ KEYWORD_ELSE,
+ KEYWORD_ENUM,
+ KEYWORD_EXPLICIT,
+ KEYWORD_EXTERN,
+ KEYWORD_EXTENDS,
+ KEYWORD_EVENT,
+ KEYWORD_FINAL,
+ KEYWORD_FLOAT,
+ KEYWORD_FOR,
+ KEYWORD_FOREACH,
+ KEYWORD_FRIEND,
+ KEYWORD_FUNCTION,
+ KEYWORD_GOTO,
+ KEYWORD_IF,
+ KEYWORD_IMPLEMENTS,
+ KEYWORD_IMPORT,
+ KEYWORD_INLINE,
+ KEYWORD_INT,
+ KEYWORD_INOUT,
+ KEYWORD_INPUT,
+ KEYWORD_INTEGER,
+ KEYWORD_INTERFACE,
+ KEYWORD_INTERNAL,
+ KEYWORD_LOCAL,
+ KEYWORD_LONG,
+ KEYWORD_M_BAD_STATE,
+ KEYWORD_M_BAD_TRANS,
+ KEYWORD_M_STATE,
+ KEYWORD_M_TRANS,
+ KEYWORD_MUTABLE,
+ KEYWORD_NAMESPACE,
+ KEYWORD_NEW,
+ KEYWORD_NEWCOV,
+ KEYWORD_NATIVE,
+ KEYWORD_OPERATOR,
+ KEYWORD_OUTPUT,
+ KEYWORD_OVERLOAD,
+ KEYWORD_OVERRIDE,
+ KEYWORD_PACKED,
+ KEYWORD_PORT,
+ KEYWORD_PACKAGE,
+ KEYWORD_PRIVATE,
+ KEYWORD_PROGRAM,
+ KEYWORD_PROTECTED,
+ KEYWORD_PUBLIC,
+ KEYWORD_REGISTER,
+ KEYWORD_RETURN,
+ KEYWORD_SHADOW,
+ KEYWORD_STATE,
+ KEYWORD_SHORT,
+ KEYWORD_SIGNED,
+ KEYWORD_STATIC,
+ KEYWORD_STRING,
+ KEYWORD_STRUCT,
+ KEYWORD_SWITCH,
+ KEYWORD_SYNCHRONIZED,
+ KEYWORD_TASK,
+ KEYWORD_TEMPLATE,
+ KEYWORD_THIS,
+ KEYWORD_THROW,
+ KEYWORD_THROWS,
+ KEYWORD_TRANSIENT,
+ KEYWORD_TRANS,
+ KEYWORD_TRANSITION,
+ KEYWORD_TRY,
+ KEYWORD_TYPEDEF,
+ KEYWORD_TYPENAME,
+ KEYWORD_UINT,
+ KEYWORD_ULONG,
+ KEYWORD_UNION,
+ KEYWORD_UNSIGNED,
+ KEYWORD_USHORT,
+ KEYWORD_USING,
+ KEYWORD_VIRTUAL,
+ KEYWORD_VOID,
+ KEYWORD_VOLATILE,
+ KEYWORD_WCHAR_T,
+ KEYWORD_WHILE
+} keywordId;
+
+/* Used to determine whether keyword is valid for the current language and
+ * what its ID is.
+ */
+typedef struct sKeywordDesc {
+ const char *name;
+ keywordId id;
+ short isValid[5]; /* indicates languages for which kw is valid */
+} keywordDesc;
+
+/* Used for reporting the type of object parsed by nextToken ().
+ */
+typedef enum eTokenType {
+ TOKEN_NONE, /* none */
+ TOKEN_ARGS, /* a parenthetical pair and its contents */
+ TOKEN_BRACE_CLOSE,
+ TOKEN_BRACE_OPEN,
+ TOKEN_COLON, /* the colon character */
+ TOKEN_COMMA, /* the comma character */
+ TOKEN_DOUBLE_COLON, /* double colon indicates nested-name-specifier */
+ TOKEN_KEYWORD,
+ TOKEN_NAME, /* an unknown name */
+ TOKEN_PACKAGE, /* a Java package name */
+ TOKEN_PAREN_NAME, /* a single name in parentheses */
+ TOKEN_SEMICOLON, /* the semicolon character */
+ TOKEN_SPEC, /* a storage class specifier, qualifier, type, etc. */
+ TOKEN_COUNT
+} tokenType;
+
+/* This describes the scoping of the current statement.
+ */
+typedef enum eTagScope {
+ SCOPE_GLOBAL, /* no storage class specified */
+ SCOPE_STATIC, /* static storage class */
+ SCOPE_EXTERN, /* external storage class */
+ SCOPE_FRIEND, /* declares access only */
+ SCOPE_TYPEDEF, /* scoping depends upon context */
+ SCOPE_COUNT
+} tagScope;
+
+typedef enum eDeclaration {
+ DECL_NONE,
+ DECL_BASE, /* base type (default) */
+ DECL_CLASS,
+ DECL_ENUM,
+ DECL_EVENT,
+ DECL_FUNCTION,
+ DECL_IGNORE, /* non-taggable "declaration" */
+ DECL_INTERFACE,
+ DECL_NAMESPACE,
+ DECL_NOMANGLE, /* C++ name demangling block */
+ DECL_PACKAGE,
+ DECL_PROGRAM, /* Vera program */
+ DECL_STRUCT,
+ DECL_TASK, /* Vera task */
+ DECL_UNION,
+ DECL_COUNT
+} declType;
+
+typedef enum eVisibilityType {
+ ACCESS_UNDEFINED,
+ ACCESS_LOCAL,
+ ACCESS_PRIVATE,
+ ACCESS_PROTECTED,
+ ACCESS_PUBLIC,
+ ACCESS_DEFAULT, /* Java-specific */
+ ACCESS_COUNT
+} accessType;
+
+/* Information about the parent class of a member (if any).
+ */
+typedef struct sMemberInfo {
+ accessType access; /* access of current statement */
+ accessType accessDefault; /* access default for current statement */
+} memberInfo;
+
+typedef struct sTokenInfo {
+ tokenType type;
+ keywordId keyword;
+ vString *name; /* the name of the token */
+ unsigned long lineNumber; /* line number of tag */
+ fpos_t filePosition; /* file position of line containing name */
+} tokenInfo;
+
+typedef enum eImplementation {
+ IMP_DEFAULT,
+ IMP_ABSTRACT,
+ IMP_VIRTUAL,
+ IMP_PURE_VIRTUAL,
+ IMP_COUNT
+} impType;
+
+/* Describes the statement currently undergoing analysis.
+ */
+typedef struct sStatementInfo {
+ tagScope scope;
+ declType declaration; /* specifier associated with TOKEN_SPEC */
+ boolean gotName; /* was a name parsed yet? */
+ boolean haveQualifyingName; /* do we have a name we are considering? */
+ boolean gotParenName; /* was a name inside parentheses parsed yet? */
+ boolean gotArgs; /* was a list of parameters parsed yet? */
+ boolean isPointer; /* is 'name' a pointer? */
+ boolean inFunction; /* are we inside of a function? */
+ boolean assignment; /* have we handled an '='? */
+ boolean notVariable; /* has a variable declaration been disqualified ? */
+ impType implementation; /* abstract or concrete implementation? */
+ unsigned int tokenIndex; /* currently active token */
+ tokenInfo *token[(int)NumTokens];
+ tokenInfo *context; /* accumulated scope of current statement */
+ tokenInfo *blockName; /* name of current block */
+ memberInfo member; /* information regarding parent class/struct */
+ vString *parentClasses; /* parent classes */
+ struct sStatementInfo *parent; /* statement we are nested within */
+} statementInfo;
+
+/* Describes the type of tag being generated.
+ */
+typedef enum eTagType {
+ TAG_UNDEFINED,
+ TAG_CLASS, /* class name */
+ TAG_ENUM, /* enumeration name */
+ TAG_ENUMERATOR, /* enumerator (enumeration value) */
+ TAG_EVENT, /* event */
+ TAG_FIELD, /* field (Java) */
+ TAG_FUNCTION, /* function definition */
+ TAG_INTERFACE, /* interface declaration */
+ TAG_LOCAL, /* local variable definition */
+ TAG_MEMBER, /* structure, class or interface member */
+ TAG_METHOD, /* method declaration */
+ TAG_NAMESPACE, /* namespace name */
+ TAG_PACKAGE, /* package name */
+ TAG_PROGRAM, /* program name */
+ TAG_PROPERTY, /* property name */
+ TAG_PROTOTYPE, /* function prototype or declaration */
+ TAG_STRUCT, /* structure name */
+ TAG_TASK, /* task name */
+ TAG_TYPEDEF, /* typedef name */
+ TAG_UNION, /* union name */
+ TAG_VARIABLE, /* variable definition */
+ TAG_EXTERN_VAR, /* external variable declaration */
+ TAG_COUNT /* must be last */
+} tagType;
+
+typedef struct sParenInfo {
+ boolean isPointer;
+ boolean isParamList;
+ boolean isKnrParamList;
+ boolean isNameCandidate;
+ boolean invalidContents;
+ boolean nestedArgs;
+ unsigned int parameterCount;
+} parenInfo;
+
+/*
+ * DATA DEFINITIONS
+ */
+
+static jmp_buf Exception;
+
+static langType Lang_c;
+static langType Lang_cpp;
+static langType Lang_csharp;
+static langType Lang_java;
+static langType Lang_vera;
+static vString *Signature;
+static boolean CollectingSignature;
+
+/* Number used to uniquely identify anonymous structs and unions. */
+static int AnonymousID = 0;
+
+/* Used to index into the CKinds table. */
+typedef enum {
+ CK_UNDEFINED = -1,
+ CK_CLASS,
+ CK_DEFINE,
+ CK_ENUMERATOR,
+ CK_FUNCTION,
+ CK_ENUMERATION,
+ CK_LOCAL,
+ CK_MEMBER,
+ CK_NAMESPACE,
+ CK_PROTOTYPE,
+ CK_STRUCT,
+ CK_TYPEDEF,
+ CK_UNION,
+ CK_VARIABLE,
+ CK_EXTERN_VARIABLE
+} cKind;
+
+static kindOption CKinds[] = {
+ {TRUE, 'c', "class", "classes"},
+ {TRUE, 'd', "macro", "macro definitions"},
+ {TRUE, 'e', "enumerator", "enumerators (values inside an enumeration)"},
+ {TRUE, 'f', "function", "function definitions"},
+ {TRUE, 'g', "enum", "enumeration names"},
+ {FALSE, 'l', "local", "local variables"},
+ {TRUE, 'm', "member", "class, struct, and union members"},
+ {TRUE, 'n', "namespace", "namespaces"},
+ {FALSE, 'p', "prototype", "function prototypes"},
+ {TRUE, 's', "struct", "structure names"},
+ {TRUE, 't', "typedef", "typedefs"},
+ {TRUE, 'u', "union", "union names"},
+ {TRUE, 'v', "variable", "variable definitions"},
+ {FALSE, 'x', "externvar", "external and forward variable declarations"},
+};
+
+typedef enum {
+ CSK_UNDEFINED = -1,
+ CSK_CLASS,
+ CSK_DEFINE,
+ CSK_ENUMERATOR,
+ CSK_EVENT,
+ CSK_FIELD,
+ CSK_ENUMERATION,
+ CSK_INTERFACE,
+ CSK_LOCAL,
+ CSK_METHOD,
+ CSK_NAMESPACE,
+ CSK_PROPERTY,
+ CSK_STRUCT,
+ CSK_TYPEDEF
+} csharpKind;
+
+static kindOption CsharpKinds[] = {
+ {TRUE, 'c', "class", "classes"},
+ {TRUE, 'd', "macro", "macro definitions"},
+ {TRUE, 'e', "enumerator", "enumerators (values inside an enumeration)"},
+ {TRUE, 'E', "event", "events"},
+ {TRUE, 'f', "field", "fields"},
+ {TRUE, 'g', "enum", "enumeration names"},
+ {TRUE, 'i', "interface", "interfaces"},
+ {FALSE, 'l', "local", "local variables"},
+ {TRUE, 'm', "method", "methods"},
+ {TRUE, 'n', "namespace", "namespaces"},
+ {TRUE, 'p', "property", "properties"},
+ {TRUE, 's', "struct", "structure names"},
+ {TRUE, 't', "typedef", "typedefs"},
+};
+
+/* Used to index into the JavaKinds table. */
+typedef enum {
+ JK_UNDEFINED = -1,
+ JK_CLASS,
+ JK_ENUM_CONSTANT,
+ JK_FIELD,
+ JK_ENUM,
+ JK_INTERFACE,
+ JK_LOCAL,
+ JK_METHOD,
+ JK_PACKAGE,
+ JK_ACCESS,
+ JK_CLASS_PREFIX
+} javaKind;
+
+static kindOption JavaKinds[] = {
+ {TRUE, 'c', "class", "classes"},
+ {TRUE, 'e', "enum constant", "enum constants"},
+ {TRUE, 'f', "field", "fields"},
+ {TRUE, 'g', "enum", "enum types"},
+ {TRUE, 'i', "interface", "interfaces"},
+ {FALSE, 'l', "local", "local variables"},
+ {TRUE, 'm', "method", "methods"},
+ {TRUE, 'p', "package", "packages"},
+};
+
+/* Used to index into the VeraKinds table. */
+typedef enum {
+ VK_UNDEFINED = -1,
+ VK_CLASS,
+ VK_DEFINE,
+ VK_ENUMERATOR,
+ VK_FUNCTION,
+ VK_ENUMERATION,
+ VK_LOCAL,
+ VK_MEMBER,
+ VK_PROGRAM,
+ VK_PROTOTYPE,
+ VK_TASK,
+ VK_TYPEDEF,
+ VK_VARIABLE,
+ VK_EXTERN_VARIABLE
+} veraKind;
+
+static kindOption VeraKinds[] = {
+ {TRUE, 'c', "class", "classes"},
+ {TRUE, 'd', "macro", "macro definitions"},
+ {TRUE, 'e', "enumerator", "enumerators (values inside an enumeration)"},
+ {TRUE, 'f', "function", "function definitions"},
+ {TRUE, 'g', "enum", "enumeration names"},
+ {FALSE, 'l', "local", "local variables"},
+ {TRUE, 'm', "member", "class, struct, and union members"},
+ {TRUE, 'p', "program", "programs"},
+ {FALSE, 'P', "prototype", "function prototypes"},
+ {TRUE, 't', "task", "tasks"},
+ {TRUE, 'T', "typedef", "typedefs"},
+ {TRUE, 'v', "variable", "variable definitions"},
+ {FALSE, 'x', "externvar", "external variable declarations"}};
+
+static const keywordDesc KeywordTable[] = {
+ /* C++ */
+ /* ANSI C | C# Java */
+ /* | | | | Vera */
+ /* keyword keyword ID | | | | | */
+ {"__attribute__", KEYWORD_ATTRIBUTE, {1, 1, 1, 0, 0}},
+ {"abstract", KEYWORD_ABSTRACT, {0, 0, 1, 1, 0}},
+ {"bad_state", KEYWORD_BAD_STATE, {0, 0, 0, 0, 1}},
+ {"bad_trans", KEYWORD_BAD_TRANS, {0, 0, 0, 0, 1}},
+ {"bind", KEYWORD_BIND, {0, 0, 0, 0, 1}},
+ {"bind_var", KEYWORD_BIND_VAR, {0, 0, 0, 0, 1}},
+ {"bit", KEYWORD_BIT, {0, 0, 0, 0, 1}},
+ {"boolean", KEYWORD_BOOLEAN, {0, 0, 0, 1, 0}},
+ {"byte", KEYWORD_BYTE, {0, 0, 0, 1, 0}},
+ {"case", KEYWORD_CASE, {1, 1, 1, 1, 0}},
+ {"catch", KEYWORD_CATCH, {0, 1, 1, 0, 0}},
+ {"char", KEYWORD_CHAR, {1, 1, 1, 1, 0}},
+ {"class", KEYWORD_CLASS, {0, 1, 1, 1, 1}},
+ {"const", KEYWORD_CONST, {1, 1, 1, 1, 0}},
+ {"constraint", KEYWORD_CONSTRAINT, {0, 0, 0, 0, 1}},
+ {"coverage_block", KEYWORD_COVERAGE_BLOCK, {0, 0, 0, 0, 1}},
+ {"coverage_def", KEYWORD_COVERAGE_DEF, {0, 0, 0, 0, 1}},
+ {"do", KEYWORD_DO, {1, 1, 1, 1, 0}},
+ {"default", KEYWORD_DEFAULT, {1, 1, 1, 1, 0}},
+ {"delegate", KEYWORD_DELEGATE, {0, 0, 1, 0, 0}},
+ {"delete", KEYWORD_DELETE, {0, 1, 0, 0, 0}},
+ {"double", KEYWORD_DOUBLE, {1, 1, 1, 1, 0}},
+ {"else", KEYWORD_ELSE, {1, 1, 1, 1, 0}},
+ {"enum", KEYWORD_ENUM, {1, 1, 1, 1, 1}},
+ {"event", KEYWORD_EVENT, {0, 0, 1, 0, 1}},
+ {"explicit", KEYWORD_EXPLICIT, {0, 1, 1, 0, 0}},
+ {"extends", KEYWORD_EXTENDS, {0, 0, 0, 1, 1}},
+ {"extern", KEYWORD_EXTERN, {1, 1, 1, 0, 1}},
+ {"final", KEYWORD_FINAL, {0, 0, 0, 1, 0}},
+ {"float", KEYWORD_FLOAT, {1, 1, 1, 1, 0}},
+ {"for", KEYWORD_FOR, {1, 1, 1, 1, 0}},
+ {"foreach", KEYWORD_FOREACH, {0, 0, 1, 0, 0}},
+ {"friend", KEYWORD_FRIEND, {0, 1, 0, 0, 0}},
+ {"function", KEYWORD_FUNCTION, {0, 0, 0, 0, 1}},
+ {"goto", KEYWORD_GOTO, {1, 1, 1, 1, 0}},
+ {"if", KEYWORD_IF, {1, 1, 1, 1, 0}},
+ {"implements", KEYWORD_IMPLEMENTS, {0, 0, 0, 1, 0}},
+ {"import", KEYWORD_IMPORT, {0, 0, 0, 1, 0}},
+ {"inline", KEYWORD_INLINE, {0, 1, 0, 0, 0}},
+ {"inout", KEYWORD_INOUT, {0, 0, 0, 0, 1}},
+ {"input", KEYWORD_INPUT, {0, 0, 0, 0, 1}},
+ {"int", KEYWORD_INT, {1, 1, 1, 1, 0}},
+ {"integer", KEYWORD_INTEGER, {0, 0, 0, 0, 1}},
+ {"interface", KEYWORD_INTERFACE, {0, 0, 1, 1, 1}},
+ {"internal", KEYWORD_INTERNAL, {0, 0, 1, 0, 0}},
+ {"local", KEYWORD_LOCAL, {0, 0, 0, 0, 1}},
+ {"long", KEYWORD_LONG, {1, 1, 1, 1, 0}},
+ {"m_bad_state", KEYWORD_M_BAD_STATE, {0, 0, 0, 0, 1}},
+ {"m_bad_trans", KEYWORD_M_BAD_TRANS, {0, 0, 0, 0, 1}},
+ {"m_state", KEYWORD_M_STATE, {0, 0, 0, 0, 1}},
+ {"m_trans", KEYWORD_M_TRANS, {0, 0, 0, 0, 1}},
+ {"mutable", KEYWORD_MUTABLE, {0, 1, 0, 0, 0}},
+ {"namespace", KEYWORD_NAMESPACE, {0, 1, 1, 0, 0}},
+ {"native", KEYWORD_NATIVE, {0, 0, 0, 1, 0}},
+ {"new", KEYWORD_NEW, {0, 1, 1, 1, 0}},
+ {"newcov", KEYWORD_NEWCOV, {0, 0, 0, 0, 1}},
+ {"operator", KEYWORD_OPERATOR, {0, 1, 1, 0, 0}},
+ {"output", KEYWORD_OUTPUT, {0, 0, 0, 0, 1}},
+ {"overload", KEYWORD_OVERLOAD, {0, 1, 0, 0, 0}},
+ {"override", KEYWORD_OVERRIDE, {0, 0, 1, 0, 0}},
+ {"package", KEYWORD_PACKAGE, {0, 0, 0, 1, 0}},
+ {"packed", KEYWORD_PACKED, {0, 0, 0, 0, 1}},
+ {"port", KEYWORD_PORT, {0, 0, 0, 0, 1}},
+ {"private", KEYWORD_PRIVATE, {0, 1, 1, 1, 0}},
+ {"program", KEYWORD_PROGRAM, {0, 0, 0, 0, 1}},
+ {"protected", KEYWORD_PROTECTED, {0, 1, 1, 1, 1}},
+ {"public", KEYWORD_PUBLIC, {0, 1, 1, 1, 1}},
+ {"register", KEYWORD_REGISTER, {1, 1, 0, 0, 0}},
+ {"return", KEYWORD_RETURN, {1, 1, 1, 1, 0}},
+ {"shadow", KEYWORD_SHADOW, {0, 0, 0, 0, 1}},
+ {"short", KEYWORD_SHORT, {1, 1, 1, 1, 0}},
+ {"signed", KEYWORD_SIGNED, {1, 1, 0, 0, 0}},
+ {"state", KEYWORD_STATE, {0, 0, 0, 0, 1}},
+ {"static", KEYWORD_STATIC, {1, 1, 1, 1, 1}},
+ {"string", KEYWORD_STRING, {0, 0, 1, 0, 1}},
+ {"struct", KEYWORD_STRUCT, {1, 1, 1, 0, 0}},
+ {"switch", KEYWORD_SWITCH, {1, 1, 1, 1, 0}},
+ {"synchronized", KEYWORD_SYNCHRONIZED, {0, 0, 0, 1, 0}},
+ {"task", KEYWORD_TASK, {0, 0, 0, 0, 1}},
+ {"template", KEYWORD_TEMPLATE, {0, 1, 0, 0, 0}},
+ {"this", KEYWORD_THIS, {0, 1, 1, 1, 0}},
+ {"throw", KEYWORD_THROW, {0, 1, 1, 1, 0}},
+ {"throws", KEYWORD_THROWS, {0, 0, 0, 1, 0}},
+ {"trans", KEYWORD_TRANS, {0, 0, 0, 0, 1}},
+ {"transition", KEYWORD_TRANSITION, {0, 0, 0, 0, 1}},
+ {"transient", KEYWORD_TRANSIENT, {0, 0, 0, 1, 0}},
+ {"try", KEYWORD_TRY, {0, 1, 1, 0, 0}},
+ {"typedef", KEYWORD_TYPEDEF, {1, 1, 1, 0, 1}},
+ {"typename", KEYWORD_TYPENAME, {0, 1, 0, 0, 0}},
+ {"uint", KEYWORD_UINT, {0, 0, 1, 0, 0}},
+ {"ulong", KEYWORD_ULONG, {0, 0, 1, 0, 0}},
+ {"union", KEYWORD_UNION, {1, 1, 0, 0, 0}},
+ {"unsigned", KEYWORD_UNSIGNED, {1, 1, 1, 0, 0}},
+ {"ushort", KEYWORD_USHORT, {0, 0, 1, 0, 0}},
+ {"using", KEYWORD_USING, {0, 1, 1, 0, 0}},
+ {"virtual", KEYWORD_VIRTUAL, {0, 1, 1, 0, 1}},
+ {"void", KEYWORD_VOID, {1, 1, 1, 1, 1}},
+ {"volatile", KEYWORD_VOLATILE, {1, 1, 1, 1, 0}},
+ {"wchar_t", KEYWORD_WCHAR_T, {1, 1, 1, 0, 0}},
+ {"while", KEYWORD_WHILE, {1, 1, 1, 1, 0}}};
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+static void createTags(const unsigned int nestLevel,
+ statementInfo *const parent);
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+extern boolean includingDefineTags(void) {
+ return CKinds[CK_DEFINE].enabled;
+}
+
+/*
+ * Token management
+ */
+
+static void initToken(tokenInfo *const token) {
+ token->type = TOKEN_NONE;
+ token->keyword = KEYWORD_NONE;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ vStringClear(token->name);
+}
+
+static void advanceToken(statementInfo *const st) {
+ if (st->tokenIndex >= (unsigned int)NumTokens - 1)
+ st->tokenIndex = 0;
+ else
+ ++st->tokenIndex;
+ initToken(st->token[st->tokenIndex]);
+}
+
+static tokenInfo *prevToken(const statementInfo *const st, unsigned int n) {
+ unsigned int tokenIndex;
+ unsigned int num = (unsigned int)NumTokens;
+ Assert(n < num);
+ tokenIndex = (st->tokenIndex + num - n) % num;
+ return st->token[tokenIndex];
+}
+
+static void setToken(statementInfo *const st, const tokenType type) {
+ tokenInfo *token;
+ token = activeToken(st);
+ initToken(token);
+ token->type = type;
+}
+
+static void retardToken(statementInfo *const st) {
+ if (st->tokenIndex == 0)
+ st->tokenIndex = (unsigned int)NumTokens - 1;
+ else
+ --st->tokenIndex;
+ setToken(st, TOKEN_NONE);
+}
+
+static tokenInfo *newToken(void) {
+ tokenInfo *const token = xMalloc(1, tokenInfo);
+ token->name = vStringNew();
+ initToken(token);
+ return token;
+}
+
+static void deleteToken(tokenInfo *const token) {
+ if (token != NULL) {
+ vStringDelete(token->name);
+ eFree(token);
+ }
+}
+
+static const char *accessString(const accessType access) {
+ static const char *const names[] = {"?", "local", "private",
+ "protected", "public", "default"};
+ Assert(sizeof(names) / sizeof(names[0]) == ACCESS_COUNT);
+ Assert((int)access < ACCESS_COUNT);
+ return names[(int)access];
+}
+
+static const char *implementationString(const impType imp) {
+ static const char *const names[] = {"?", "abstract", "virtual",
+ "pure virtual"};
+ Assert(sizeof(names) / sizeof(names[0]) == IMP_COUNT);
+ Assert((int)imp < IMP_COUNT);
+ return names[(int)imp];
+}
+
+/*
+ * Debugging functions
+ */
+
+#ifdef DEBUG
+
+#define boolString(c) ((c) ? "TRUE" : "FALSE")
+
+static const char *tokenString(const tokenType type) {
+ static const char *const names[] = {
+ "none", "args", "}", "{", "colon",
+ "comma", "double colon", "keyword", "name", "package",
+ "paren-name", "semicolon", "specifier"};
+ Assert(sizeof(names) / sizeof(names[0]) == TOKEN_COUNT);
+ Assert((int)type < TOKEN_COUNT);
+ return names[(int)type];
+}
+
+static const char *scopeString(const tagScope scope) {
+ static const char *const names[] = {"global", "static", "extern", "friend",
+ "typedef"};
+ Assert(sizeof(names) / sizeof(names[0]) == SCOPE_COUNT);
+ Assert((int)scope < SCOPE_COUNT);
+ return names[(int)scope];
+}
+
+static const char *declString(const declType declaration) {
+ static const char *const names[] = {
+ "?", "base", "class", "enum", "event",
+ "function", "ignore", "interface", "namespace", "no mangle",
+ "package", "program", "struct", "task", "union",
+ };
+ Assert(sizeof(names) / sizeof(names[0]) == DECL_COUNT);
+ Assert((int)declaration < DECL_COUNT);
+ return names[(int)declaration];
+}
+
+static const char *keywordString(const keywordId keyword) {
+ const size_t count = sizeof(KeywordTable) / sizeof(KeywordTable[0]);
+ const char *name = "none";
+ size_t i;
+ for (i = 0; i < count; ++i) {
+ const keywordDesc *p = &KeywordTable[i];
+ if (p->id == keyword) {
+ name = p->name;
+ break;
+ }
+ }
+ return name;
+}
+
+static void __unused__ pt(tokenInfo *const token) {
+ if (isType(token, TOKEN_NAME))
+ printf("type: %-12s: %-13s line: %lu\n", tokenString(token->type),
+ vStringValue(token->name), token->lineNumber);
+ else if (isType(token, TOKEN_KEYWORD))
+ printf("type: %-12s: %-13s line: %lu\n", tokenString(token->type),
+ keywordString(token->keyword), token->lineNumber);
+ else
+ printf("type: %-12s line: %lu\n", tokenString(token->type),
+ token->lineNumber);
+}
+
+static void __unused__ ps(statementInfo *const st) {
+ unsigned int i;
+ printf("scope: %s decl: %s gotName: %s gotParenName: %s\n",
+ scopeString(st->scope), declString(st->declaration),
+ boolString(st->gotName), boolString(st->gotParenName));
+ printf("haveQualifyingName: %s\n", boolString(st->haveQualifyingName));
+ printf("access: %s default: %s\n", accessString(st->member.access),
+ accessString(st->member.accessDefault));
+ printf("token : ");
+ pt(activeToken(st));
+ for (i = 1; i < (unsigned int)NumTokens; ++i) {
+ printf("prev %u : ", i);
+ pt(prevToken(st, i));
+ }
+ printf("context: ");
+ pt(st->context);
+}
+
+#endif
+
+/*
+ * Statement management
+ */
+
+static boolean isContextualKeyword(const tokenInfo *const token) {
+ boolean result;
+ switch (token->keyword) {
+ case KEYWORD_CLASS:
+ case KEYWORD_ENUM:
+ case KEYWORD_INTERFACE:
+ case KEYWORD_NAMESPACE:
+ case KEYWORD_STRUCT:
+ case KEYWORD_UNION:
+ result = TRUE;
+ break;
+
+ default:
+ result = FALSE;
+ break;
+ }
+ return result;
+}
+
+static boolean isContextualStatement(const statementInfo *const st) {
+ boolean result = FALSE;
+ if (st != NULL) switch (st->declaration) {
+ case DECL_CLASS:
+ case DECL_ENUM:
+ case DECL_INTERFACE:
+ case DECL_NAMESPACE:
+ case DECL_STRUCT:
+ case DECL_UNION:
+ result = TRUE;
+ break;
+
+ default:
+ result = FALSE;
+ break;
+ }
+ return result;
+}
+
+static boolean isMember(const statementInfo *const st) {
+ boolean result;
+ if (isType(st->context, TOKEN_NAME))
+ result = TRUE;
+ else
+ result = (boolean)(st->parent != NULL && isContextualStatement(st->parent));
+ return result;
+}
+
+static void initMemberInfo(statementInfo *const st) {
+ accessType accessDefault = ACCESS_UNDEFINED;
+
+ if (st->parent != NULL) switch (st->parent->declaration) {
+ case DECL_ENUM:
+ accessDefault =
+ (isLanguage(Lang_java) ? ACCESS_PUBLIC : ACCESS_UNDEFINED);
+ break;
+ case DECL_NAMESPACE:
+ accessDefault = ACCESS_UNDEFINED;
+ break;
+
+ case DECL_CLASS:
+ if (isLanguage(Lang_java))
+ accessDefault = ACCESS_DEFAULT;
+ else
+ accessDefault = ACCESS_PRIVATE;
+ break;
+
+ case DECL_INTERFACE:
+ case DECL_STRUCT:
+ case DECL_UNION:
+ accessDefault = ACCESS_PUBLIC;
+ break;
+
+ default:
+ break;
+ }
+ st->member.accessDefault = accessDefault;
+ st->member.access = accessDefault;
+}
+
+static void reinitStatement(statementInfo *const st, const boolean partial) {
+ unsigned int i;
+
+ if (!partial) {
+ st->scope = SCOPE_GLOBAL;
+ if (isContextualStatement(st->parent))
+ st->declaration = DECL_BASE;
+ else
+ st->declaration = DECL_NONE;
+ }
+ st->gotParenName = FALSE;
+ st->isPointer = FALSE;
+ st->inFunction = FALSE;
+ st->assignment = FALSE;
+ st->notVariable = FALSE;
+ st->implementation = IMP_DEFAULT;
+ st->gotArgs = FALSE;
+ st->gotName = FALSE;
+ st->haveQualifyingName = FALSE;
+ st->tokenIndex = 0;
+
+ if (st->parent != NULL) st->inFunction = st->parent->inFunction;
+
+ for (i = 0; i < (unsigned int)NumTokens; ++i) initToken(st->token[i]);
+
+ initToken(st->context);
+
+ /* Keep the block name, so that a variable following after a comma will
+ * still have the structure name.
+ */
+ if (!partial) initToken(st->blockName);
+
+ vStringClear(st->parentClasses);
+
+ /* Init member info.
+ */
+ if (!partial) st->member.access = st->member.accessDefault;
+}
+
+static void initStatement(statementInfo *const st,
+ statementInfo *const parent) {
+ st->parent = parent;
+ initMemberInfo(st);
+ reinitStatement(st, FALSE);
+}
+
+/*
+ * Tag generation functions
+ */
+static cKind cTagKind(const tagType type) {
+ cKind result = CK_UNDEFINED;
+ switch (type) {
+ case TAG_CLASS:
+ result = CK_CLASS;
+ break;
+ case TAG_ENUM:
+ result = CK_ENUMERATION;
+ break;
+ case TAG_ENUMERATOR:
+ result = CK_ENUMERATOR;
+ break;
+ case TAG_FUNCTION:
+ result = CK_FUNCTION;
+ break;
+ case TAG_LOCAL:
+ result = CK_LOCAL;
+ break;
+ case TAG_MEMBER:
+ result = CK_MEMBER;
+ break;
+ case TAG_NAMESPACE:
+ result = CK_NAMESPACE;
+ break;
+ case TAG_PROTOTYPE:
+ result = CK_PROTOTYPE;
+ break;
+ case TAG_STRUCT:
+ result = CK_STRUCT;
+ break;
+ case TAG_TYPEDEF:
+ result = CK_TYPEDEF;
+ break;
+ case TAG_UNION:
+ result = CK_UNION;
+ break;
+ case TAG_VARIABLE:
+ result = CK_VARIABLE;
+ break;
+ case TAG_EXTERN_VAR:
+ result = CK_EXTERN_VARIABLE;
+ break;
+
+ default:
+ Assert("Bad C tag type" == NULL);
+ break;
+ }
+ return result;
+}
+
+static csharpKind csharpTagKind(const tagType type) {
+ csharpKind result = CSK_UNDEFINED;
+ switch (type) {
+ case TAG_CLASS:
+ result = CSK_CLASS;
+ break;
+ case TAG_ENUM:
+ result = CSK_ENUMERATION;
+ break;
+ case TAG_ENUMERATOR:
+ result = CSK_ENUMERATOR;
+ break;
+ case TAG_EVENT:
+ result = CSK_EVENT;
+ break;
+ case TAG_FIELD:
+ result = CSK_FIELD;
+ break;
+ case TAG_INTERFACE:
+ result = CSK_INTERFACE;
+ break;
+ case TAG_LOCAL:
+ result = CSK_LOCAL;
+ break;
+ case TAG_METHOD:
+ result = CSK_METHOD;
+ break;
+ case TAG_NAMESPACE:
+ result = CSK_NAMESPACE;
+ break;
+ case TAG_PROPERTY:
+ result = CSK_PROPERTY;
+ break;
+ case TAG_STRUCT:
+ result = CSK_STRUCT;
+ break;
+ case TAG_TYPEDEF:
+ result = CSK_TYPEDEF;
+ break;
+
+ default:
+ Assert("Bad C# tag type" == NULL);
+ break;
+ }
+ return result;
+}
+
+static javaKind javaTagKind(const tagType type) {
+ javaKind result = JK_UNDEFINED;
+ switch (type) {
+ case TAG_CLASS:
+ result = JK_CLASS;
+ break;
+ case TAG_ENUM:
+ result = JK_ENUM;
+ break;
+ case TAG_ENUMERATOR:
+ result = JK_ENUM_CONSTANT;
+ break;
+ case TAG_FIELD:
+ result = JK_FIELD;
+ break;
+ case TAG_INTERFACE:
+ result = JK_INTERFACE;
+ break;
+ case TAG_LOCAL:
+ result = JK_LOCAL;
+ break;
+ case TAG_METHOD:
+ result = JK_METHOD;
+ break;
+ case TAG_PACKAGE:
+ result = JK_PACKAGE;
+ break;
+
+ default:
+ Assert("Bad Java tag type" == NULL);
+ break;
+ }
+ return result;
+}
+
+static veraKind veraTagKind(const tagType type) {
+ veraKind result = VK_UNDEFINED;
+ switch (type) {
+ case TAG_CLASS:
+ result = VK_CLASS;
+ break;
+ case TAG_ENUM:
+ result = VK_ENUMERATION;
+ break;
+ case TAG_ENUMERATOR:
+ result = VK_ENUMERATOR;
+ break;
+ case TAG_FUNCTION:
+ result = VK_FUNCTION;
+ break;
+ case TAG_LOCAL:
+ result = VK_LOCAL;
+ break;
+ case TAG_MEMBER:
+ result = VK_MEMBER;
+ break;
+ case TAG_PROGRAM:
+ result = VK_PROGRAM;
+ break;
+ case TAG_PROTOTYPE:
+ result = VK_PROTOTYPE;
+ break;
+ case TAG_TASK:
+ result = VK_TASK;
+ break;
+ case TAG_TYPEDEF:
+ result = VK_TYPEDEF;
+ break;
+ case TAG_VARIABLE:
+ result = VK_VARIABLE;
+ break;
+ case TAG_EXTERN_VAR:
+ result = VK_EXTERN_VARIABLE;
+ break;
+
+ default:
+ Assert("Bad Vera tag type" == NULL);
+ break;
+ }
+ return result;
+}
+
+static const char *tagName(const tagType type) {
+ const char *result;
+ if (isLanguage(Lang_csharp))
+ result = CsharpKinds[csharpTagKind(type)].name;
+ else if (isLanguage(Lang_java))
+ result = JavaKinds[javaTagKind(type)].name;
+ else if (isLanguage(Lang_vera))
+ result = VeraKinds[veraTagKind(type)].name;
+ else
+ result = CKinds[cTagKind(type)].name;
+ return result;
+}
+
+static int tagLetter(const tagType type) {
+ int result;
+ if (isLanguage(Lang_csharp))
+ result = CsharpKinds[csharpTagKind(type)].letter;
+ else if (isLanguage(Lang_java))
+ result = JavaKinds[javaTagKind(type)].letter;
+ else if (isLanguage(Lang_vera))
+ result = VeraKinds[veraTagKind(type)].letter;
+ else
+ result = CKinds[cTagKind(type)].letter;
+ return result;
+}
+
+static boolean includeTag(const tagType type, const boolean isFileScope) {
+ boolean result;
+ if (isFileScope && !Option.include.fileScope)
+ result = FALSE;
+ else if (isLanguage(Lang_csharp))
+ result = CsharpKinds[csharpTagKind(type)].enabled;
+ else if (isLanguage(Lang_java))
+ result = JavaKinds[javaTagKind(type)].enabled;
+ else if (isLanguage(Lang_vera))
+ result = VeraKinds[veraTagKind(type)].enabled;
+ else
+ result = CKinds[cTagKind(type)].enabled;
+ return result;
+}
+
+static tagType declToTagType(const declType declaration) {
+ tagType type = TAG_UNDEFINED;
+
+ switch (declaration) {
+ case DECL_CLASS:
+ type = TAG_CLASS;
+ break;
+ case DECL_ENUM:
+ type = TAG_ENUM;
+ break;
+ case DECL_EVENT:
+ type = TAG_EVENT;
+ break;
+ case DECL_FUNCTION:
+ type = TAG_FUNCTION;
+ break;
+ case DECL_INTERFACE:
+ type = TAG_INTERFACE;
+ break;
+ case DECL_NAMESPACE:
+ type = TAG_NAMESPACE;
+ break;
+ case DECL_PROGRAM:
+ type = TAG_PROGRAM;
+ break;
+ case DECL_TASK:
+ type = TAG_TASK;
+ break;
+ case DECL_STRUCT:
+ type = TAG_STRUCT;
+ break;
+ case DECL_UNION:
+ type = TAG_UNION;
+ break;
+
+ default:
+ Assert("Unexpected declaration" == NULL);
+ break;
+ }
+ return type;
+}
+
+static const char *accessField(const statementInfo *const st) {
+ const char *result = NULL;
+ if (isLanguage(Lang_cpp) && st->scope == SCOPE_FRIEND)
+ result = "friend";
+ else if (st->member.access != ACCESS_UNDEFINED)
+ result = accessString(st->member.access);
+ return result;
+}
+
+static void addContextSeparator(vString *const scope) {
+ if (isLanguage(Lang_c) || isLanguage(Lang_cpp))
+ vStringCatS(scope, "::");
+ else if (isLanguage(Lang_java) || isLanguage(Lang_csharp))
+ vStringCatS(scope, ".");
+}
+
+static void addOtherFields(tagEntryInfo *const tag, const tagType type,
+ const statementInfo *const st, vString *const scope,
+ vString *const typeRef) {
+ /* For selected tag types, append an extension flag designating the
+ * parent object in which the tag is defined.
+ */
+ switch (type) {
+ default:
+ break;
+
+ case TAG_FUNCTION:
+ case TAG_METHOD:
+ case TAG_PROTOTYPE:
+ if (vStringLength(Signature) > 0)
+ tag->extensionFields.signature = vStringValue(Signature);
+ case TAG_CLASS:
+ case TAG_ENUM:
+ case TAG_ENUMERATOR:
+ case TAG_EVENT:
+ case TAG_FIELD:
+ case TAG_INTERFACE:
+ case TAG_MEMBER:
+ case TAG_NAMESPACE:
+ case TAG_PROPERTY:
+ case TAG_STRUCT:
+ case TAG_TASK:
+ case TAG_TYPEDEF:
+ case TAG_UNION:
+ if (vStringLength(scope) > 0 &&
+ (isMember(st) || st->parent->declaration == DECL_NAMESPACE)) {
+ if (isType(st->context, TOKEN_NAME))
+ tag->extensionFields.scope[0] = tagName(TAG_CLASS);
+ else
+ tag->extensionFields.scope[0] =
+ tagName(declToTagType(parentDecl(st)));
+ tag->extensionFields.scope[1] = vStringValue(scope);
+ }
+ if ((type == TAG_CLASS || type == TAG_INTERFACE || type == TAG_STRUCT) &&
+ vStringLength(st->parentClasses) > 0) {
+
+ tag->extensionFields.inheritance = vStringValue(st->parentClasses);
+ }
+ if (st->implementation != IMP_DEFAULT &&
+ (isLanguage(Lang_cpp) || isLanguage(Lang_csharp) ||
+ isLanguage(Lang_java))) {
+ tag->extensionFields.implementation =
+ implementationString(st->implementation);
+ }
+ if (isMember(st)) {
+ tag->extensionFields.access = accessField(st);
+ }
+ break;
+ }
+
+ /* Add typename info, type of the tag and name of struct/union/etc. */
+ if ((type == TAG_TYPEDEF || type == TAG_VARIABLE || type == TAG_MEMBER) &&
+ isContextualStatement(st)) {
+ char *p;
+
+ tag->extensionFields.typeRef[0] = tagName(declToTagType(st->declaration));
+ p = vStringValue(st->blockName->name);
+
+ /* If there was no {} block get the name from the token before the
+ * name (current token is ';' or ',', previous token is the name).
+ */
+ if (p == NULL || *p == '\0') {
+ tokenInfo *const prev2 = prevToken(st, 2);
+ if (isType(prev2, TOKEN_NAME)) p = vStringValue(prev2->name);
+ }
+
+ /* Prepend the scope name if there is one. */
+ if (vStringLength(scope) > 0) {
+ vStringCopy(typeRef, scope);
+ addContextSeparator(typeRef);
+ vStringCatS(typeRef, p);
+ p = vStringValue(typeRef);
+ }
+ tag->extensionFields.typeRef[1] = p;
+ }
+}
+
+static void findScopeHierarchy(vString *const string,
+ const statementInfo *const st) {
+ vStringClear(string);
+ if (isType(st->context, TOKEN_NAME)) vStringCopy(string, st->context->name);
+ if (st->parent != NULL) {
+ vString *temp = vStringNew();
+ const statementInfo *s;
+ for (s = st->parent; s != NULL; s = s->parent) {
+ if (isContextualStatement(s) || s->declaration == DECL_NAMESPACE ||
+ s->declaration == DECL_PROGRAM) {
+ vStringCopy(temp, string);
+ vStringClear(string);
+ Assert(isType(s->blockName, TOKEN_NAME));
+ if (isType(s->context, TOKEN_NAME) &&
+ vStringLength(s->context->name) > 0) {
+ vStringCat(string, s->context->name);
+ addContextSeparator(string);
+ }
+ vStringCat(string, s->blockName->name);
+ if (vStringLength(temp) > 0) addContextSeparator(string);
+ vStringCat(string, temp);
+ }
+ }
+ vStringDelete(temp);
+ }
+}
+
+static void makeExtraTagEntry(const tagType type, tagEntryInfo *const e,
+ vString *const scope) {
+ if (Option.include.qualifiedTags && scope != NULL &&
+ vStringLength(scope) > 0) {
+ vString *const scopedName = vStringNew();
+
+ if (type != TAG_ENUMERATOR)
+ vStringCopy(scopedName, scope);
+ else {
+ /* remove last component (i.e. enumeration name) from scope */
+ const char *const sc = vStringValue(scope);
+ const char *colon = strrchr(sc, ':');
+ if (colon != NULL) {
+ while (*colon == ':' && colon > sc) --colon;
+ vStringNCopy(scopedName, scope, colon + 1 - sc);
+ }
+ }
+ if (vStringLength(scopedName) > 0) {
+ addContextSeparator(scopedName);
+ vStringCatS(scopedName, e->name);
+ e->name = vStringValue(scopedName);
+ makeTagEntry(e);
+ }
+ vStringDelete(scopedName);
+ }
+}
+
+static void makeTag(const tokenInfo *const token, const statementInfo *const st,
+ boolean isFileScope, const tagType type) {
+ /* Nothing is really of file scope when it appears in a header file.
+ */
+ isFileScope = (boolean)(isFileScope && !isHeaderFile());
+
+ if (isType(token, TOKEN_NAME) && vStringLength(token->name) > 0 &&
+ includeTag(type, isFileScope)) {
+ vString *scope = vStringNew();
+ /* Use "typeRef" to store the typename from addOtherFields() until
+ * it's used in makeTagEntry().
+ */
+ vString *typeRef = vStringNew();
+ tagEntryInfo e;
+
+ initTagEntry(&e, vStringValue(token->name));
+
+ e.lineNumber = token->lineNumber;
+ e.filePosition = token->filePosition;
+ e.isFileScope = isFileScope;
+ e.kindName = tagName(type);
+ e.kind = tagLetter(type);
+
+ findScopeHierarchy(scope, st);
+ addOtherFields(&e, type, st, scope, typeRef);
+
+ makeTagEntry(&e);
+ makeExtraTagEntry(type, &e, scope);
+ vStringDelete(scope);
+ vStringDelete(typeRef);
+ }
+}
+
+static boolean isValidTypeSpecifier(const declType declaration) {
+ boolean result;
+ switch (declaration) {
+ case DECL_BASE:
+ case DECL_CLASS:
+ case DECL_ENUM:
+ case DECL_EVENT:
+ case DECL_STRUCT:
+ case DECL_UNION:
+ result = TRUE;
+ break;
+
+ default:
+ result = FALSE;
+ break;
+ }
+ return result;
+}
+
+static void qualifyEnumeratorTag(const statementInfo *const st,
+ const tokenInfo *const nameToken) {
+ if (isType(nameToken, TOKEN_NAME))
+ makeTag(nameToken, st, TRUE, TAG_ENUMERATOR);
+}
+
+static void qualifyFunctionTag(const statementInfo *const st,
+ const tokenInfo *const nameToken) {
+ if (isType(nameToken, TOKEN_NAME)) {
+ tagType type;
+ const boolean isFileScope =
+ (boolean)(st->member.access == ACCESS_PRIVATE ||
+ (!isMember(st) && st->scope == SCOPE_STATIC));
+ if (isLanguage(Lang_java) || isLanguage(Lang_csharp))
+ type = TAG_METHOD;
+ else if (isLanguage(Lang_vera) && st->declaration == DECL_TASK)
+ type = TAG_TASK;
+ else
+ type = TAG_FUNCTION;
+ makeTag(nameToken, st, isFileScope, type);
+ }
+}
+
+static void qualifyFunctionDeclTag(const statementInfo *const st,
+ const tokenInfo *const nameToken) {
+ if (!isType(nameToken, TOKEN_NAME))
+ ;
+ else if (isLanguage(Lang_java) || isLanguage(Lang_csharp))
+ qualifyFunctionTag(st, nameToken);
+ else if (st->scope == SCOPE_TYPEDEF)
+ makeTag(nameToken, st, TRUE, TAG_TYPEDEF);
+ else if (isValidTypeSpecifier(st->declaration) && !isLanguage(Lang_csharp))
+ makeTag(nameToken, st, TRUE, TAG_PROTOTYPE);
+}
+
+static void qualifyCompoundTag(const statementInfo *const st,
+ const tokenInfo *const nameToken) {
+ if (isType(nameToken, TOKEN_NAME)) {
+ const tagType type = declToTagType(st->declaration);
+ const boolean fileScoped =
+ (boolean)(!(isLanguage(Lang_java) || isLanguage(Lang_csharp) ||
+ isLanguage(Lang_vera)));
+
+ if (type != TAG_UNDEFINED) makeTag(nameToken, st, fileScoped, type);
+ }
+}
+
+static void qualifyBlockTag(statementInfo *const st,
+ const tokenInfo *const nameToken) {
+ switch (st->declaration) {
+ case DECL_CLASS:
+ case DECL_ENUM:
+ case DECL_INTERFACE:
+ case DECL_NAMESPACE:
+ case DECL_PROGRAM:
+ case DECL_STRUCT:
+ case DECL_UNION:
+ qualifyCompoundTag(st, nameToken);
+ break;
+ default:
+ break;
+ }
+}
+
+static void qualifyVariableTag(const statementInfo *const st,
+ const tokenInfo *const nameToken) {
+ /* We have to watch that we do not interpret a declaration of the
+ * form "struct tag;" as a variable definition. In such a case, the
+ * token preceding the name will be a keyword.
+ */
+ if (!isType(nameToken, TOKEN_NAME))
+ ;
+ else if (st->scope == SCOPE_TYPEDEF)
+ makeTag(nameToken, st, TRUE, TAG_TYPEDEF);
+ else if (st->declaration == DECL_EVENT)
+ makeTag(nameToken, st, (boolean)(st->member.access == ACCESS_PRIVATE),
+ TAG_EVENT);
+ else if (st->declaration == DECL_PACKAGE)
+ makeTag(nameToken, st, FALSE, TAG_PACKAGE);
+ else if (isValidTypeSpecifier(st->declaration)) {
+ if (st->notVariable)
+ ;
+ else if (isMember(st)) {
+ if (isLanguage(Lang_java) || isLanguage(Lang_csharp))
+ makeTag(nameToken, st, (boolean)(st->member.access == ACCESS_PRIVATE),
+ TAG_FIELD);
+ else if (st->scope == SCOPE_GLOBAL || st->scope == SCOPE_STATIC)
+ makeTag(nameToken, st, TRUE, TAG_MEMBER);
+ } else {
+ if (st->scope == SCOPE_EXTERN || !st->haveQualifyingName)
+ makeTag(nameToken, st, FALSE, TAG_EXTERN_VAR);
+ else if (st->inFunction)
+ makeTag(nameToken, st, (boolean)(st->scope == SCOPE_STATIC), TAG_LOCAL);
+ else
+ makeTag(nameToken, st, (boolean)(st->scope == SCOPE_STATIC),
+ TAG_VARIABLE);
+ }
+ }
+}
+
+/*
+ * Parsing functions
+ */
+
+static int skipToOneOf(const char *const chars) {
+ int c;
+ do
+ c = cppGetc();
+ while (c != EOF && c != '\0' && strchr(chars, c) == NULL);
+ return c;
+}
+
+/* Skip to the next non-white character.
+ */
+static int skipToNonWhite(void) {
+ boolean found = FALSE;
+ int c;
+
+#if 0
+ do
+ c = cppGetc ();
+ while (isspace (c));
+#else
+ while (1) {
+ c = cppGetc();
+ if (isspace(c))
+ found = TRUE;
+ else
+ break;
+ }
+ if (CollectingSignature && found) vStringPut(Signature, ' ');
+#endif
+
+ return c;
+}
+
+/* Skips to the next brace in column 1. This is intended for cases where
+ * preprocessor constructs result in unbalanced braces.
+ */
+static void skipToFormattedBraceMatch(void) {
+ int c, next;
+
+ c = cppGetc();
+ next = cppGetc();
+ while (c != EOF && (c != '\n' || next != '}')) {
+ c = next;
+ next = cppGetc();
+ }
+}
+
+/* Skip to the matching character indicated by the pair string. If skipping
+ * to a matching brace and any brace is found within a different level of a
+ * #if conditional statement while brace formatting is in effect, we skip to
+ * the brace matched by its formatting. It is assumed that we have already
+ * read the character which starts the group (i.e. the first character of
+ * "pair").
+ */
+static void skipToMatch(const char *const pair) {
+ const boolean braceMatching = (boolean)(strcmp("{}", pair) == 0);
+ const boolean braceFormatting = (boolean)(isBraceFormat() && braceMatching);
+ const unsigned int initialLevel = getDirectiveNestLevel();
+ const int begin = pair[0], end = pair[1];
+ const unsigned long inputLineNumber = getInputLineNumber();
+ int matchLevel = 1;
+ int c = '\0';
+
+ while (matchLevel > 0 && (c = skipToNonWhite()) != EOF) {
+ if (CollectingSignature) vStringPut(Signature, c);
+ if (c == begin) {
+ ++matchLevel;
+ if (braceFormatting && getDirectiveNestLevel() != initialLevel) {
+ skipToFormattedBraceMatch();
+ break;
+ }
+ } else if (c == end) {
+ --matchLevel;
+ if (braceFormatting && getDirectiveNestLevel() != initialLevel) {
+ skipToFormattedBraceMatch();
+ break;
+ }
+ }
+ }
+ if (c == EOF) {
+ verbose("%s: failed to find match for '%c' at line %lu\n",
+ getInputFileName(), begin, inputLineNumber);
+ if (braceMatching)
+ longjmp(Exception, (int)ExceptionBraceFormattingError);
+ else
+ longjmp(Exception, (int)ExceptionFormattingError);
+ }
+}
+
+static void skipParens(void) {
+ const int c = skipToNonWhite();
+
+ if (c == '(')
+ skipToMatch("()");
+ else
+ cppUngetc(c);
+}
+
+static void skipBraces(void) {
+ const int c = skipToNonWhite();
+
+ if (c == '{')
+ skipToMatch("{}");
+ else
+ cppUngetc(c);
+}
+
+static keywordId analyzeKeyword(const char *const name) {
+ const keywordId id = (keywordId)lookupKeyword(name, getSourceLanguage());
+ return id;
+}
+
+static void analyzeIdentifier(tokenInfo *const token) {
+ char *const name = vStringValue(token->name);
+ const char *replacement = NULL;
+ boolean parensToo = FALSE;
+
+ if (isLanguage(Lang_java) || !isIgnoreToken(name, &parensToo, &replacement)) {
+ if (replacement != NULL)
+ token->keyword = analyzeKeyword(replacement);
+ else
+ token->keyword = analyzeKeyword(vStringValue(token->name));
+
+ if (token->keyword == KEYWORD_NONE)
+ token->type = TOKEN_NAME;
+ else
+ token->type = TOKEN_KEYWORD;
+ } else {
+ initToken(token);
+ if (parensToo) {
+ int c = skipToNonWhite();
+
+ if (c == '(') skipToMatch("()");
+ }
+ }
+}
+
+static void readIdentifier(tokenInfo *const token, const int firstChar) {
+ vString *const name = token->name;
+ int c = firstChar;
+ boolean first = TRUE;
+
+ initToken(token);
+
+ /* Bug #1585745: strangely, C++ destructors allow whitespace between
+ * the ~ and the class name. */
+ if (isLanguage(Lang_cpp) && firstChar == '~') {
+ vStringPut(name, c);
+ c = skipToNonWhite();
+ }
+
+ do {
+ vStringPut(name, c);
+ if (CollectingSignature) {
+ if (!first) vStringPut(Signature, c);
+ first = FALSE;
+ }
+ c = cppGetc();
+ } while (isident(c) || ((isLanguage(Lang_java) || isLanguage(Lang_csharp)) &&
+ (isHighChar(c) || c == '.')));
+ vStringTerminate(name);
+ cppUngetc(c); /* unget non-identifier character */
+
+ analyzeIdentifier(token);
+}
+
+static void readPackageName(tokenInfo *const token, const int firstChar) {
+ vString *const name = token->name;
+ int c = firstChar;
+
+ initToken(token);
+
+ while (isident(c) || c == '.') {
+ vStringPut(name, c);
+ c = cppGetc();
+ }
+ vStringTerminate(name);
+ cppUngetc(c); /* unget non-package character */
+}
+
+static void readPackageOrNamespace(statementInfo *const st,
+ const declType declaration) {
+ st->declaration = declaration;
+
+ if (declaration == DECL_NAMESPACE && !isLanguage(Lang_csharp)) {
+ /* In C++ a namespace is specified one level at a time. */
+ return;
+ } else {
+ /* In C#, a namespace can also be specified like a Java package name. */
+ tokenInfo *const token = activeToken(st);
+ Assert(isType(token, TOKEN_KEYWORD));
+ readPackageName(token, skipToNonWhite());
+ token->type = TOKEN_NAME;
+ st->gotName = TRUE;
+ st->haveQualifyingName = TRUE;
+ }
+}
+
+static void processName(statementInfo *const st) {
+ Assert(isType(activeToken(st), TOKEN_NAME));
+ if (st->gotName && st->declaration == DECL_NONE) st->declaration = DECL_BASE;
+ st->gotName = TRUE;
+ st->haveQualifyingName = TRUE;
+}
+
+static void readOperator(statementInfo *const st) {
+ const char *const acceptable = "+-*/%^&|~!=<>,[]";
+ const tokenInfo *const prev = prevToken(st, 1);
+ tokenInfo *const token = activeToken(st);
+ vString *const name = token->name;
+ int c = skipToNonWhite();
+
+ /* When we arrive here, we have the keyword "operator" in 'name'.
+ */
+ if (isType(prev, TOKEN_KEYWORD) &&
+ (prev->keyword == KEYWORD_ENUM || prev->keyword == KEYWORD_STRUCT ||
+ prev->keyword == KEYWORD_UNION))
+ ; /* ignore "operator" keyword if preceded by these keywords */
+ else if (c == '(') {
+ /* Verify whether this is a valid function call (i.e. "()") operator.
+ */
+ if (cppGetc() == ')') {
+ vStringPut(name, ' '); /* always separate operator from keyword */
+ c = skipToNonWhite();
+ if (c == '(') vStringCatS(name, "()");
+ } else {
+ skipToMatch("()");
+ c = cppGetc();
+ }
+ } else if (isident1(c)) {
+ /* Handle "new" and "delete" operators, and conversion functions
+ * (per 13.3.1.1.2 [2] of the C++ spec).
+ */
+ boolean whiteSpace = TRUE; /* default causes insertion of space */
+ do {
+ if (isspace(c))
+ whiteSpace = TRUE;
+ else {
+ if (whiteSpace) {
+ vStringPut(name, ' ');
+ whiteSpace = FALSE;
+ }
+ vStringPut(name, c);
+ }
+ c = cppGetc();
+ } while (!isOneOf(c, "(;") && c != EOF);
+ vStringTerminate(name);
+ } else if (isOneOf(c, acceptable)) {
+ vStringPut(name, ' '); /* always separate operator from keyword */
+ do {
+ vStringPut(name, c);
+ c = cppGetc();
+ } while (isOneOf(c, acceptable));
+ vStringTerminate(name);
+ }
+
+ cppUngetc(c);
+
+ token->type = TOKEN_NAME;
+ token->keyword = KEYWORD_NONE;
+ processName(st);
+}
+
+static void copyToken(tokenInfo *const dest, const tokenInfo *const src) {
+ dest->type = src->type;
+ dest->keyword = src->keyword;
+ dest->filePosition = src->filePosition;
+ dest->lineNumber = src->lineNumber;
+ vStringCopy(dest->name, src->name);
+}
+
+static void setAccess(statementInfo *const st, const accessType access) {
+ if (isMember(st)) {
+ if (isLanguage(Lang_cpp)) {
+ int c = skipToNonWhite();
+
+ if (c == ':')
+ reinitStatement(st, FALSE);
+ else
+ cppUngetc(c);
+
+ st->member.accessDefault = access;
+ }
+ st->member.access = access;
+ }
+}
+
+static void discardTypeList(tokenInfo *const token) {
+ int c = skipToNonWhite();
+ while (isident1(c)) {
+ readIdentifier(token, c);
+ c = skipToNonWhite();
+ if (c == '.' || c == ',') c = skipToNonWhite();
+ }
+ cppUngetc(c);
+}
+
+static void addParentClass(statementInfo *const st, tokenInfo *const token) {
+ if (vStringLength(token->name) > 0 && vStringLength(st->parentClasses) > 0) {
+ vStringPut(st->parentClasses, ',');
+ }
+ vStringCat(st->parentClasses, token->name);
+}
+
+static void readParents(statementInfo *const st, const int qualifier) {
+ tokenInfo *const token = newToken();
+ tokenInfo *const parent = newToken();
+ int c;
+
+ do {
+ c = skipToNonWhite();
+ if (isident1(c)) {
+ readIdentifier(token, c);
+ if (isType(token, TOKEN_NAME))
+ vStringCat(parent->name, token->name);
+ else {
+ addParentClass(st, parent);
+ initToken(parent);
+ }
+ } else if (c == qualifier)
+ vStringPut(parent->name, c);
+ else if (c == '<')
+ skipToMatch("<>");
+ else if (isType(token, TOKEN_NAME)) {
+ addParentClass(st, parent);
+ initToken(parent);
+ }
+ } while (c != '{' && c != EOF);
+ cppUngetc(c);
+ deleteToken(parent);
+ deleteToken(token);
+}
+
+static void skipStatement(statementInfo *const st) {
+ st->declaration = DECL_IGNORE;
+ skipToOneOf(";");
+}
+
+static void processInterface(statementInfo *const st) {
+ st->declaration = DECL_INTERFACE;
+}
+
+static void processToken(tokenInfo *const token, statementInfo *const st) {
+ switch (token->keyword) /* is it a reserved word? */
+ {
+ default:
+ break;
+
+ case KEYWORD_NONE:
+ processName(st);
+ break;
+ case KEYWORD_ABSTRACT:
+ st->implementation = IMP_ABSTRACT;
+ break;
+ case KEYWORD_ATTRIBUTE:
+ skipParens();
+ initToken(token);
+ break;
+ case KEYWORD_BIND:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_BIT:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_CATCH:
+ skipParens();
+ skipBraces();
+ break;
+ case KEYWORD_CHAR:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_CLASS:
+ st->declaration = DECL_CLASS;
+ break;
+ case KEYWORD_CONST:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_DOUBLE:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_ENUM:
+ st->declaration = DECL_ENUM;
+ break;
+ case KEYWORD_EXTENDS:
+ readParents(st, '.');
+ setToken(st, TOKEN_NONE);
+ break;
+ case KEYWORD_FLOAT:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_FUNCTION:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_FRIEND:
+ st->scope = SCOPE_FRIEND;
+ break;
+ case KEYWORD_GOTO:
+ skipStatement(st);
+ break;
+ case KEYWORD_IMPLEMENTS:
+ readParents(st, '.');
+ setToken(st, TOKEN_NONE);
+ break;
+ case KEYWORD_IMPORT:
+ skipStatement(st);
+ break;
+ case KEYWORD_INT:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_INTEGER:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_INTERFACE:
+ processInterface(st);
+ break;
+ case KEYWORD_LOCAL:
+ setAccess(st, ACCESS_LOCAL);
+ break;
+ case KEYWORD_LONG:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_OPERATOR:
+ readOperator(st);
+ break;
+ case KEYWORD_PRIVATE:
+ setAccess(st, ACCESS_PRIVATE);
+ break;
+ case KEYWORD_PROGRAM:
+ st->declaration = DECL_PROGRAM;
+ break;
+ case KEYWORD_PROTECTED:
+ setAccess(st, ACCESS_PROTECTED);
+ break;
+ case KEYWORD_PUBLIC:
+ setAccess(st, ACCESS_PUBLIC);
+ break;
+ case KEYWORD_RETURN:
+ skipStatement(st);
+ break;
+ case KEYWORD_SHORT:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_SIGNED:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_STRING:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_STRUCT:
+ st->declaration = DECL_STRUCT;
+ break;
+ case KEYWORD_TASK:
+ st->declaration = DECL_TASK;
+ break;
+ case KEYWORD_THROWS:
+ discardTypeList(token);
+ break;
+ case KEYWORD_UNION:
+ st->declaration = DECL_UNION;
+ break;
+ case KEYWORD_UNSIGNED:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_USING:
+ skipStatement(st);
+ break;
+ case KEYWORD_VOID:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_VOLATILE:
+ st->declaration = DECL_BASE;
+ break;
+ case KEYWORD_VIRTUAL:
+ st->implementation = IMP_VIRTUAL;
+ break;
+ case KEYWORD_WCHAR_T:
+ st->declaration = DECL_BASE;
+ break;
+
+ case KEYWORD_NAMESPACE:
+ readPackageOrNamespace(st, DECL_NAMESPACE);
+ break;
+ case KEYWORD_PACKAGE:
+ readPackageOrNamespace(st, DECL_PACKAGE);
+ break;
+
+ case KEYWORD_EVENT:
+ if (isLanguage(Lang_csharp)) st->declaration = DECL_EVENT;
+ break;
+
+ case KEYWORD_TYPEDEF:
+ reinitStatement(st, FALSE);
+ st->scope = SCOPE_TYPEDEF;
+ break;
+
+ case KEYWORD_EXTERN:
+ if (!isLanguage(Lang_csharp) || !st->gotName) {
+ reinitStatement(st, FALSE);
+ st->scope = SCOPE_EXTERN;
+ st->declaration = DECL_BASE;
+ }
+ break;
+
+ case KEYWORD_STATIC:
+ if (!(isLanguage(Lang_java) || isLanguage(Lang_csharp))) {
+ reinitStatement(st, FALSE);
+ st->scope = SCOPE_STATIC;
+ st->declaration = DECL_BASE;
+ }
+ break;
+
+ case KEYWORD_FOR:
+ case KEYWORD_FOREACH:
+ case KEYWORD_IF:
+ case KEYWORD_SWITCH:
+ case KEYWORD_WHILE: {
+ int c = skipToNonWhite();
+ if (c == '(') skipToMatch("()");
+ break;
+ }
+ }
+}
+
+/*
+ * Parenthesis handling functions
+ */
+
+static void restartStatement(statementInfo *const st) {
+ tokenInfo *const save = newToken();
+ tokenInfo *token = activeToken(st);
+
+ copyToken(save, token);
+ DebugStatement(if (debug(DEBUG_PARSE)) printf("");)
+ reinitStatement(st, FALSE);
+ token = activeToken(st);
+ copyToken(token, save);
+ deleteToken(save);
+ processToken(token, st);
+}
+
+/* Skips over a the mem-initializer-list of a ctor-initializer, defined as:
+ *
+ * mem-initializer-list:
+ * mem-initializer, mem-initializer-list
+ *
+ * mem-initializer:
+ * [::] [nested-name-spec] class-name (...)
+ * identifier
+ */
+static void skipMemIntializerList(tokenInfo *const token) {
+ int c;
+
+ do {
+ c = skipToNonWhite();
+ while (isident1(c) || c == ':') {
+ if (c != ':') readIdentifier(token, c);
+ c = skipToNonWhite();
+ }
+ if (c == '<') {
+ skipToMatch("<>");
+ c = skipToNonWhite();
+ }
+ if (c == '(') {
+ skipToMatch("()");
+ c = skipToNonWhite();
+ }
+ } while (c == ',');
+ cppUngetc(c);
+}
+
+static void skipMacro(statementInfo *const st) {
+ tokenInfo *const prev2 = prevToken(st, 2);
+
+ if (isType(prev2, TOKEN_NAME)) retardToken(st);
+ skipToMatch("()");
+}
+
+/* Skips over characters following the parameter list. This will be either
+ * non-ANSI style function declarations or C++ stuff. Our choices:
+ *
+ * C (K&R):
+ * int func ();
+ * int func (one, two) int one; float two; {...}
+ * C (ANSI):
+ * int func (int one, float two);
+ * int func (int one, float two) {...}
+ * C++:
+ * int foo (...) [const|volatile] [throw (...)];
+ * int foo (...) [const|volatile] [throw (...)] [ctor-initializer] {...}
+ * int foo (...) [const|volatile] [throw (...)] try [ctor-initializer] {...}
+ * catch (...) {...}
+ */
+static boolean skipPostArgumentStuff(statementInfo *const st,
+ parenInfo *const info) {
+ tokenInfo *const token = activeToken(st);
+ unsigned int parameters = info->parameterCount;
+ unsigned int elementCount = 0;
+ boolean restart = FALSE;
+ boolean end = FALSE;
+ int c = skipToNonWhite();
+
+ do {
+ switch (c) {
+ case ')':
+ break;
+ case ':':
+ skipMemIntializerList(token);
+ break; /* ctor-initializer */
+ case '[':
+ skipToMatch("[]");
+ break;
+ case '=':
+ cppUngetc(c);
+ end = TRUE;
+ break;
+ case '{':
+ cppUngetc(c);
+ end = TRUE;
+ break;
+ case '}':
+ cppUngetc(c);
+ end = TRUE;
+ break;
+
+ case '(':
+ if (elementCount > 0) ++elementCount;
+ skipToMatch("()");
+ break;
+
+ case ';':
+ if (parameters == 0 || elementCount < 2) {
+ cppUngetc(c);
+ end = TRUE;
+ } else if (--parameters == 0)
+ end = TRUE;
+ break;
+
+ default:
+ if (isident1(c)) {
+ readIdentifier(token, c);
+ switch (token->keyword) {
+ case KEYWORD_ATTRIBUTE:
+ skipParens();
+ break;
+ case KEYWORD_THROW:
+ skipParens();
+ break;
+ case KEYWORD_TRY:
+ break;
+
+ case KEYWORD_CONST:
+ case KEYWORD_VOLATILE:
+ if (vStringLength(Signature) > 0) {
+ vStringPut(Signature, ' ');
+ vStringCat(Signature, token->name);
+ }
+ break;
+
+ case KEYWORD_CATCH:
+ case KEYWORD_CLASS:
+ case KEYWORD_EXPLICIT:
+ case KEYWORD_EXTERN:
+ case KEYWORD_FRIEND:
+ case KEYWORD_INLINE:
+ case KEYWORD_MUTABLE:
+ case KEYWORD_NAMESPACE:
+ case KEYWORD_NEW:
+ case KEYWORD_NEWCOV:
+ case KEYWORD_OPERATOR:
+ case KEYWORD_OVERLOAD:
+ case KEYWORD_PRIVATE:
+ case KEYWORD_PROTECTED:
+ case KEYWORD_PUBLIC:
+ case KEYWORD_STATIC:
+ case KEYWORD_TEMPLATE:
+ case KEYWORD_TYPEDEF:
+ case KEYWORD_TYPENAME:
+ case KEYWORD_USING:
+ case KEYWORD_VIRTUAL:
+ /* Never allowed within parameter declarations. */
+ restart = TRUE;
+ end = TRUE;
+ break;
+
+ default:
+ if (isType(token, TOKEN_NONE))
+ ;
+ else if (info->isKnrParamList && info->parameterCount > 0)
+ ++elementCount;
+ else {
+ /* If we encounter any other identifier immediately
+ * following an empty parameter list, this is almost
+ * certainly one of those Microsoft macro "thingies"
+ * that the automatic source code generation sticks
+ * in. Terminate the current statement.
+ */
+ restart = TRUE;
+ end = TRUE;
+ }
+ break;
+ }
+ }
+ }
+ if (!end) {
+ c = skipToNonWhite();
+ if (c == EOF) end = TRUE;
+ }
+ } while (!end);
+
+ if (restart)
+ restartStatement(st);
+ else
+ setToken(st, TOKEN_NONE);
+
+ return (boolean)(c != EOF);
+}
+
+static void skipJavaThrows(statementInfo *const st) {
+ tokenInfo *const token = activeToken(st);
+ int c = skipToNonWhite();
+
+ if (isident1(c)) {
+ readIdentifier(token, c);
+ if (token->keyword == KEYWORD_THROWS) {
+ do {
+ c = skipToNonWhite();
+ if (isident1(c)) {
+ readIdentifier(token, c);
+ c = skipToNonWhite();
+ }
+ } while (c == '.' || c == ',');
+ }
+ }
+ cppUngetc(c);
+ setToken(st, TOKEN_NONE);
+}
+
+static void analyzePostParens(statementInfo *const st, parenInfo *const info) {
+ const unsigned long inputLineNumber = getInputLineNumber();
+ int c = skipToNonWhite();
+
+ cppUngetc(c);
+ if (isOneOf(c, "{;,="))
+ ;
+ else if (isLanguage(Lang_java))
+ skipJavaThrows(st);
+ else {
+ if (!skipPostArgumentStuff(st, info)) {
+ verbose("%s: confusing argument declarations beginning at line %lu\n",
+ getInputFileName(), inputLineNumber);
+ longjmp(Exception, (int)ExceptionFormattingError);
+ }
+ }
+}
+
+static boolean languageSupportsGenerics(void) {
+ return (boolean)(isLanguage(Lang_cpp) || isLanguage(Lang_csharp) ||
+ isLanguage(Lang_java));
+}
+
+static void processAngleBracket(void) {
+ int c = cppGetc();
+ if (c == '>') {
+ /* already found match for template */
+ } else if (languageSupportsGenerics() && c != '<' && c != '=') {
+ /* this is a template */
+ cppUngetc(c);
+ skipToMatch("<>");
+ } else if (c == '<') {
+ /* skip "<<" or "<<=". */
+ c = cppGetc();
+ if (c != '=') {
+ cppUngetc(c);
+ }
+ } else {
+ cppUngetc(c);
+ }
+}
+
+static void parseJavaAnnotation(statementInfo *const st) {
+ /*
+ * @Override
+ * @Target(ElementType.METHOD)
+ * @SuppressWarnings(value = "unchecked")
+ *
+ * But watch out for "@interface"!
+ */
+ tokenInfo *const token = activeToken(st);
+
+ int c = skipToNonWhite();
+ readIdentifier(token, c);
+ if (token->keyword == KEYWORD_INTERFACE) {
+ /* Oops. This was actually "@interface" defining a new annotation. */
+ processInterface(st);
+ } else {
+ /* Bug #1691412: skip any annotation arguments. */
+ skipParens();
+ }
+}
+
+static int parseParens(statementInfo *const st, parenInfo *const info) {
+ tokenInfo *const token = activeToken(st);
+ unsigned int identifierCount = 0;
+ unsigned int depth = 1;
+ boolean firstChar = TRUE;
+ int nextChar = '\0';
+
+ CollectingSignature = TRUE;
+ vStringClear(Signature);
+ vStringPut(Signature, '(');
+ info->parameterCount = 1;
+ do {
+ int c = skipToNonWhite();
+ vStringPut(Signature, c);
+
+ switch (c) {
+ case '&':
+ case '*':
+ info->isPointer = TRUE;
+ info->isKnrParamList = FALSE;
+ if (identifierCount == 0) info->isParamList = FALSE;
+ initToken(token);
+ break;
+
+ case ':':
+ info->isKnrParamList = FALSE;
+ break;
+
+ case '.':
+ info->isNameCandidate = FALSE;
+ c = cppGetc();
+ if (c != '.') {
+ cppUngetc(c);
+ info->isKnrParamList = FALSE;
+ } else {
+ c = cppGetc();
+ if (c != '.') {
+ cppUngetc(c);
+ info->isKnrParamList = FALSE;
+ } else
+ vStringCatS(Signature, "..."); /* variable arg list */
+ }
+ break;
+
+ case ',':
+ info->isNameCandidate = FALSE;
+ if (info->isKnrParamList) {
+ ++info->parameterCount;
+ identifierCount = 0;
+ }
+ break;
+
+ case '=':
+ info->isKnrParamList = FALSE;
+ info->isNameCandidate = FALSE;
+ if (firstChar) {
+ info->isParamList = FALSE;
+ skipMacro(st);
+ depth = 0;
+ }
+ break;
+
+ case '[':
+ info->isKnrParamList = FALSE;
+ skipToMatch("[]");
+ break;
+
+ case '<':
+ info->isKnrParamList = FALSE;
+ processAngleBracket();
+ break;
+
+ case ')':
+ if (firstChar) info->parameterCount = 0;
+ --depth;
+ break;
+
+ case '(':
+ info->isKnrParamList = FALSE;
+ if (firstChar) {
+ info->isNameCandidate = FALSE;
+ cppUngetc(c);
+ vStringClear(Signature);
+ skipMacro(st);
+ depth = 0;
+ vStringChop(Signature);
+ } else if (isType(token, TOKEN_PAREN_NAME)) {
+ c = skipToNonWhite();
+ if (c == '*') /* check for function pointer */
+ {
+ skipToMatch("()");
+ c = skipToNonWhite();
+ if (c == '(')
+ skipToMatch("()");
+ else
+ cppUngetc(c);
+ } else {
+ cppUngetc(c);
+ cppUngetc('(');
+ info->nestedArgs = TRUE;
+ }
+ } else
+ ++depth;
+ break;
+
+ default:
+ if (c == '@' && isLanguage(Lang_java)) {
+ parseJavaAnnotation(st);
+ } else if (isident1(c)) {
+ if (++identifierCount > 1) info->isKnrParamList = FALSE;
+ readIdentifier(token, c);
+ if (isType(token, TOKEN_NAME) && info->isNameCandidate)
+ token->type = TOKEN_PAREN_NAME;
+ else if (isType(token, TOKEN_KEYWORD)) {
+ if (token->keyword != KEYWORD_CONST &&
+ token->keyword != KEYWORD_VOLATILE) {
+ info->isKnrParamList = FALSE;
+ info->isNameCandidate = FALSE;
+ }
+ }
+ } else {
+ info->isParamList = FALSE;
+ info->isKnrParamList = FALSE;
+ info->isNameCandidate = FALSE;
+ info->invalidContents = TRUE;
+ }
+ break;
+ }
+ firstChar = FALSE;
+ } while (!info->nestedArgs && depth > 0 &&
+ (info->isKnrParamList || info->isNameCandidate));
+
+ if (!info->nestedArgs)
+ while (depth > 0) {
+ skipToMatch("()");
+ --depth;
+ }
+
+ if (!info->isNameCandidate) initToken(token);
+
+ vStringTerminate(Signature);
+ if (info->isKnrParamList) vStringClear(Signature);
+ CollectingSignature = FALSE;
+ return nextChar;
+}
+
+static void initParenInfo(parenInfo *const info) {
+ info->isPointer = FALSE;
+ info->isParamList = TRUE;
+ info->isKnrParamList = isLanguage(Lang_c);
+ info->isNameCandidate = TRUE;
+ info->invalidContents = FALSE;
+ info->nestedArgs = FALSE;
+ info->parameterCount = 0;
+}
+
+static void analyzeParens(statementInfo *const st) {
+ tokenInfo *const prev = prevToken(st, 1);
+
+ if (st->inFunction && !st->assignment) st->notVariable = TRUE;
+ if (!isType(prev, TOKEN_NONE)) /* in case of ignored enclosing macros */
+ {
+ tokenInfo *const token = activeToken(st);
+ parenInfo info;
+ int c;
+
+ initParenInfo(&info);
+ parseParens(st, &info);
+ c = skipToNonWhite();
+ cppUngetc(c);
+ if (info.invalidContents)
+ reinitStatement(st, FALSE);
+ else if (info.isNameCandidate && isType(token, TOKEN_PAREN_NAME) &&
+ !st->gotParenName &&
+ (!info.isParamList || !st->haveQualifyingName || c == '(' ||
+ (c == '=' && st->implementation != IMP_VIRTUAL) ||
+ (st->declaration == DECL_NONE && isOneOf(c, ",;")))) {
+ token->type = TOKEN_NAME;
+ processName(st);
+ st->gotParenName = TRUE;
+ if (!(c == '(' && info.nestedArgs)) st->isPointer = info.isPointer;
+ } else if (!st->gotArgs && info.isParamList) {
+ st->gotArgs = TRUE;
+ setToken(st, TOKEN_ARGS);
+ advanceToken(st);
+ if (st->scope != SCOPE_TYPEDEF) analyzePostParens(st, &info);
+ } else
+ setToken(st, TOKEN_NONE);
+ }
+}
+
+/*
+ * Token parsing functions
+ */
+
+static void addContext(statementInfo *const st, const tokenInfo *const token) {
+ if (isType(token, TOKEN_NAME)) {
+ if (vStringLength(st->context->name) > 0) {
+ if (isLanguage(Lang_c) || isLanguage(Lang_cpp))
+ vStringCatS(st->context->name, "::");
+ else if (isLanguage(Lang_java) || isLanguage(Lang_csharp))
+ vStringCatS(st->context->name, ".");
+ }
+ vStringCat(st->context->name, token->name);
+ st->context->type = TOKEN_NAME;
+ }
+}
+
+static boolean inheritingDeclaration(declType decl) {
+ /* C# supports inheritance for enums. C++0x will too, but not yet. */
+ if (decl == DECL_ENUM) {
+ return (boolean)(isLanguage(Lang_csharp));
+ }
+ return (boolean)(decl == DECL_CLASS || decl == DECL_STRUCT ||
+ decl == DECL_INTERFACE);
+}
+
+static void processColon(statementInfo *const st) {
+ int c = (isLanguage(Lang_cpp) ? cppGetc() : skipToNonWhite());
+ const boolean doubleColon = (boolean)(c == ':');
+
+ if (doubleColon) {
+ setToken(st, TOKEN_DOUBLE_COLON);
+ st->haveQualifyingName = FALSE;
+ } else {
+ cppUngetc(c);
+ if ((isLanguage(Lang_cpp) || isLanguage(Lang_csharp)) &&
+ inheritingDeclaration(st->declaration)) {
+ readParents(st, ':');
+ } else if (parentDecl(st) == DECL_STRUCT) {
+ c = skipToOneOf(",;");
+ if (c == ',')
+ setToken(st, TOKEN_COMMA);
+ else if (c == ';')
+ setToken(st, TOKEN_SEMICOLON);
+ } else {
+ const tokenInfo *const prev = prevToken(st, 1);
+ const tokenInfo *const prev2 = prevToken(st, 2);
+ if (prev->keyword == KEYWORD_DEFAULT || prev2->keyword == KEYWORD_CASE ||
+ st->parent != NULL) {
+ reinitStatement(st, FALSE);
+ }
+ }
+ }
+}
+
+/* Skips over any initializing value which may follow an '=' character in a
+ * variable definition.
+ */
+static int skipInitializer(statementInfo *const st) {
+ boolean done = FALSE;
+ int c;
+
+ while (!done) {
+ c = skipToNonWhite();
+
+ if (c == EOF)
+ longjmp(Exception, (int)ExceptionFormattingError);
+ else
+ switch (c) {
+ case ',':
+ case ';':
+ done = TRUE;
+ break;
+
+ case '0':
+ if (st->implementation == IMP_VIRTUAL)
+ st->implementation = IMP_PURE_VIRTUAL;
+ break;
+
+ case '[':
+ skipToMatch("[]");
+ break;
+ case '(':
+ skipToMatch("()");
+ break;
+ case '{':
+ skipToMatch("{}");
+ break;
+ case '<':
+ processAngleBracket();
+ break;
+
+ case '}':
+ if (insideEnumBody(st))
+ done = TRUE;
+ else if (!isBraceFormat()) {
+ verbose("%s: unexpected closing brace at line %lu\n",
+ getInputFileName(), getInputLineNumber());
+ longjmp(Exception, (int)ExceptionBraceFormattingError);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ return c;
+}
+
+static void processInitializer(statementInfo *const st) {
+ const boolean inEnumBody = insideEnumBody(st);
+ int c = cppGetc();
+
+ if (c != '=') {
+ cppUngetc(c);
+ c = skipInitializer(st);
+ st->assignment = TRUE;
+ if (c == ';')
+ setToken(st, TOKEN_SEMICOLON);
+ else if (c == ',')
+ setToken(st, TOKEN_COMMA);
+ else if (c == '}' && inEnumBody) {
+ cppUngetc(c);
+ setToken(st, TOKEN_COMMA);
+ }
+ if (st->scope == SCOPE_EXTERN) st->scope = SCOPE_GLOBAL;
+ }
+}
+
+static void parseIdentifier(statementInfo *const st, const int c) {
+ tokenInfo *const token = activeToken(st);
+
+ readIdentifier(token, c);
+ if (!isType(token, TOKEN_NONE)) processToken(token, st);
+}
+
+static void parseGeneralToken(statementInfo *const st, const int c) {
+ const tokenInfo *const prev = prevToken(st, 1);
+
+ if (isident1(c) || (isLanguage(Lang_java) && isHighChar(c))) {
+ parseIdentifier(st, c);
+ if (isType(st->context, TOKEN_NAME) &&
+ isType(activeToken(st), TOKEN_NAME) && isType(prev, TOKEN_NAME)) {
+ initToken(st->context);
+ }
+ } else if (c == '.' || c == '-') {
+ if (!st->assignment) st->notVariable = TRUE;
+ if (c == '-') {
+ int c2 = cppGetc();
+ if (c2 != '>') cppUngetc(c2);
+ }
+ } else if (c == '!' || c == '>') {
+ int c2 = cppGetc();
+ if (c2 != '=') cppUngetc(c2);
+ } else if (c == '@' && isLanguage(Lang_java)) {
+ parseJavaAnnotation(st);
+ } else if (isExternCDecl(st, c)) {
+ st->declaration = DECL_NOMANGLE;
+ st->scope = SCOPE_GLOBAL;
+ }
+}
+
+/* Reads characters from the pre-processor and assembles tokens, setting
+ * the current statement state.
+ */
+static void nextToken(statementInfo *const st) {
+ tokenInfo *token;
+ do {
+ int c = skipToNonWhite();
+ switch (c) {
+ case EOF:
+ longjmp(Exception, (int)ExceptionEOF);
+ break;
+ case '(':
+ analyzeParens(st);
+ break;
+ case '<':
+ processAngleBracket();
+ break;
+ case '*':
+ st->haveQualifyingName = FALSE;
+ break;
+ case ',':
+ setToken(st, TOKEN_COMMA);
+ break;
+ case ':':
+ processColon(st);
+ break;
+ case ';':
+ setToken(st, TOKEN_SEMICOLON);
+ break;
+ case '=':
+ processInitializer(st);
+ break;
+ case '[':
+ skipToMatch("[]");
+ break;
+ case '{':
+ setToken(st, TOKEN_BRACE_OPEN);
+ break;
+ case '}':
+ setToken(st, TOKEN_BRACE_CLOSE);
+ break;
+ default:
+ parseGeneralToken(st, c);
+ break;
+ }
+ token = activeToken(st);
+ } while (isType(token, TOKEN_NONE));
+}
+
+/*
+ * Scanning support functions
+ */
+
+static statementInfo *CurrentStatement = NULL;
+
+static statementInfo *newStatement(statementInfo *const parent) {
+ statementInfo *const st = xMalloc(1, statementInfo);
+ unsigned int i;
+
+ for (i = 0; i < (unsigned int)NumTokens; ++i) st->token[i] = newToken();
+
+ st->context = newToken();
+ st->blockName = newToken();
+ st->parentClasses = vStringNew();
+
+ initStatement(st, parent);
+ CurrentStatement = st;
+
+ return st;
+}
+
+static void deleteStatement(void) {
+ statementInfo *const st = CurrentStatement;
+ statementInfo *const parent = st->parent;
+ unsigned int i;
+
+ for (i = 0; i < (unsigned int)NumTokens; ++i) {
+ deleteToken(st->token[i]);
+ st->token[i] = NULL;
+ }
+ deleteToken(st->blockName);
+ st->blockName = NULL;
+ deleteToken(st->context);
+ st->context = NULL;
+ vStringDelete(st->parentClasses);
+ st->parentClasses = NULL;
+ eFree(st);
+ CurrentStatement = parent;
+}
+
+static void deleteAllStatements(void) {
+ while (CurrentStatement != NULL) deleteStatement();
+}
+
+static boolean isStatementEnd(const statementInfo *const st) {
+ const tokenInfo *const token = activeToken(st);
+ boolean isEnd;
+
+ if (isType(token, TOKEN_SEMICOLON))
+ isEnd = TRUE;
+ else if (isType(token, TOKEN_BRACE_CLOSE))
+ /* Java and C# do not require semicolons to end a block. Neither do C++
+ * namespaces. All other blocks require a semicolon to terminate them.
+ */
+ isEnd = (boolean)(isLanguage(Lang_java) || isLanguage(Lang_csharp) ||
+ !isContextualStatement(st));
+ else
+ isEnd = FALSE;
+
+ return isEnd;
+}
+
+static void checkStatementEnd(statementInfo *const st) {
+ const tokenInfo *const token = activeToken(st);
+
+ if (isType(token, TOKEN_COMMA))
+ reinitStatement(st, TRUE);
+ else if (isStatementEnd(st)) {
+ DebugStatement(if (debug(DEBUG_PARSE)) printf("");)
+ reinitStatement(st, FALSE);
+ cppEndStatement();
+ } else {
+ cppBeginStatement();
+ advanceToken(st);
+ }
+}
+
+static void nest(statementInfo *const st, const unsigned int nestLevel) {
+ switch (st->declaration) {
+ case DECL_CLASS:
+ case DECL_ENUM:
+ case DECL_INTERFACE:
+ case DECL_NAMESPACE:
+ case DECL_NOMANGLE:
+ case DECL_STRUCT:
+ case DECL_UNION:
+ createTags(nestLevel, st);
+ break;
+
+ case DECL_FUNCTION:
+ case DECL_TASK:
+ st->inFunction = TRUE;
+ /* fall through */
+ default:
+ if (includeTag(TAG_LOCAL, FALSE))
+ createTags(nestLevel, st);
+ else
+ skipToMatch("{}");
+ break;
+ }
+ advanceToken(st);
+ setToken(st, TOKEN_BRACE_CLOSE);
+}
+
+static void tagCheck(statementInfo *const st) {
+ const tokenInfo *const token = activeToken(st);
+ const tokenInfo *const prev = prevToken(st, 1);
+ const tokenInfo *const prev2 = prevToken(st, 2);
+
+ switch (token->type) {
+ case TOKEN_NAME:
+ if (insideEnumBody(st)) qualifyEnumeratorTag(st, token);
+ break;
+#if 0
+ case TOKEN_PACKAGE:
+ if (st->haveQualifyingName)
+ makeTag (token, st, FALSE, TAG_PACKAGE);
+ break;
+#endif
+ case TOKEN_BRACE_OPEN:
+ if (isType(prev, TOKEN_ARGS)) {
+ if (st->haveQualifyingName) {
+ if (!isLanguage(Lang_vera)) st->declaration = DECL_FUNCTION;
+ if (isType(prev2, TOKEN_NAME)) copyToken(st->blockName, prev2);
+ qualifyFunctionTag(st, prev2);
+ }
+ } else if (isContextualStatement(st) ||
+ st->declaration == DECL_NAMESPACE ||
+ st->declaration == DECL_PROGRAM) {
+ if (isType(prev, TOKEN_NAME))
+ copyToken(st->blockName, prev);
+ else {
+ /* For an anonymous struct or union we use a unique ID
+ * a number, so that the members can be found.
+ */
+ char buf[20]; /* length of "_anon" + digits + null */
+ sprintf(buf, "__anon%d", ++AnonymousID);
+ vStringCopyS(st->blockName->name, buf);
+ st->blockName->type = TOKEN_NAME;
+ st->blockName->keyword = KEYWORD_NONE;
+ }
+ qualifyBlockTag(st, prev);
+ } else if (isLanguage(Lang_csharp))
+ makeTag(prev, st, FALSE, TAG_PROPERTY);
+ break;
+
+ case TOKEN_SEMICOLON:
+ case TOKEN_COMMA:
+ if (insideEnumBody(st))
+ ;
+ else if (isType(prev, TOKEN_NAME)) {
+ if (isContextualKeyword(prev2))
+ makeTag(prev, st, TRUE, TAG_EXTERN_VAR);
+ else
+ qualifyVariableTag(st, prev);
+ } else if (isType(prev, TOKEN_ARGS) && isType(prev2, TOKEN_NAME)) {
+ if (st->isPointer)
+ qualifyVariableTag(st, prev2);
+ else
+ qualifyFunctionDeclTag(st, prev2);
+ }
+ if (isLanguage(Lang_java) && token->type == TOKEN_SEMICOLON &&
+ insideEnumBody(st)) {
+ /* In Java, after an initial enum-like part,
+ * a semicolon introduces a class-like part.
+ * See Bug #1730485 for the full rationale. */
+ st->parent->declaration = DECL_CLASS;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* Parses the current file and decides whether to write out and tags that
+ * are discovered.
+ */
+static void createTags(const unsigned int nestLevel,
+ statementInfo *const parent) {
+ statementInfo *const st = newStatement(parent);
+
+ DebugStatement(if (nestLevel > 0)
+ debugParseNest(TRUE, nestLevel);) while (TRUE) {
+ tokenInfo *token;
+
+ nextToken(st);
+ token = activeToken(st);
+ if (isType(token, TOKEN_BRACE_CLOSE)) {
+ if (nestLevel > 0)
+ break;
+ else {
+ verbose("%s: unexpected closing brace at line %lu\n",
+ getInputFileName(), getInputLineNumber());
+ longjmp(Exception, (int)ExceptionBraceFormattingError);
+ }
+ } else if (isType(token, TOKEN_DOUBLE_COLON)) {
+ addContext(st, prevToken(st, 1));
+ advanceToken(st);
+ } else {
+ tagCheck(st);
+ if (isType(token, TOKEN_BRACE_OPEN)) nest(st, nestLevel + 1);
+ checkStatementEnd(st);
+ }
+ }
+ deleteStatement();
+ DebugStatement(if (nestLevel > 0) debugParseNest(FALSE, nestLevel - 1);)
+}
+
+static boolean findCTags(const unsigned int passCount) {
+ exception_t exception;
+ boolean retry;
+
+ Assert(passCount < 3);
+ cppInit((boolean)(passCount > 1), isLanguage(Lang_csharp));
+ Signature = vStringNew();
+
+ exception = (exception_t)setjmp(Exception);
+ retry = FALSE;
+ if (exception == ExceptionNone)
+ createTags(0, NULL);
+ else {
+ deleteAllStatements();
+ if (exception == ExceptionBraceFormattingError && passCount == 1) {
+ retry = TRUE;
+ verbose("%s: retrying file with fallback brace matching algorithm\n",
+ getInputFileName());
+ }
+ }
+ vStringDelete(Signature);
+ cppTerminate();
+ return retry;
+}
+
+static void buildKeywordHash(const langType language, unsigned int idx) {
+ const size_t count = sizeof(KeywordTable) / sizeof(KeywordTable[0]);
+ size_t i;
+ for (i = 0; i < count; ++i) {
+ const keywordDesc *const p = &KeywordTable[i];
+ if (p->isValid[idx]) addKeyword(p->name, language, (int)p->id);
+ }
+}
+
+static void initializeCParser(const langType language) {
+ Lang_c = language;
+ buildKeywordHash(language, 0);
+}
+
+static void initializeCppParser(const langType language) {
+ Lang_cpp = language;
+ buildKeywordHash(language, 1);
+}
+
+static void initializeCsharpParser(const langType language) {
+ Lang_csharp = language;
+ buildKeywordHash(language, 2);
+}
+
+static void initializeJavaParser(const langType language) {
+ Lang_java = language;
+ buildKeywordHash(language, 3);
+}
+
+static void initializeVeraParser(const langType language) {
+ Lang_vera = language;
+ buildKeywordHash(language, 4);
+}
+
+extern parserDefinition *CParser(void) {
+ static const char *const extensions[] = {"c", NULL};
+ parserDefinition *def = parserNew("C");
+ def->kinds = CKinds;
+ def->kindCount = KIND_COUNT(CKinds);
+ def->extensions = extensions;
+ def->parser2 = findCTags;
+ def->initialize = initializeCParser;
+ return def;
+}
+
+extern parserDefinition *CppParser(void) {
+ static const char *const extensions[] = {"c++", "cc", "cp", "cpp", "cxx", "h",
+ "h++", "hh", "hp", "hpp", "hxx",
+#ifndef CASE_INSENSITIVE_FILENAMES
+ "C", "H",
+#endif
+ NULL};
+ parserDefinition *def = parserNew("C++");
+ def->kinds = CKinds;
+ def->kindCount = KIND_COUNT(CKinds);
+ def->extensions = extensions;
+ def->parser2 = findCTags;
+ def->initialize = initializeCppParser;
+ return def;
+}
+
+extern parserDefinition *CsharpParser(void) {
+ static const char *const extensions[] = {"cs", NULL};
+ parserDefinition *def = parserNew("C#");
+ def->kinds = CsharpKinds;
+ def->kindCount = KIND_COUNT(CsharpKinds);
+ def->extensions = extensions;
+ def->parser2 = findCTags;
+ def->initialize = initializeCsharpParser;
+ return def;
+}
+
+extern parserDefinition *JavaParser(void) {
+ static const char *const extensions[] = {"java", NULL};
+ parserDefinition *def = parserNew("Java");
+ def->kinds = JavaKinds;
+ def->kindCount = KIND_COUNT(JavaKinds);
+ def->extensions = extensions;
+ def->parser2 = findCTags;
+ def->initialize = initializeJavaParser;
+ return def;
+}
+
+extern parserDefinition *VeraParser(void) {
+ static const char *const extensions[] = {"vr", "vri", "vrh", NULL};
+ parserDefinition *def = parserNew("Vera");
+ def->kinds = VeraKinds;
+ def->kindCount = KIND_COUNT(VeraKinds);
+ def->extensions = extensions;
+ def->parser2 = findCTags;
+ def->initialize = initializeVeraParser;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */
diff --git a/third_party/ctags/cobol.c b/third_party/ctags/cobol.c
new file mode 100644
index 000000000..3ee9b4b7c
--- /dev/null
+++ b/third_party/ctags/cobol.c
@@ -0,0 +1,47 @@
+/*
+ * $Id: cobol.c 443 2006-05-30 04:37:13Z darren $
+ *
+ * Copyright (c) 2000-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for COBOL language
+ * files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parse.h"
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void installCobolRegex(const langType language) {
+ addTagRegex(
+ language,
+ "^[ \t]*[0-9]+[ \t]+([A-Z0-9][A-Z0-9-]*)[ "
+ "\t]+(BLANK|OCCURS|IS|JUST|PIC|REDEFINES|RENAMES|SIGN|SYNC|USAGE|VALUE)",
+ "\\1", "d,data,data items", "i");
+ addTagRegex(language, "^[ \t]*[FSR]D[ \t]+([A-Z0-9][A-Z0-9-]*)\\.", "\\1",
+ "f,file,file descriptions (FD, SD, RD)", "i");
+ addTagRegex(language, "^[ \t]*[0-9]+[ \t]+([A-Z0-9][A-Z0-9-]*)\\.", "\\1",
+ "g,group,group items", "i");
+ addTagRegex(language, "^[ \t]*([A-Z0-9][A-Z0-9-]*)\\.", "\\1",
+ "p,paragraph,paragraphs", "i");
+ addTagRegex(language, "^[ \t]*PROGRAM-ID\\.[ \t]+([A-Z0-9][A-Z0-9-]*)\\.",
+ "\\1", "P,program,program ids", "i");
+ addTagRegex(language, "^[ \t]*([A-Z0-9][A-Z0-9-]*)[ \t]+SECTION\\.", "\\1",
+ "s,section,sections", "i");
+}
+
+extern parserDefinition* CobolParser() {
+ static const char* const extensions[] = {"cbl", "cob", "CBL", "COB", NULL};
+ parserDefinition* def = parserNew("Cobol");
+ def->extensions = extensions;
+ def->initialize = installCobolRegex;
+ def->regex = TRUE;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/config.h b/third_party/ctags/config.h
new file mode 100644
index 000000000..552c69f3b
--- /dev/null
+++ b/third_party/ctags/config.h
@@ -0,0 +1,283 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define this label if your system uses case-insensitive file names */
+/* #undef CASE_INSENSITIVE_FILENAMES */
+
+/* Define this label if you wish to check the regcomp() function at run time
+ for correct behavior. This function is currently broken on Cygwin. */
+/* #undef CHECK_REGCOMP */
+
+/* You can define this label to be a string containing the name of a
+ site-specific configuration file containing site-wide default options. The
+ files /etc/ctags.conf and /usr/local/etc/ctags.conf are already checked, so
+ only define one here if you need a file somewhere else. */
+/* #undef CUSTOM_CONFIGURATION_FILE */
+
+/* Define this as desired.
+ * 1: Original ctags format
+ * 2: Extended ctags format with extension flags in EX-style comment.
+ */
+#define DEFAULT_FILE_FORMAT 2
+
+/* Define this label to use the system sort utility (which is probably more
+ * efficient) over the internal sorting algorithm.
+ */
+#ifndef INTERNAL_SORT
+#define EXTERNAL_SORT 1
+#endif
+
+/* Define to 1 if you have the `chmod' function. */
+/* #undef HAVE_CHMOD */
+
+/* Define to 1 if you have the `chsize' function. */
+/* #undef HAVE_CHSIZE */
+
+/* Define to 1 if you have the `clock' function. */
+#define HAVE_CLOCK 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fgetpos' function. */
+#define HAVE_FGETPOS 1
+
+/* Define to 1 if you have the `findfirst' function. */
+/* #undef HAVE_FINDFIRST */
+
+/* Define to 1 if you have the `fnmatch' function. */
+#define HAVE_FNMATCH 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_FNMATCH_H 1
+
+/* Define to 1 if you have the `ftruncate' function. */
+/* #undef HAVE_FTRUNCATE */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the `opendir' function. */
+#define HAVE_OPENDIR 1
+
+/* Define to 1 if you have the `putenv' function. */
+/* #undef HAVE_PUTENV */
+
+/* Define to 1 if you have the `regcomp' function. */
+#define HAVE_REGCOMP 1
+
+/* Define to 1 if you have the `remove' function. */
+#define HAVE_REMOVE 1
+
+/* Define to 1 if you have the `setenv' function. */
+#define HAVE_SETENV 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_STAT_H */
+
+/* Define this macro if the field "st_ino" exists in struct stat in
+ . */
+#define HAVE_STAT_ST_INO 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the `stricmp' function. */
+/* #undef HAVE_STRICMP */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if you have the `strnicmp' function. */
+/* #undef HAVE_STRNICMP */
+
+/* Define to 1 if you have the `strstr' function. */
+#define HAVE_STRSTR 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_DIR_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_TIMES_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the `tempnam' function. */
+/* #undef HAVE_TEMPNAM */
+
+/* Define to 1 if you have the `times' function. */
+/* #undef HAVE_TIMES */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_TIME_H 1
+
+/* Define to 1 if you have the `truncate' function. */
+#define HAVE_TRUNCATE 1
+
+/* Define to 1 if you have the header file. */
+/* #undef HAVE_TYPES_H */
+
+/* Define to 1 if you have the header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the `_findfirst' function. */
+/* #undef HAVE__FINDFIRST */
+
+/* Define as the maximum integer on your system if not defined . */
+/* #undef INT_MAX */
+
+/* Define to the appropriate size for tmpnam() if does not define
+ this. */
+/* #undef L_tmpnam */
+
+/* Define this label if you want macro tags (defined lables) to use patterns
+ in the EX command by default (original ctags behavior is to use line
+ numbers). */
+/* #undef MACROS_USE_PATTERNS */
+
+/* If you receive error or warning messages indicating that you are missing a
+ prototype for, or a type mismatch using, the following function, define
+ this label and remake. */
+/* #undef NEED_PROTO_FGETPOS */
+
+/* If you receive error or warning messages indicating that you are missing a
+ prototype for, or a type mismatch using, the following function, define
+ this label and remake. */
+/* #undef NEED_PROTO_FTRUNCATE */
+
+/* If you receive error or warning messages indicating that you are missing a
+ prototype for, or a type mismatch using, the following function, define
+ this label and remake. */
+/* #undef NEED_PROTO_GETENV */
+
+/* If you receive error or warning messages indicating that you are missing a
+ prototype for, or a type mismatch using, the following function, define
+ this label and remake. */
+/* #undef NEED_PROTO_LSTAT */
+
+/* If you receive error or warning messages indicating that you are missing a
+ prototype for, or a type mismatch using, the following function, define
+ this label and remake. */
+/* #undef NEED_PROTO_MALLOC */
+
+/* If you receive error or warning messages indicating that you are missing a
+ prototype for, or a type mismatch using, the following function, define
+ this label and remake. */
+/* #undef NEED_PROTO_REMOVE */
+
+/* If you receive error or warning messages indicating that you are missing a
+ prototype for, or a type mismatch using, the following function, define
+ this label and remake. */
+/* #undef NEED_PROTO_STAT */
+
+/* If you receive error or warning messages indicating that you are missing a
+ prototype for, or a type mismatch using, the following function, define
+ this label and remake. */
+/* #undef NEED_PROTO_TRUNCATE */
+
+/* If you receive error or warning messages indicating that you are missing a
+ prototype for, or a type mismatch using, the following function, define
+ this label and remake. */
+/* #undef NEED_PROTO_UNLINK */
+
+/* Define this is you have a prototype for putenv() in , but doesn't
+ declare its argument as "const char *". */
+/* #undef NON_CONST_PUTENV_PROTOTYPE */
+
+/* Package name. */
+/* #undef PACKAGE */
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* Define this label if regcomp() is broken. */
+/* #undef REGCOMP_BROKEN */
+
+/* Define this value used by fseek() appropriately if (or
+ on SunOS 4.1.x) does not define them. */
+/* #undef SEEK_SET */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define this label if your system supports starting scripts with a line of
+ the form "#! /bin/sh" to select the interpreter to use for the script. */
+#define SYS_INTERPRETER 1
+
+/* If you wish to change the directory in which temporary files are stored,
+ define this label to the directory desired. */
+#define TMPDIR kTmpPath
+
+/* Package version. */
+/* #undef VERSION */
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* This corrects the problem of missing prototypes for certain functions in
+ some GNU installations (e.g. SunOS 4.1.x). */
+/* #undef __USE_FIXED_PROTOTYPES__ */
+
+/* Define to the appropriate type if does not define this. */
+/* #undef clock_t */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to long if does not define this. */
+/* #undef fpos_t */
+
+/* Define to `long int' if does not define. */
+/* #undef off_t */
+
+/* Define remove to unlink if you have unlink(), but not remove(). */
+/* #undef remove */
+
+/* Define to `unsigned int' if does not define. */
+/* #undef size_t */
diff --git a/third_party/ctags/ctags.h b/third_party/ctags/ctags.h
new file mode 100644
index 000000000..685f0220e
--- /dev/null
+++ b/third_party/ctags/ctags.h
@@ -0,0 +1,26 @@
+/*
+ * $Id: ctags.h 702 2009-03-14 03:52:21Z dhiebert $
+ *
+ * Copyright (c) 1996-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * Program definitions
+ */
+#ifndef _CTAGS_H
+#define _CTAGS_H
+
+/*
+ * MACROS
+ */
+#ifndef PROGRAM_VERSION
+#define PROGRAM_VERSION "5.9~svn20110310"
+#endif
+#define PROGRAM_NAME "Exuberant Ctags"
+#define PROGRAM_URL "http://ctags.sourceforge.net"
+#define PROGRAM_COPYRIGHT "Copyright (C) 1996-2009"
+#define AUTHOR_NAME "Darren Hiebert"
+#define AUTHOR_EMAIL "dhiebert@users.sourceforge.net"
+
+#endif /* _CTAGS_H */
diff --git a/third_party/ctags/ctags.mk b/third_party/ctags/ctags.mk
new file mode 100644
index 000000000..e4ead4cdd
--- /dev/null
+++ b/third_party/ctags/ctags.mk
@@ -0,0 +1,61 @@
+#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
+#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘
+
+THIRD_PARTY_CTAGS_FILES := $(wildcard third_party/ctags/*)
+THIRD_PARTY_CTAGS_HDRS = $(filter %.h,$(THIRD_PARTY_CTAGS_FILES))
+THIRD_PARTY_CTAGS_SRCS = $(filter %.c,$(THIRD_PARTY_CTAGS_FILES))
+
+THIRD_PARTY_CTAGS_BINS = \
+ o/$(MODE)/third_party/ctags/ctags.com
+
+THIRD_PARTY_CTAGS_CHECKS = \
+ o/$(MODE)/third_party/ctags/ctags.pkg \
+ $(THIRD_PARTY_CTAGS_HDRS:%=o/$(MODE)/%.ok)
+
+THIRD_PARTY_CTAGS_DIRECTDEPS = \
+ LIBC_CALLS \
+ LIBC_CALLS_HEFTY \
+ LIBC_CONV \
+ LIBC_RUNTIME \
+ LIBC_MEM \
+ LIBC_FMT \
+ LIBC_LOG \
+ LIBC_TIME \
+ LIBC_SYSV \
+ LIBC_STR \
+ LIBC_STUBS \
+ LIBC_STDIO \
+ LIBC_NEXGEN32E \
+ LIBC_UNICODE \
+ THIRD_PARTY_MUSL \
+ THIRD_PARTY_REGEX
+
+THIRD_PARTY_CTAGS_DEPS := \
+ $(call uniq,$(foreach x,$(THIRD_PARTY_CTAGS_DIRECTDEPS),$($(x))))
+
+o/$(MODE)/third_party/ctags/ctags.srcs.a: \
+ third_party/ctags/ \
+ $(THIRD_PARTY_CTAGS_SRCS:%=o/$(MODE)/%.zip.o)
+
+o/$(MODE)/third_party/ctags/ctags.com.dbg: \
+ o/$(MODE)/third_party/ctags/ctags.pkg \
+ o/$(MODE)/third_party/ctags/ctags.srcs.a \
+ $(THIRD_PARTY_CTAGS_DEPS) \
+ $(THIRD_PARTY_CTAGS_SRCS:%.c=o/$(MODE)/%.o) \
+ $(CRT) \
+ $(APE)
+ @$(APELINK)
+
+o/$(MODE)/third_party/ctags/ctags.pkg: \
+ $(THIRD_PARTY_CTAGS_SRCS:%.c=o/$(MODE)/%.o) \
+ o/$(MODE)/third_party/ctags/ctags.srcs.pkg \
+ $(foreach x,$(THIRD_PARTY_CTAGS_DIRECTDEPS),$($(x)_A).pkg)
+
+o/$(MODE)/third_party/ctags/ctags.srcs.pkg: \
+ $(THIRD_PARTY_CTAGS_SRCS:%=o/$(MODE)/%.zip.o) \
+ $(foreach x,$(THIRD_PARTY_CTAGS_DIRECTDEPS),$($(x)_A).pkg)
+
+.PHONY: o/$(MODE)/third_party/ctags
+o/$(MODE)/third_party/ctags: \
+ $(THIRD_PARTY_CTAGS_BINS) \
+ $(THIRD_PARTY_CTAGS_CHECKS)
diff --git a/third_party/ctags/debug.h b/third_party/ctags/debug.h
new file mode 100644
index 000000000..2b13c981c
--- /dev/null
+++ b/third_party/ctags/debug.h
@@ -0,0 +1,53 @@
+#ifndef _DEBUG_H
+#define _DEBUG_H
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/entry.h"
+
+/*
+ * Macros
+ */
+
+#ifdef DEBUG
+#define debug(level) ((Option.debugLevel & (long)(level)) != 0)
+#define DebugStatement(x) x
+#define PrintStatus(x) \
+ if (debug(DEBUG_STATUS)) printf x;
+#define Assert(c) assert(c)
+#else
+#define DebugStatement(x)
+#define PrintStatus(x)
+#define Assert(c)
+#ifndef NDEBUG
+#define NDEBUG
+#endif
+#endif
+
+/*
+ * Data declarations
+ */
+
+/* Defines the debugging levels.
+ */
+enum eDebugLevels {
+ DEBUG_READ = 0x01, /* echo raw (filtered) characters */
+ DEBUG_PARSE = 0x02, /* echo parsing results */
+ DEBUG_STATUS = 0x04, /* echo file status information */
+ DEBUG_OPTION = 0x08, /* echo option parsing */
+ DEBUG_CPP = 0x10, /* echo characters out of pre-processor */
+ DEBUG_RAW = 0x20 /* echo raw (filtered) characters */
+};
+
+/*
+ * Function prototypes
+ */
+extern void lineBreak(void);
+extern void debugPrintf(const enum eDebugLevels level, const char *const format,
+ ...) __printf__(2, 3);
+extern void debugPutc(const int level, const int c);
+extern void debugParseNest(const boolean increase, const unsigned int level);
+extern void debugCppNest(const boolean begin, const unsigned int level);
+extern void debugCppIgnore(const boolean ignore);
+extern void debugEntry(const tagEntryInfo *const tag);
+
+#endif /* _DEBUG_H */
diff --git a/third_party/ctags/dosbatch.c b/third_party/ctags/dosbatch.c
new file mode 100644
index 000000000..b446b8646
--- /dev/null
+++ b/third_party/ctags/dosbatch.c
@@ -0,0 +1,35 @@
+/*
+ * $Id$
+ *
+ * Copyright (c) 2009, David Fishburn
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for DOS Batch language
+ * files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parse.h"
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void installDosBatchRegex(const langType language) {
+ addTagRegex(language, "^:([A-Za-z_0-9]+)", "\\1", "l,label,labels", NULL);
+ addTagRegex(language, "set[ \t]+([A-Za-z_0-9]+)[ \t]*=", "\\1",
+ "v,variable,variables", NULL);
+}
+
+extern parserDefinition* DosBatchParser() {
+ static const char* const extensions[] = {"bat", "cmd", NULL};
+ parserDefinition* const def = parserNew("DosBatch");
+ def->extensions = extensions;
+ def->initialize = installDosBatchRegex;
+ def->regex = TRUE;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/eiffel.c b/third_party/ctags/eiffel.c
new file mode 100644
index 000000000..135d214e1
--- /dev/null
+++ b/third_party/ctags/eiffel.c
@@ -0,0 +1,1304 @@
+/*
+ * $Id: eiffel.c 748 2009-11-06 02:44:42Z dhiebert $
+ *
+ * Copyright (c) 1998-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for Eiffel language
+ * files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/calls/calls.h"
+#include "libc/conv/conv.h"
+#include "libc/runtime/runtime.h"
+#include "libc/str/str.h"
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * MACROS
+ */
+#define isident(c) (isalnum(c) || (c) == '_')
+#define isFreeOperatorChar(c) \
+ ((c) == '@' || (c) == '#' || (c) == '|' || (c) == '&')
+#define isType(token, t) (boolean)((token)->type == (t))
+#define isKeyword(token, k) (boolean)((token)->keyword == (k))
+
+/*
+ * DATA DECLARATIONS
+ */
+
+typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
+
+/* Used to specify type of keyword.
+ */
+typedef enum eKeywordId {
+ KEYWORD_NONE = -1,
+ KEYWORD_alias,
+ KEYWORD_all,
+ KEYWORD_and,
+ KEYWORD_as,
+ KEYWORD_assign,
+ KEYWORD_attached,
+ KEYWORD_check,
+ KEYWORD_class,
+ KEYWORD_convert,
+ KEYWORD_create,
+ KEYWORD_creation,
+ KEYWORD_Current,
+ KEYWORD_debug,
+ KEYWORD_deferred,
+ KEYWORD_detachable,
+ KEYWORD_do,
+ KEYWORD_else,
+ KEYWORD_elseif,
+ KEYWORD_end,
+ KEYWORD_ensure,
+ KEYWORD_expanded,
+ KEYWORD_export,
+ KEYWORD_external,
+ KEYWORD_false,
+ KEYWORD_feature,
+ KEYWORD_from,
+ KEYWORD_frozen,
+ KEYWORD_if,
+ KEYWORD_implies,
+ KEYWORD_indexing,
+ KEYWORD_infix,
+ KEYWORD_inherit,
+ KEYWORD_inspect,
+ KEYWORD_invariant,
+ KEYWORD_is,
+ KEYWORD_like,
+ KEYWORD_local,
+ KEYWORD_loop,
+ KEYWORD_not,
+ KEYWORD_obsolete,
+ KEYWORD_old,
+ KEYWORD_once,
+ KEYWORD_or,
+ KEYWORD_prefix,
+ KEYWORD_redefine,
+ KEYWORD_rename,
+ KEYWORD_require,
+ KEYWORD_rescue,
+ KEYWORD_Result,
+ KEYWORD_retry,
+ KEYWORD_select,
+ KEYWORD_separate,
+ KEYWORD_strip,
+ KEYWORD_then,
+ KEYWORD_true,
+ KEYWORD_undefine,
+ KEYWORD_unique,
+ KEYWORD_until,
+ KEYWORD_variant,
+ KEYWORD_when,
+ KEYWORD_xor
+} keywordId;
+
+/* Used to determine whether keyword is valid for the token language and
+ * what its ID is.
+ */
+typedef struct sKeywordDesc {
+ const char *name;
+ keywordId id;
+} keywordDesc;
+
+typedef enum eTokenType {
+ TOKEN_UNDEFINED,
+ TOKEN_BANG,
+ TOKEN_CHARACTER,
+ TOKEN_CLOSE_BRACE,
+ TOKEN_CLOSE_BRACKET,
+ TOKEN_CLOSE_PAREN,
+ TOKEN_COLON,
+ TOKEN_COMMA,
+ TOKEN_CONSTRAINT,
+ TOKEN_DOT,
+ TOKEN_DOLLAR,
+ TOKEN_IDENTIFIER,
+ TOKEN_KEYWORD,
+ TOKEN_NUMERIC,
+ TOKEN_OPEN_BRACE,
+ TOKEN_OPEN_BRACKET,
+ TOKEN_OPEN_PAREN,
+ TOKEN_OPERATOR,
+ TOKEN_OTHER,
+ TOKEN_QUESTION,
+ TOKEN_SEMICOLON,
+ TOKEN_SEPARATOR,
+ TOKEN_STRING,
+ TOKEN_TILDE
+} tokenType;
+
+typedef struct sTokenInfo {
+ tokenType type;
+ keywordId keyword;
+ boolean isExported;
+ vString *string;
+ vString *className;
+ vString *featureName;
+} tokenInfo;
+
+/*
+ * DATA DEFINITIONS
+ */
+
+static langType Lang_eiffel;
+
+#ifdef TYPE_REFERENCE_TOOL
+
+static const char *FileName;
+static FILE *File;
+static int PrintClass;
+static int PrintReferences;
+static int SelfReferences;
+static int Debug;
+static stringList *GenericNames;
+static stringList *ReferencedTypes;
+
+#else
+
+typedef enum {
+ EKIND_CLASS,
+ EKIND_FEATURE,
+ EKIND_LOCAL,
+ EKIND_QUALIFIED_TAGS
+} eiffelKind;
+
+static kindOption EiffelKinds[] = {{TRUE, 'c', "class", "classes"},
+ {TRUE, 'f', "feature", "features"},
+ {FALSE, 'l', "local", "local entities"}};
+
+#endif
+
+static jmp_buf Exception;
+
+static const keywordDesc EiffelKeywordTable[] = {
+ /* keyword keyword ID */
+ {"alias", KEYWORD_alias},
+ {"all", KEYWORD_all},
+ {"and", KEYWORD_and},
+ {"as", KEYWORD_as},
+ {"assign", KEYWORD_assign},
+ {"attached", KEYWORD_attached},
+ {"check", KEYWORD_check},
+ {"class", KEYWORD_class},
+ {"convert", KEYWORD_convert},
+ {"create", KEYWORD_create},
+ {"creation", KEYWORD_creation},
+ {"current", KEYWORD_Current},
+ {"debug", KEYWORD_debug},
+ {"deferred", KEYWORD_deferred},
+ {"detachable", KEYWORD_detachable},
+ {"do", KEYWORD_do},
+ {"else", KEYWORD_else},
+ {"elseif", KEYWORD_elseif},
+ {"end", KEYWORD_end},
+ {"ensure", KEYWORD_ensure},
+ {"expanded", KEYWORD_expanded},
+ {"export", KEYWORD_export},
+ {"external", KEYWORD_external},
+ {"false", KEYWORD_false},
+ {"feature", KEYWORD_feature},
+ {"from", KEYWORD_from},
+ {"frozen", KEYWORD_frozen},
+ {"if", KEYWORD_if},
+ {"implies", KEYWORD_implies},
+ {"indexing", KEYWORD_indexing},
+ {"infix", KEYWORD_infix},
+ {"inherit", KEYWORD_inherit},
+ {"inspect", KEYWORD_inspect},
+ {"invariant", KEYWORD_invariant},
+ {"is", KEYWORD_is},
+ {"like", KEYWORD_like},
+ {"local", KEYWORD_local},
+ {"loop", KEYWORD_loop},
+ {"not", KEYWORD_not},
+ {"obsolete", KEYWORD_obsolete},
+ {"old", KEYWORD_old},
+ {"once", KEYWORD_once},
+ {"or", KEYWORD_or},
+ {"prefix", KEYWORD_prefix},
+ {"redefine", KEYWORD_redefine},
+ {"rename", KEYWORD_rename},
+ {"require", KEYWORD_require},
+ {"rescue", KEYWORD_rescue},
+ {"result", KEYWORD_Result},
+ {"retry", KEYWORD_retry},
+ {"select", KEYWORD_select},
+ {"separate", KEYWORD_separate},
+ {"strip", KEYWORD_strip},
+ {"then", KEYWORD_then},
+ {"true", KEYWORD_true},
+ {"undefine", KEYWORD_undefine},
+ {"unique", KEYWORD_unique},
+ {"until", KEYWORD_until},
+ {"variant", KEYWORD_variant},
+ {"when", KEYWORD_when},
+ {"xor", KEYWORD_xor}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void buildEiffelKeywordHash(void) {
+ const size_t count =
+ sizeof(EiffelKeywordTable) / sizeof(EiffelKeywordTable[0]);
+ size_t i;
+ for (i = 0; i < count; ++i) {
+ const keywordDesc *const p = &EiffelKeywordTable[i];
+ addKeyword(p->name, Lang_eiffel, (int)p->id);
+ }
+}
+
+#ifdef TYPE_REFERENCE_TOOL
+
+static void addGenericName(tokenInfo *const token) {
+ vStringUpper(token->string);
+ if (vStringLength(token->string) > 0)
+ stringListAdd(GenericNames, vStringNewCopy(token->string));
+}
+
+static boolean isGeneric(tokenInfo *const token) {
+ return (boolean)stringListHas(GenericNames, vStringValue(token->string));
+}
+
+static void reportType(tokenInfo *const token) {
+ vStringUpper(token->string);
+ if (vStringLength(token->string) > 0 && !isGeneric(token) &&
+ (SelfReferences || strcmp(vStringValue(token->string),
+ vStringValue(token->className)) != 0) &&
+ !stringListHas(ReferencedTypes, vStringValue(token->string))) {
+ printf("%s\n", vStringValue(token->string));
+ stringListAdd(ReferencedTypes, vStringNewCopy(token->string));
+ }
+}
+
+static int fileGetc(void) {
+ int c = getc(File);
+ if (c == '\r') {
+ c = getc(File);
+ if (c != '\n') {
+ ungetc(c, File);
+ c = '\n';
+ }
+ }
+ if (Debug > 0 && c != EOF) putc(c, errout);
+ return c;
+}
+
+static int fileUngetc(c) {
+ return ungetc(c, File);
+}
+
+extern char *readLine(vString *const vLine, FILE *const fp) {
+ return NULL;
+}
+
+#else
+
+/*
+ * Tag generation functions
+ */
+
+static void makeEiffelClassTag(tokenInfo *const token) {
+ if (EiffelKinds[EKIND_CLASS].enabled) {
+ const char *const name = vStringValue(token->string);
+ tagEntryInfo e;
+
+ initTagEntry(&e, name);
+
+ e.kindName = EiffelKinds[EKIND_CLASS].name;
+ e.kind = EiffelKinds[EKIND_CLASS].letter;
+
+ makeTagEntry(&e);
+ }
+ vStringCopy(token->className, token->string);
+}
+
+static void makeEiffelFeatureTag(tokenInfo *const token) {
+ if (EiffelKinds[EKIND_FEATURE].enabled &&
+ (token->isExported || Option.include.fileScope)) {
+ const char *const name = vStringValue(token->string);
+ tagEntryInfo e;
+
+ initTagEntry(&e, name);
+
+ e.isFileScope = (boolean)(!token->isExported);
+ e.kindName = EiffelKinds[EKIND_FEATURE].name;
+ e.kind = EiffelKinds[EKIND_FEATURE].letter;
+ e.extensionFields.scope[0] = EiffelKinds[EKIND_CLASS].name;
+ e.extensionFields.scope[1] = vStringValue(token->className);
+
+ makeTagEntry(&e);
+
+ if (Option.include.qualifiedTags) {
+ vString *qualified = vStringNewInit(vStringValue(token->className));
+ vStringPut(qualified, '.');
+ vStringCat(qualified, token->string);
+ e.name = vStringValue(qualified);
+ makeTagEntry(&e);
+ vStringDelete(qualified);
+ }
+ }
+ vStringCopy(token->featureName, token->string);
+}
+
+static void makeEiffelLocalTag(tokenInfo *const token) {
+ if (EiffelKinds[EKIND_LOCAL].enabled && Option.include.fileScope) {
+ const char *const name = vStringValue(token->string);
+ vString *scope = vStringNew();
+ tagEntryInfo e;
+
+ initTagEntry(&e, name);
+
+ e.isFileScope = TRUE;
+ e.kindName = EiffelKinds[EKIND_LOCAL].name;
+ e.kind = EiffelKinds[EKIND_LOCAL].letter;
+
+ vStringCopy(scope, token->className);
+ vStringPut(scope, '.');
+ vStringCat(scope, token->featureName);
+
+ e.extensionFields.scope[0] = EiffelKinds[EKIND_FEATURE].name;
+ e.extensionFields.scope[1] = vStringValue(scope);
+
+ makeTagEntry(&e);
+ vStringDelete(scope);
+ }
+}
+
+#endif
+
+/*
+ * Parsing functions
+ */
+
+static int skipToCharacter(const int c) {
+ int d;
+
+ do {
+ d = fileGetc();
+ } while (d != EOF && d != c);
+
+ return d;
+}
+
+/* If a numeric is passed in 'c', this is used as the first digit of the
+ * numeric being parsed.
+ */
+static vString *parseInteger(int c) {
+ vString *string = vStringNew();
+
+ if (c == '\0') c = fileGetc();
+ if (c == '-') {
+ vStringPut(string, c);
+ c = fileGetc();
+ } else if (!isdigit(c))
+ c = fileGetc();
+ while (c != EOF && (isdigit(c) || c == '_')) {
+ vStringPut(string, c);
+ c = fileGetc();
+ }
+ vStringTerminate(string);
+ fileUngetc(c);
+
+ return string;
+}
+
+static vString *parseNumeric(int c) {
+ vString *string = vStringNew();
+ vString *integer = parseInteger(c);
+ vStringCopy(string, integer);
+ vStringDelete(integer);
+
+ c = fileGetc();
+ if (c == '.') {
+ integer = parseInteger('\0');
+ vStringPut(string, c);
+ vStringCat(string, integer);
+ vStringDelete(integer);
+ c = fileGetc();
+ }
+ if (tolower(c) == 'e') {
+ integer = parseInteger('\0');
+ vStringPut(string, c);
+ vStringCat(string, integer);
+ vStringDelete(integer);
+ } else if (!isspace(c))
+ fileUngetc(c);
+
+ vStringTerminate(string);
+
+ return string;
+}
+
+static int parseEscapedCharacter(void) {
+ int d = '\0';
+ int c = fileGetc();
+
+ switch (c) {
+ case 'A':
+ d = '@';
+ break;
+ case 'B':
+ d = '\b';
+ break;
+ case 'C':
+ d = '^';
+ break;
+ case 'D':
+ d = '$';
+ break;
+ case 'F':
+ d = '\f';
+ break;
+ case 'H':
+ d = '\\';
+ break;
+ case 'L':
+ d = '~';
+ break;
+ case 'N':
+ d = '\n';
+ break;
+#ifdef QDOS
+ case 'Q':
+ d = 0x9F;
+ break;
+#else
+ case 'Q':
+ d = '`';
+ break;
+#endif
+ case 'R':
+ d = '\r';
+ break;
+ case 'S':
+ d = '#';
+ break;
+ case 'T':
+ d = '\t';
+ break;
+ case 'U':
+ d = '\0';
+ break;
+ case 'V':
+ d = '|';
+ break;
+ case '%':
+ d = '%';
+ break;
+ case '\'':
+ d = '\'';
+ break;
+ case '"':
+ d = '"';
+ break;
+ case '(':
+ d = '[';
+ break;
+ case ')':
+ d = ']';
+ break;
+ case '<':
+ d = '{';
+ break;
+ case '>':
+ d = '}';
+ break;
+
+ case '\n':
+ skipToCharacter('%');
+ break;
+
+ case '/': {
+ vString *string = parseInteger('\0');
+ const char *value = vStringValue(string);
+ const unsigned long ascii = atol(value);
+ vStringDelete(string);
+
+ c = fileGetc();
+ if (c == '/' && ascii < 256) d = ascii;
+ break;
+ }
+
+ default:
+ break;
+ }
+ return d;
+}
+
+static int parseCharacter(void) {
+ int c = fileGetc();
+ int result = c;
+
+ if (c == '%') result = parseEscapedCharacter();
+
+ c = fileGetc();
+ if (c != '\'') skipToCharacter('\n');
+
+ return result;
+}
+
+static void parseString(vString *const string) {
+ boolean verbatim = FALSE;
+ boolean align = FALSE;
+ boolean end = FALSE;
+ vString *verbatimCloser = vStringNew();
+ vString *lastLine = vStringNew();
+ int prev = '\0';
+ int c;
+
+ while (!end) {
+ c = fileGetc();
+ if (c == EOF)
+ end = TRUE;
+ else if (c == '"') {
+ if (!verbatim)
+ end = TRUE;
+ else
+ end = (boolean)(
+ strcmp(vStringValue(lastLine), vStringValue(verbatimCloser)) == 0);
+ } else if (c == '\n') {
+ if (verbatim) vStringClear(lastLine);
+ if (prev == '[' /* || prev == '{' */) {
+ verbatim = TRUE;
+ vStringClear(verbatimCloser);
+ vStringClear(lastLine);
+ if (prev == '{')
+ vStringPut(verbatimCloser, '}');
+ else {
+ vStringPut(verbatimCloser, ']');
+ align = TRUE;
+ }
+ vStringNCat(verbatimCloser, string, vStringLength(string) - 1);
+ vStringClear(string);
+ }
+ if (verbatim && align) {
+ do
+ c = fileGetc();
+ while (isspace(c));
+ }
+ } else if (c == '%')
+ c = parseEscapedCharacter();
+ if (!end) {
+ vStringPut(string, c);
+ if (verbatim) {
+ vStringPut(lastLine, c);
+ vStringTerminate(lastLine);
+ }
+ prev = c;
+ }
+ }
+ vStringTerminate(string);
+ vStringDelete(lastLine);
+ vStringDelete(verbatimCloser);
+}
+
+/* Read a C identifier beginning with "firstChar" and places it into "name".
+ */
+static void parseIdentifier(vString *const string, const int firstChar) {
+ int c = firstChar;
+
+ do {
+ vStringPut(string, c);
+ c = fileGetc();
+ } while (isident(c));
+
+ vStringTerminate(string);
+ if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */
+}
+
+static void parseFreeOperator(vString *const string, const int firstChar) {
+ int c = firstChar;
+
+ do {
+ vStringPut(string, c);
+ c = fileGetc();
+ } while (c > ' ');
+
+ vStringTerminate(string);
+ if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */
+}
+
+static void copyToken(tokenInfo *dst, const tokenInfo *src) {
+ dst->type = src->type;
+ dst->keyword = src->keyword;
+ dst->isExported = src->isExported;
+
+ vStringCopy(dst->string, src->string);
+ vStringCopy(dst->className, src->className);
+ vStringCopy(dst->featureName, src->featureName);
+}
+
+static tokenInfo *newToken(void) {
+ tokenInfo *const token = xMalloc(1, tokenInfo);
+
+ token->type = TOKEN_UNDEFINED;
+ token->keyword = KEYWORD_NONE;
+ token->isExported = TRUE;
+
+ token->string = vStringNew();
+ token->className = vStringNew();
+ token->featureName = vStringNew();
+
+ return token;
+}
+
+static void deleteToken(tokenInfo *const token) {
+ vStringDelete(token->string);
+ vStringDelete(token->className);
+ vStringDelete(token->featureName);
+
+ eFree(token);
+}
+
+static void readToken(tokenInfo *const token) {
+ int c;
+
+ token->type = TOKEN_UNDEFINED;
+ token->keyword = KEYWORD_NONE;
+ vStringClear(token->string);
+
+getNextChar:
+
+ do
+ c = fileGetc();
+ while (c == '\t' || c == ' ' || c == '\n');
+
+ switch (c) {
+ case EOF:
+ longjmp(Exception, (int)ExceptionEOF);
+ break;
+ case ';':
+ token->type = TOKEN_SEMICOLON;
+ break;
+ case '!':
+ token->type = TOKEN_BANG;
+ break;
+ case '}':
+ token->type = TOKEN_CLOSE_BRACE;
+ break;
+ case ']':
+ token->type = TOKEN_CLOSE_BRACKET;
+ break;
+ case ')':
+ token->type = TOKEN_CLOSE_PAREN;
+ break;
+ case ',':
+ token->type = TOKEN_COMMA;
+ break;
+ case '$':
+ token->type = TOKEN_DOLLAR;
+ break;
+ case '.':
+ token->type = TOKEN_DOT;
+ break;
+ case '{':
+ token->type = TOKEN_OPEN_BRACE;
+ break;
+ case '[':
+ token->type = TOKEN_OPEN_BRACKET;
+ break;
+ case '(':
+ token->type = TOKEN_OPEN_PAREN;
+ break;
+ case '~':
+ token->type = TOKEN_TILDE;
+ break;
+
+ case '+':
+ case '*':
+ case '^':
+ case '=':
+ token->type = TOKEN_OPERATOR;
+ break;
+
+ case '-':
+ c = fileGetc();
+ if (c == '>')
+ token->type = TOKEN_CONSTRAINT;
+ else if (c == '-') /* is this the start of a comment? */
+ {
+ skipToCharacter('\n');
+ goto getNextChar;
+ } else {
+ if (!isspace(c)) fileUngetc(c);
+ token->type = TOKEN_OPERATOR;
+ }
+ break;
+
+ case '?':
+ case ':': {
+ int c2 = fileGetc();
+ if (c2 == '=')
+ token->type = TOKEN_OPERATOR;
+ else {
+ if (!isspace(c2)) fileUngetc(c2);
+ if (c == ':')
+ token->type = TOKEN_COLON;
+ else
+ token->type = TOKEN_QUESTION;
+ }
+ break;
+ }
+
+ case '<':
+ c = fileGetc();
+ if (c != '=' && c != '>' && !isspace(c)) fileUngetc(c);
+ token->type = TOKEN_OPERATOR;
+ break;
+
+ case '>':
+ c = fileGetc();
+ if (c != '=' && c != '>' && !isspace(c)) fileUngetc(c);
+ token->type = TOKEN_OPERATOR;
+ break;
+
+ case '/':
+ c = fileGetc();
+ if (c != '/' && c != '=' && !isspace(c)) fileUngetc(c);
+ token->type = TOKEN_OPERATOR;
+ break;
+
+ case '\\':
+ c = fileGetc();
+ if (c != '\\' && !isspace(c)) fileUngetc(c);
+ token->type = TOKEN_OPERATOR;
+ break;
+
+ case '"':
+ token->type = TOKEN_STRING;
+ parseString(token->string);
+ break;
+
+ case '\'':
+ token->type = TOKEN_CHARACTER;
+ parseCharacter();
+ break;
+
+ default:
+ if (isalpha(c)) {
+ parseIdentifier(token->string, c);
+ token->keyword = analyzeToken(token->string, Lang_eiffel);
+ if (isKeyword(token, KEYWORD_NONE))
+ token->type = TOKEN_IDENTIFIER;
+ else
+ token->type = TOKEN_KEYWORD;
+ } else if (isdigit(c)) {
+ vString *numeric = parseNumeric(c);
+ vStringCat(token->string, numeric);
+ vStringDelete(numeric);
+ token->type = TOKEN_NUMERIC;
+ } else if (isFreeOperatorChar(c)) {
+ parseFreeOperator(token->string, c);
+ token->type = TOKEN_OPERATOR;
+ } else {
+ token->type = TOKEN_UNDEFINED;
+ Assert(!isType(token, TOKEN_UNDEFINED));
+ }
+ break;
+ }
+}
+
+/*
+ * Scanning functions
+ */
+
+static boolean isIdentifierMatch(const tokenInfo *const token,
+ const char *const name) {
+ return (boolean)(isType(token, TOKEN_IDENTIFIER) &&
+ strcasecmp(vStringValue(token->string), name) == 0);
+}
+
+static void findToken(tokenInfo *const token, const tokenType type) {
+ while (!isType(token, type)) readToken(token);
+}
+
+static void findKeyword(tokenInfo *const token, const keywordId keyword) {
+ while (!isKeyword(token, keyword)) readToken(token);
+}
+
+static boolean parseType(tokenInfo *const token);
+
+static void parseGeneric(tokenInfo *const token,
+ boolean declaration __unused__) {
+ unsigned int depth = 0;
+#ifdef TYPE_REFERENCE_TOOL
+ boolean constraint = FALSE;
+#endif
+ Assert(isType(token, TOKEN_OPEN_BRACKET));
+ do {
+ if (isType(token, TOKEN_OPEN_BRACKET)) {
+ ++depth;
+ readToken(token);
+ } else if (isType(token, TOKEN_CLOSE_BRACKET)) {
+ --depth;
+ readToken(token);
+ }
+#ifdef TYPE_REFERENCE_TOOL
+ else if (declaration) {
+ boolean advanced = FALSE;
+ if (depth == 1) {
+ if (isType(token, TOKEN_CONSTRAINT))
+ constraint = TRUE;
+ else if (isKeyword(token, KEYWORD_create))
+ findKeyword(token, KEYWORD_end);
+ else if (isType(token, TOKEN_IDENTIFIER)) {
+ if (constraint)
+ advanced = parseType(token);
+ else
+ addGenericName(token);
+ constraint = FALSE;
+ }
+ } else if (isType(token, TOKEN_IDENTIFIER))
+ advanced = parseType(token);
+ if (!advanced) readToken(token);
+ }
+#endif
+ else
+ parseType(token);
+ } while (depth > 0);
+}
+
+static boolean parseType(tokenInfo *const token) {
+ tokenInfo *const id = newToken();
+ copyToken(id, token);
+ readToken(token);
+ if (isType(token, TOKEN_COLON)) /* check for "{entity: TYPE}" */
+ {
+ readToken(id);
+ readToken(token);
+ }
+ if (isKeyword(id, KEYWORD_like)) {
+ if (isType(token, TOKEN_IDENTIFIER) || isKeyword(token, KEYWORD_Current))
+ readToken(token);
+ } else {
+ if (isKeyword(id, KEYWORD_attached) || isKeyword(id, KEYWORD_detachable) ||
+ isKeyword(id, KEYWORD_expanded)) {
+ copyToken(id, token);
+ readToken(token);
+ }
+ if (isType(id, TOKEN_IDENTIFIER)) {
+#ifdef TYPE_REFERENCE_TOOL
+ reportType(id);
+#endif
+ if (isType(token, TOKEN_OPEN_BRACKET))
+ parseGeneric(token, FALSE);
+ else if ((strcmp("BIT", vStringValue(id->string)) == 0))
+ readToken(token); /* read token after number of bits */
+ }
+ }
+ deleteToken(id);
+ return TRUE;
+}
+
+static void parseEntityType(tokenInfo *const token) {
+ Assert(isType(token, TOKEN_COLON));
+ readToken(token);
+
+ if (isType(token, TOKEN_BANG) || isType(token, TOKEN_QUESTION))
+ readToken(token); /* skip over '!' or '?' */
+ parseType(token);
+}
+
+static void parseLocal(tokenInfo *const token) {
+ Assert(isKeyword(token, KEYWORD_local));
+ readToken(token);
+
+ /* Check keyword first in case local clause is empty
+ */
+ while (!isKeyword(token, KEYWORD_do) && !isKeyword(token, KEYWORD_once)) {
+#ifndef TYPE_REFERENCE_TOOL
+ if (isType(token, TOKEN_IDENTIFIER)) makeEiffelLocalTag(token);
+#endif
+ readToken(token);
+ if (isType(token, TOKEN_COLON)) parseEntityType(token);
+ }
+}
+
+static void findFeatureEnd(tokenInfo *const token) {
+ boolean isFound = isKeyword(token, KEYWORD_is);
+ if (isFound) readToken(token);
+ switch (token->keyword) {
+ case KEYWORD_deferred:
+ case KEYWORD_do:
+ case KEYWORD_external:
+ case KEYWORD_local:
+ case KEYWORD_obsolete:
+ case KEYWORD_once:
+ case KEYWORD_require: {
+ int depth = 1;
+
+ while (depth > 0) {
+#ifdef TYPE_REFERENCE_TOOL
+ if (isType(token, TOKEN_OPEN_BRACE)) {
+ readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER)) parseType(token);
+ } else if (isType(token, TOKEN_BANG)) {
+ readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER)) parseType(token);
+ if (isType(token, TOKEN_BANG)) readToken(token);
+ } else
+#endif
+ switch (token->keyword) {
+ case KEYWORD_check:
+ case KEYWORD_debug:
+ case KEYWORD_from:
+ case KEYWORD_if:
+ case KEYWORD_inspect:
+ ++depth;
+ break;
+
+ case KEYWORD_local:
+ parseLocal(token);
+ break;
+
+ case KEYWORD_end:
+ --depth;
+ break;
+
+ default:
+ break;
+ }
+ readToken(token);
+ }
+ break;
+ }
+
+ default:
+ /* is this a manifest constant? */
+ if (isFound || isType(token, TOKEN_OPERATOR)) {
+ if (isType(token, TOKEN_OPERATOR)) readToken(token);
+ readToken(token);
+ }
+ break;
+ }
+}
+
+static boolean readFeatureName(tokenInfo *const token) {
+ boolean isFeatureName = FALSE;
+
+ if (isKeyword(token, KEYWORD_frozen)) readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER))
+ isFeatureName = TRUE;
+ else if (isKeyword(token, KEYWORD_assign)) /* legacy code */
+ isFeatureName = TRUE;
+ else if (isKeyword(token, KEYWORD_infix) ||
+ isKeyword(token, KEYWORD_prefix)) {
+ readToken(token);
+ if (isType(token, TOKEN_STRING)) isFeatureName = TRUE;
+ }
+ return isFeatureName;
+}
+
+static void parseArguments(tokenInfo *const token) {
+#ifndef TYPE_REFERENCE_TOOL
+ findToken(token, TOKEN_CLOSE_PAREN);
+ readToken(token);
+#else
+ Assert(isType(token, TOKEN_OPEN_PAREN));
+ readToken(token);
+ do {
+ if (isType(token, TOKEN_COLON))
+ parseEntityType(token);
+ else
+ readToken(token);
+ } while (!isType(token, TOKEN_CLOSE_PAREN));
+ readToken(token);
+#endif
+}
+
+static boolean parseFeature(tokenInfo *const token) {
+ boolean found = FALSE;
+ while (readFeatureName(token)) {
+ found = TRUE;
+#ifndef TYPE_REFERENCE_TOOL
+ makeEiffelFeatureTag(token);
+#endif
+ readToken(token);
+ if (isType(token, TOKEN_COMMA)) readToken(token);
+ }
+ if (found) {
+ if (isKeyword(token, KEYWORD_alias)) {
+ readToken(token);
+#ifndef TYPE_REFERENCE_TOOL
+ if (isType(token, TOKEN_STRING)) makeEiffelFeatureTag(token);
+#endif
+ readToken(token);
+ }
+ if (isType(token, TOKEN_OPEN_PAREN)) /* arguments? */
+ parseArguments(token);
+ if (isType(token, TOKEN_COLON)) /* a query? */
+ parseEntityType(token);
+ if (isKeyword(token, KEYWORD_assign)) {
+ readToken(token);
+ readToken(token);
+ }
+ if (isKeyword(token, KEYWORD_obsolete)) {
+ readToken(token);
+ if (isType(token, TOKEN_STRING)) readToken(token);
+ }
+ findFeatureEnd(token);
+ }
+ return found;
+}
+
+static void parseExport(tokenInfo *const token) {
+ token->isExported = TRUE;
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_BRACE)) {
+ token->isExported = FALSE;
+ while (!isType(token, TOKEN_CLOSE_BRACE)) {
+ if (isType(token, TOKEN_IDENTIFIER))
+ token->isExported |= !isIdentifierMatch(token, "NONE");
+ readToken(token);
+ }
+ readToken(token);
+ }
+}
+
+static void parseFeatureClauses(tokenInfo *const token) {
+ Assert(isKeyword(token, KEYWORD_feature));
+ do {
+ if (isKeyword(token, KEYWORD_feature)) parseExport(token);
+ if (!isKeyword(token, KEYWORD_feature) &&
+ !isKeyword(token, KEYWORD_invariant) &&
+ !isKeyword(token, KEYWORD_indexing)) {
+ if (!parseFeature(token)) readToken(token);
+ }
+ } while (!isKeyword(token, KEYWORD_end) &&
+ !isKeyword(token, KEYWORD_invariant) &&
+ !isKeyword(token, KEYWORD_indexing));
+}
+
+static void parseRename(tokenInfo *const token) {
+ Assert(isKeyword(token, KEYWORD_rename));
+ do {
+ readToken(token);
+ if (readFeatureName(token)) {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_as)) {
+ readToken(token);
+ if (readFeatureName(token)) {
+#ifndef TYPE_REFERENCE_TOOL
+ makeEiffelFeatureTag(token); /* renamed feature */
+#endif
+ readToken(token);
+ }
+ }
+ }
+ } while (isType(token, TOKEN_COMMA));
+}
+
+static void parseInherit(tokenInfo *const token) {
+ Assert(isKeyword(token, KEYWORD_inherit));
+ readToken(token);
+ while (isType(token, TOKEN_IDENTIFIER)) {
+ parseType(token);
+ if (isType(token, TOKEN_KEYWORD)) {
+ switch (token->keyword) /* check for feature adaptation */
+ {
+ case KEYWORD_rename:
+ parseRename(token);
+ case KEYWORD_export:
+ case KEYWORD_undefine:
+ case KEYWORD_redefine:
+ case KEYWORD_select:
+ findKeyword(token, KEYWORD_end);
+ readToken(token);
+ break;
+
+ case KEYWORD_end:
+ readToken(token);
+ break;
+
+ default:
+ break;
+ }
+ }
+ if (isType(token, TOKEN_SEMICOLON)) readToken(token);
+ }
+}
+
+static void parseConvert(tokenInfo *const token) {
+ Assert(isKeyword(token, KEYWORD_convert));
+ do {
+ readToken(token);
+ if (!isType(token, TOKEN_IDENTIFIER))
+ break;
+ else if (isType(token, TOKEN_OPEN_PAREN)) {
+ while (!isType(token, TOKEN_CLOSE_PAREN)) readToken(token);
+ } else if (isType(token, TOKEN_COLON)) {
+ readToken(token);
+ if (!isType(token, TOKEN_OPEN_BRACE))
+ break;
+ else
+ while (!isType(token, TOKEN_CLOSE_BRACE)) readToken(token);
+ }
+ } while (isType(token, TOKEN_COMMA));
+}
+
+static void parseClass(tokenInfo *const token) {
+ Assert(isKeyword(token, KEYWORD_class));
+ readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER)) {
+#ifndef TYPE_REFERENCE_TOOL
+ makeEiffelClassTag(token);
+ readToken(token);
+#else
+ vStringCopy(token->className, token->string);
+ vStringUpper(token->className);
+ if (PrintClass) puts(vStringValue(token->className));
+ if (!PrintReferences) exit(0);
+ readToken(token);
+#endif
+ }
+
+ do {
+ if (isType(token, TOKEN_OPEN_BRACKET))
+ parseGeneric(token, TRUE);
+ else if (!isType(token, TOKEN_KEYWORD))
+ readToken(token);
+ else
+ switch (token->keyword) {
+ case KEYWORD_inherit:
+ parseInherit(token);
+ break;
+ case KEYWORD_feature:
+ parseFeatureClauses(token);
+ break;
+ case KEYWORD_convert:
+ parseConvert(token);
+ break;
+ default:
+ readToken(token);
+ break;
+ }
+ } while (!isKeyword(token, KEYWORD_end));
+}
+
+static void initialize(const langType language) {
+ Lang_eiffel = language;
+ buildEiffelKeywordHash();
+}
+
+static void findEiffelTags(void) {
+ tokenInfo *const token = newToken();
+ exception_t exception;
+
+ exception = (exception_t)(setjmp(Exception));
+ while (exception == ExceptionNone) {
+ findKeyword(token, KEYWORD_class);
+ parseClass(token);
+ }
+ deleteToken(token);
+}
+
+#ifndef TYPE_REFERENCE_TOOL
+
+extern parserDefinition *EiffelParser(void) {
+ static const char *const extensions[] = {"e", NULL};
+ parserDefinition *def = parserNew("Eiffel");
+ def->kinds = EiffelKinds;
+ def->kindCount = KIND_COUNT(EiffelKinds);
+ def->extensions = extensions;
+ def->parser = findEiffelTags;
+ def->initialize = initialize;
+ return def;
+}
+
+#else
+
+static void findReferences(void) {
+ ReferencedTypes = stringListNew();
+ GenericNames = stringListNew();
+ initialize(0);
+
+ findEiffelTags();
+
+ stringListDelete(GenericNames);
+ GenericNames = NULL;
+ stringListDelete(ReferencedTypes);
+ ReferencedTypes = NULL;
+}
+
+static const char *const Usage =
+ "Prints names of types referenced by an Eiffel language file.\n"
+ "\n"
+ "Usage: %s [-cdrs] [file_name | -]\n"
+ "\n"
+ "Options:\n"
+ " -c Print class name of current file (on first line of output).\n"
+ " -d Enable debug output.\n"
+ " -r Print types referenced by current file (default unless -c).\n"
+ " -s Include self-references.\n"
+ "\n";
+
+extern int main(int argc, char **argv) {
+ int i;
+ for (i = 1; argv[i] != NULL; ++i) {
+ const char *const arg = argv[i];
+ if (arg[0] == '-') {
+ int j;
+ if (arg[1] == '\0') {
+ File = stdin;
+ FileName = "stdin";
+ } else
+ for (j = 1; arg[j] != '\0'; ++j) switch (arg[j]) {
+ case 'c':
+ PrintClass = 1;
+ break;
+ case 'r':
+ PrintReferences = 1;
+ break;
+ case 's':
+ SelfReferences = 1;
+ break;
+ case 'd':
+ Debug = 1;
+ break;
+ default:
+ fprintf(errout, "%s: unknown option: %c\n", argv[0], arg[1]);
+ fprintf(errout, Usage, argv[0]);
+ exit(1);
+ break;
+ }
+ } else if (File != NULL) {
+ fprintf(errout, Usage, argv[0]);
+ exit(1);
+ } else {
+ FileName = arg;
+ File = fopen(FileName, "r");
+ if (File == NULL) {
+ perror(argv[0]);
+ exit(1);
+ }
+ }
+ }
+ if (!PrintClass) PrintReferences = 1;
+ if (File == NULL) {
+ fprintf(errout, Usage, argv[0]);
+ exit(1);
+ } else {
+ findReferences();
+ fclose(File);
+ }
+ return 0;
+}
+
+#endif
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/entry.c b/third_party/ctags/entry.c
new file mode 100644
index 000000000..4f2d9318c
--- /dev/null
+++ b/third_party/ctags/entry.c
@@ -0,0 +1,736 @@
+/*
+ * $Id: entry.c 766 2010-09-11 18:59:45Z dhiebert $
+ *
+ * Copyright (c) 1996-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for creating tag entries.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/calls/calls.h"
+#include "libc/calls/weirdtypes.h"
+#include "libc/errno.h"
+#include "libc/fmt/fmt.h"
+#include "libc/runtime/runtime.h"
+#include "libc/str/str.h"
+#include "third_party/ctags/ctags.h"
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/main.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/sort.h"
+#include "third_party/ctags/strlist.h"
+
+/*
+ * MACROS
+ */
+#define PSEUDO_TAG_PREFIX "!_"
+
+#define includeExtensionFlags() (Option.tagFileFormat > 1)
+
+/*
+ * Portability defines
+ */
+#if !defined(HAVE_TRUNCATE) && !defined(HAVE_FTRUNCATE) && !defined(HAVE_CHSIZE)
+#define USE_REPLACEMENT_TRUNCATE
+#endif
+
+/* Hack for rediculous practice of Microsoft Visual C++.
+ */
+#if defined(WIN32) && defined(_MSC_VER)
+#define chsize _chsize
+#define open _open
+#define close _close
+#define O_RDWR _O_RDWR
+#endif
+
+/*
+ * DATA DEFINITIONS
+ */
+
+tagFile TagFile = {
+ NULL, /* tag file name */
+ NULL, /* tag file directory (absolute) */
+ NULL, /* file pointer */
+ {0, 0}, /* numTags */
+ {0, 0, 0}, /* max */
+ {NULL, NULL, 0}, /* etags */
+ NULL /* vLine */
+};
+
+static boolean TagsToStdout = FALSE;
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+#ifdef NEED_PROTO_TRUNCATE
+extern int truncate(const char *path, off_t length);
+#endif
+
+#ifdef NEED_PROTO_FTRUNCATE
+extern int ftruncate(int fd, off_t length);
+#endif
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+extern void freeTagFileResources(void) {
+ if (TagFile.directory != NULL) eFree(TagFile.directory);
+ vStringDelete(TagFile.vLine);
+}
+
+extern const char *tagFileName(void) {
+ return TagFile.name;
+}
+
+/*
+ * Pseudo tag support
+ */
+
+static void rememberMaxLengths(const size_t nameLength,
+ const size_t lineLength) {
+ if (nameLength > TagFile.max.tag) TagFile.max.tag = nameLength;
+
+ if (lineLength > TagFile.max.line) TagFile.max.line = lineLength;
+}
+
+static void writePseudoTag(const char *const tagName,
+ const char *const fileName,
+ const char *const pattern) {
+ const int length = fprintf(TagFile.fp, "%s%s\t%s\t/%s/\n", PSEUDO_TAG_PREFIX,
+ tagName, fileName, pattern);
+ ++TagFile.numTags.added;
+ rememberMaxLengths(strlen(tagName), (size_t)length);
+}
+
+static void addPseudoTags(void) {
+ if (!Option.xref) {
+ char format[11];
+ const char *formatComment = "unknown format";
+
+ sprintf(format, "%u", Option.tagFileFormat);
+
+ if (Option.tagFileFormat == 1)
+ formatComment = "original ctags format";
+ else if (Option.tagFileFormat == 2)
+ formatComment =
+ "extended format; --format=1 will not append ;\" to lines";
+
+ writePseudoTag("TAG_FILE_FORMAT", format, formatComment);
+ writePseudoTag("TAG_FILE_SORTED",
+ Option.sorted == SO_FOLDSORTED
+ ? "2"
+ : (Option.sorted == SO_SORTED ? "1" : "0"),
+ "0=unsorted, 1=sorted, 2=foldcase");
+ writePseudoTag("TAG_PROGRAM_AUTHOR", AUTHOR_NAME, AUTHOR_EMAIL);
+ writePseudoTag("TAG_PROGRAM_NAME", PROGRAM_NAME, "");
+ writePseudoTag("TAG_PROGRAM_URL", PROGRAM_URL, "official site");
+ writePseudoTag("TAG_PROGRAM_VERSION", PROGRAM_VERSION, "");
+ }
+}
+
+static void updateSortedFlag(const char *const line, FILE *const fp,
+ fpos_t startOfLine) {
+ const char *const tab = strchr(line, '\t');
+
+ if (tab != NULL) {
+ const long boolOffset = tab - line + 1; /* where it should be */
+
+ if (line[boolOffset] == '0' || line[boolOffset] == '1') {
+ fpos_t nextLine;
+
+ if (fgetpos(fp, &nextLine) == -1 || fsetpos(fp, &startOfLine) == -1)
+ error(WARNING, "Failed to update 'sorted' pseudo-tag");
+ else {
+ fpos_t flagLocation;
+ int c, d;
+ do
+ c = fgetc(fp);
+ while (c != '\t' && c != '\n');
+ fgetpos(fp, &flagLocation);
+ d = fgetc(fp);
+ if (c == '\t' && (d == '0' || d == '1') && d != (int)Option.sorted) {
+ fsetpos(fp, &flagLocation);
+ fputc(Option.sorted == SO_FOLDSORTED
+ ? '2'
+ : (Option.sorted == SO_SORTED ? '1' : '0'),
+ fp);
+ }
+ fsetpos(fp, &nextLine);
+ }
+ }
+ }
+}
+
+/* Look through all line beginning with "!_TAG_FILE", and update those which
+ * require it.
+ */
+static long unsigned int updatePseudoTags(FILE *const fp) {
+ enum { maxEntryLength = 20 };
+ char entry[maxEntryLength + 1];
+ unsigned long linesRead = 0;
+ fpos_t startOfLine;
+ size_t entryLength;
+ const char *line;
+
+ sprintf(entry, "%sTAG_FILE", PSEUDO_TAG_PREFIX);
+ entryLength = strlen(entry);
+ Assert(entryLength < maxEntryLength);
+
+ fgetpos(fp, &startOfLine);
+ line = readLine(TagFile.vLine, fp);
+ while (line != NULL && line[0] == entry[0]) {
+ ++linesRead;
+ if (strncmp(line, entry, entryLength) == 0) {
+ char tab, classType[16];
+
+ if (sscanf(line + entryLength, "%15s%c", classType, &tab) == 2 &&
+ tab == '\t') {
+ if (strcmp(classType, "_SORTED") == 0)
+ updateSortedFlag(line, fp, startOfLine);
+ }
+ fgetpos(fp, &startOfLine);
+ }
+ line = readLine(TagFile.vLine, fp);
+ }
+ while (line != NULL) /* skip to end of file */
+ {
+ ++linesRead;
+ line = readLine(TagFile.vLine, fp);
+ }
+ return linesRead;
+}
+
+/*
+ * Tag file management
+ */
+
+static boolean isValidTagAddress(const char *const excmd) {
+ boolean isValid = FALSE;
+
+ if (strchr("/?", excmd[0]) != NULL)
+ isValid = TRUE;
+ else {
+ char *address = xMalloc(strlen(excmd) + 1, char);
+ if (sscanf(excmd, "%[^;\n]", address) == 1 &&
+ strspn(address, "0123456789") == strlen(address))
+ isValid = TRUE;
+ eFree(address);
+ }
+ return isValid;
+}
+
+static boolean isCtagsLine(const char *const line) {
+ enum fieldList { TAG, TAB1, SRC_FILE, TAB2, EXCMD, NUM_FIELDS };
+ boolean ok = FALSE; /* we assume not unless confirmed */
+ const size_t fieldLength = strlen(line) + 1;
+ char *const fields = xMalloc(NUM_FIELDS * fieldLength, char);
+
+ if (fields == NULL)
+ error(FATAL, "Cannot analyze tag file");
+ else {
+#define field(x) (fields + ((size_t)(x)*fieldLength))
+
+ const int numFields =
+ sscanf(line, "%[^\t]%[\t]%[^\t]%[\t]%[^\r\n]", field(TAG), field(TAB1),
+ field(SRC_FILE), field(TAB2), field(EXCMD));
+
+ /* There must be exactly five fields: two tab fields containing
+ * exactly one tab each, the tag must not begin with "#", and the
+ * file name should not end with ";", and the excmd must be
+ * accceptable.
+ *
+ * These conditions will reject tag-looking lines like:
+ * int a;
+ * #define LABEL
+ */
+ if (numFields == NUM_FIELDS && strlen(field(TAB1)) == 1 &&
+ strlen(field(TAB2)) == 1 && field(TAG)[0] != '#' &&
+ field(SRC_FILE)[strlen(field(SRC_FILE)) - 1] != ';' &&
+ isValidTagAddress(field(EXCMD)))
+ ok = TRUE;
+
+ eFree(fields);
+ }
+ return ok;
+}
+
+static boolean isEtagsLine(const char *const line) {
+ boolean result = FALSE;
+ if (line[0] == '\f') result = (boolean)(line[1] == '\n' || line[1] == '\r');
+ return result;
+}
+
+static boolean isTagFile(const char *const filename) {
+ boolean ok = FALSE; /* we assume not unless confirmed */
+ FILE *const fp = fopen(filename, "rb");
+
+ if (fp == NULL && errno == ENOENT)
+ ok = TRUE;
+ else if (fp != NULL) {
+ const char *line = readLine(TagFile.vLine, fp);
+
+ if (line == NULL)
+ ok = TRUE;
+ else
+ ok = (boolean)(isCtagsLine(line) || isEtagsLine(line));
+ fclose(fp);
+ }
+ return ok;
+}
+
+extern void copyBytes(FILE *const fromFp, FILE *const toFp, const long size) {
+ enum { BufferSize = 1000 };
+ long toRead, numRead;
+ char *buffer = xMalloc(BufferSize, char);
+ long remaining = size;
+ do {
+ toRead = (0 < remaining && remaining < BufferSize) ? remaining
+ : (long)BufferSize;
+ numRead = fread(buffer, (size_t)1, (size_t)toRead, fromFp);
+ if (fwrite(buffer, (size_t)1, (size_t)numRead, toFp) < (size_t)numRead)
+ error(FATAL | PERROR, "cannot complete write");
+ if (remaining > 0) remaining -= numRead;
+ } while (numRead == toRead && remaining != 0);
+ eFree(buffer);
+}
+
+extern void copyFile(const char *const from, const char *const to,
+ const long size) {
+ FILE *const fromFp = fopen(from, "rb");
+ if (fromFp == NULL)
+ error(FATAL | PERROR, "cannot open file to copy");
+ else {
+ FILE *const toFp = fopen(to, "wb");
+ if (toFp == NULL)
+ error(FATAL | PERROR, "cannot open copy destination");
+ else {
+ copyBytes(fromFp, toFp, size);
+ fclose(toFp);
+ }
+ fclose(fromFp);
+ }
+}
+
+extern void openTagFile(void) {
+ setDefaultTagFileName();
+ TagsToStdout = isDestinationStdout();
+
+ if (TagFile.vLine == NULL) TagFile.vLine = vStringNew();
+
+ /* Open the tags file.
+ */
+ if (TagsToStdout)
+ TagFile.fp = tempFile("w", &TagFile.name);
+ else {
+ boolean fileExists;
+
+ setDefaultTagFileName();
+ TagFile.name = eStrdup(Option.tagFileName);
+ fileExists = doesFileExist(TagFile.name);
+ if (fileExists && !isTagFile(TagFile.name))
+ error(FATAL,
+ "\"%s\" doesn't look like a tag file; I refuse to overwrite it.",
+ TagFile.name);
+
+ if (Option.etags) {
+ if (Option.append && fileExists)
+ TagFile.fp = fopen(TagFile.name, "a+b");
+ else
+ TagFile.fp = fopen(TagFile.name, "w+b");
+ } else {
+ if (Option.append && fileExists) {
+ TagFile.fp = fopen(TagFile.name, "r+");
+ if (TagFile.fp != NULL) {
+ TagFile.numTags.prev = updatePseudoTags(TagFile.fp);
+ fclose(TagFile.fp);
+ TagFile.fp = fopen(TagFile.name, "a+");
+ }
+ } else {
+ TagFile.fp = fopen(TagFile.name, "w");
+ if (TagFile.fp != NULL) addPseudoTags();
+ }
+ }
+ if (TagFile.fp == NULL) {
+ error(FATAL | PERROR, "cannot open tag file");
+ exit(1);
+ }
+ }
+ if (TagsToStdout)
+ TagFile.directory = eStrdup(CurrentDirectory);
+ else
+ TagFile.directory = absoluteDirname(TagFile.name);
+}
+
+#ifdef USE_REPLACEMENT_TRUNCATE
+
+/* Replacement for missing library function.
+ */
+static int replacementTruncate(const char *const name, const long size) {
+ char *tempName = NULL;
+ FILE *fp = tempFile("w", &tempName);
+ fclose(fp);
+ copyFile(name, tempName, size);
+ copyFile(tempName, name, WHOLE_FILE);
+ remove(tempName);
+ eFree(tempName);
+
+ return 0;
+}
+
+#endif
+
+static void sortTagFile(void) {
+ if (TagFile.numTags.added > 0L) {
+ if (Option.sorted != SO_UNSORTED) {
+ verbose("sorting tag file\n");
+#ifdef EXTERNAL_SORT
+ externalSortTags(TagsToStdout);
+#else
+ internalSortTags(TagsToStdout);
+#endif
+ } else if (TagsToStdout)
+ catFile(tagFileName());
+ }
+ if (TagsToStdout) remove(tagFileName()); /* remove temporary file */
+}
+
+static void resizeTagFile(const long newSize) {
+ int result;
+
+#ifdef USE_REPLACEMENT_TRUNCATE
+ result = replacementTruncate(TagFile.name, newSize);
+#else
+#ifdef HAVE_TRUNCATE
+ result = truncate(TagFile.name, (off_t)newSize);
+#else
+ const int fd = open(TagFile.name, O_RDWR);
+
+ if (fd == -1)
+ result = -1;
+ else {
+#ifdef HAVE_FTRUNCATE
+ result = ftruncate(fd, (off_t)newSize);
+#else
+#ifdef HAVE_CHSIZE
+ result = chsize(fd, newSize);
+#endif
+#endif
+ close(fd);
+ }
+#endif
+#endif
+ if (result == -1)
+ fprintf(errout, "Cannot shorten tag file: errno = %d\n", errno);
+}
+
+static void writeEtagsIncludes(FILE *const fp) {
+ if (Option.etagsInclude) {
+ unsigned int i;
+ for (i = 0; i < stringListCount(Option.etagsInclude); ++i) {
+ vString *item = stringListItem(Option.etagsInclude, i);
+ fprintf(fp, "\f\n%s,include\n", vStringValue(item));
+ }
+ }
+}
+
+extern void closeTagFile(const boolean resize) {
+ long desiredSize, size;
+
+ if (Option.etags) writeEtagsIncludes(TagFile.fp);
+ desiredSize = ftell(TagFile.fp);
+ fseek(TagFile.fp, 0L, SEEK_END);
+ size = ftell(TagFile.fp);
+ fclose(TagFile.fp);
+ if (resize && desiredSize < size) {
+ DebugStatement(debugPrintf(DEBUG_STATUS,
+ "shrinking %s from %ld to %ld bytes\n",
+ TagFile.name, size, desiredSize);)
+ resizeTagFile(desiredSize);
+ }
+ sortTagFile();
+ eFree(TagFile.name);
+ TagFile.name = NULL;
+}
+
+extern void beginEtagsFile(void) {
+ TagFile.etags.fp = tempFile("w+b", &TagFile.etags.name);
+ TagFile.etags.byteCount = 0;
+}
+
+extern void endEtagsFile(const char *const name) {
+ const char *line;
+
+ fprintf(TagFile.fp, "\f\n%s,%ld\n", name, (long)TagFile.etags.byteCount);
+ if (TagFile.etags.fp != NULL) {
+ rewind(TagFile.etags.fp);
+ while ((line = readLine(TagFile.vLine, TagFile.etags.fp)) != NULL)
+ fputs(line, TagFile.fp);
+ fclose(TagFile.etags.fp);
+ remove(TagFile.etags.name);
+ eFree(TagFile.etags.name);
+ TagFile.etags.fp = NULL;
+ TagFile.etags.name = NULL;
+ }
+}
+
+/*
+ * Tag entry management
+ */
+
+/* This function copies the current line out to a specified file. It has no
+ * effect on the fileGetc () function. During copying, any '\' characters
+ * are doubled and a leading '^' or trailing '$' is also quoted. End of line
+ * characters (line feed or carriage return) are dropped.
+ */
+static size_t writeSourceLine(FILE *const fp, const char *const line) {
+ size_t length = 0;
+ const char *p;
+
+ /* Write everything up to, but not including, a line end character.
+ */
+ for (p = line; *p != '\0'; ++p) {
+ const int next = *(p + 1);
+ const int c = *p;
+
+ if (c == CRETURN || c == NEWLINE) break;
+
+ /* If character is '\', or a terminal '$', then quote it.
+ */
+ if (c == BACKSLASH || c == (Option.backward ? '?' : '/') ||
+ (c == '$' && (next == NEWLINE || next == CRETURN))) {
+ putc(BACKSLASH, fp);
+ ++length;
+ }
+ putc(c, fp);
+ ++length;
+ }
+ return length;
+}
+
+/* Writes "line", stripping leading and duplicate white space.
+ */
+static size_t writeCompactSourceLine(FILE *const fp, const char *const line) {
+ boolean lineStarted = FALSE;
+ size_t length = 0;
+ const char *p;
+ int c;
+
+ /* Write everything up to, but not including, the newline.
+ */
+ for (p = line, c = *p; c != NEWLINE && c != '\0'; c = *++p) {
+ if (lineStarted || !isspace(c)) /* ignore leading spaces */
+ {
+ lineStarted = TRUE;
+ if (isspace(c)) {
+ int next;
+
+ /* Consume repeating white space.
+ */
+ while (next = *(p + 1), isspace(next) && next != NEWLINE) ++p;
+ c = ' '; /* force space character for any white space */
+ }
+ if (c != CRETURN || *(p + 1) != NEWLINE) {
+ putc(c, fp);
+ ++length;
+ }
+ }
+ }
+ return length;
+}
+
+static int writeXrefEntry(const tagEntryInfo *const tag) {
+ const char *const line =
+ readSourceLine(TagFile.vLine, tag->filePosition, NULL);
+ int length;
+
+ if (Option.tagFileFormat == 1)
+ length = fprintf(TagFile.fp, "%-16s %4lu %-16s ", tag->name,
+ tag->lineNumber, tag->sourceFileName);
+ else
+ length = fprintf(TagFile.fp, "%-16s %-10s %4lu %-16s ", tag->name,
+ tag->kindName, tag->lineNumber, tag->sourceFileName);
+
+ length += writeCompactSourceLine(TagFile.fp, line);
+ putc(NEWLINE, TagFile.fp);
+ ++length;
+
+ return length;
+}
+
+/* Truncates the text line containing the tag at the character following the
+ * tag, providing a character which designates the end of the tag.
+ */
+static void truncateTagLine(char *const line, const char *const token,
+ const boolean discardNewline) {
+ char *p = strstr(line, token);
+
+ if (p != NULL) {
+ p += strlen(token);
+ if (*p != '\0' && !(*p == '\n' && discardNewline))
+ ++p; /* skip past character terminating character */
+ *p = '\0';
+ }
+}
+
+static int writeEtagsEntry(const tagEntryInfo *const tag) {
+ int length;
+
+ if (tag->isFileEntry)
+ length = fprintf(TagFile.etags.fp, "\177%s\001%lu,0\n", tag->name,
+ tag->lineNumber);
+ else {
+ long seekValue;
+ char *const line =
+ readSourceLine(TagFile.vLine, tag->filePosition, &seekValue);
+
+ if (tag->truncateLine)
+ truncateTagLine(line, tag->name, TRUE);
+ else
+ line[strlen(line) - 1] = '\0';
+
+ length = fprintf(TagFile.etags.fp, "%s\177%s\001%lu,%ld\n", line, tag->name,
+ tag->lineNumber, seekValue);
+ }
+ TagFile.etags.byteCount += length;
+
+ return length;
+}
+
+static int addExtensionFields(const tagEntryInfo *const tag) {
+ const char *const kindKey = Option.extensionFields.kindKey ? "kind:" : "";
+ boolean first = TRUE;
+ const char *separator = ";\"";
+ const char *const empty = "";
+ int length = 0;
+/* "sep" returns a value only the first time it is evaluated */
+#define sep (first ? (first = FALSE, separator) : empty)
+
+ if (tag->kindName != NULL &&
+ (Option.extensionFields.kindLong ||
+ (Option.extensionFields.kind && tag->kind == '\0')))
+ length += fprintf(TagFile.fp, "%s\t%s%s", sep, kindKey, tag->kindName);
+ else if (tag->kind != '\0' &&
+ (Option.extensionFields.kind ||
+ (Option.extensionFields.kindLong && tag->kindName == NULL)))
+ length += fprintf(TagFile.fp, "%s\t%s%c", sep, kindKey, tag->kind);
+
+ if (Option.extensionFields.lineNumber)
+ length += fprintf(TagFile.fp, "%s\tline:%ld", sep, tag->lineNumber);
+
+ if (Option.extensionFields.language && tag->language != NULL)
+ length += fprintf(TagFile.fp, "%s\tlanguage:%s", sep, tag->language);
+
+ if (Option.extensionFields.scope && tag->extensionFields.scope[0] != NULL &&
+ tag->extensionFields.scope[1] != NULL)
+ length +=
+ fprintf(TagFile.fp, "%s\t%s:%s", sep, tag->extensionFields.scope[0],
+ tag->extensionFields.scope[1]);
+
+ if (Option.extensionFields.typeRef &&
+ tag->extensionFields.typeRef[0] != NULL &&
+ tag->extensionFields.typeRef[1] != NULL)
+ length += fprintf(TagFile.fp, "%s\ttyperef:%s:%s", sep,
+ tag->extensionFields.typeRef[0],
+ tag->extensionFields.typeRef[1]);
+
+ if (Option.extensionFields.fileScope && tag->isFileScope)
+ length += fprintf(TagFile.fp, "%s\tfile:", sep);
+
+ if (Option.extensionFields.inheritance &&
+ tag->extensionFields.inheritance != NULL)
+ length += fprintf(TagFile.fp, "%s\tinherits:%s", sep,
+ tag->extensionFields.inheritance);
+
+ if (Option.extensionFields.access && tag->extensionFields.access != NULL)
+ length +=
+ fprintf(TagFile.fp, "%s\taccess:%s", sep, tag->extensionFields.access);
+
+ if (Option.extensionFields.implementation &&
+ tag->extensionFields.implementation != NULL)
+ length += fprintf(TagFile.fp, "%s\timplementation:%s", sep,
+ tag->extensionFields.implementation);
+
+ if (Option.extensionFields.signature &&
+ tag->extensionFields.signature != NULL)
+ length += fprintf(TagFile.fp, "%s\tsignature:%s", sep,
+ tag->extensionFields.signature);
+
+ return length;
+#undef sep
+}
+
+static int writePatternEntry(const tagEntryInfo *const tag) {
+ char *const line = readSourceLine(TagFile.vLine, tag->filePosition, NULL);
+ const int searchChar = Option.backward ? '?' : '/';
+ boolean newlineTerminated;
+ int length = 0;
+
+ if (line == NULL) error(FATAL, "bad tag in %s", vStringValue(File.name));
+ if (tag->truncateLine) truncateTagLine(line, tag->name, FALSE);
+ newlineTerminated = (boolean)(line[strlen(line) - 1] == '\n');
+
+ length += fprintf(TagFile.fp, "%c^", searchChar);
+ length += writeSourceLine(TagFile.fp, line);
+ length +=
+ fprintf(TagFile.fp, "%s%c", newlineTerminated ? "$" : "", searchChar);
+
+ return length;
+}
+
+static int writeLineNumberEntry(const tagEntryInfo *const tag) {
+ return fprintf(TagFile.fp, "%lu", tag->lineNumber);
+}
+
+static int writeCtagsEntry(const tagEntryInfo *const tag) {
+ int length = fprintf(TagFile.fp, "%s\t%s\t", tag->name, tag->sourceFileName);
+
+ if (tag->lineNumberEntry)
+ length += writeLineNumberEntry(tag);
+ else
+ length += writePatternEntry(tag);
+
+ if (includeExtensionFlags()) length += addExtensionFields(tag);
+
+ length += fprintf(TagFile.fp, "\n");
+
+ return length;
+}
+
+extern void makeTagEntry(const tagEntryInfo *const tag) {
+ Assert(tag->name != NULL);
+ if (tag->name[0] == '\0')
+ error(WARNING, "ignoring null tag in %s", vStringValue(File.name));
+ else {
+ int length = 0;
+
+ DebugStatement(debugEntry(tag);) if (Option.xref) {
+ if (!tag->isFileEntry) length = writeXrefEntry(tag);
+ }
+ else if (Option.etags) length = writeEtagsEntry(tag);
+ else length = writeCtagsEntry(tag);
+
+ ++TagFile.numTags.added;
+ rememberMaxLengths(strlen(tag->name), (size_t)length);
+ DebugStatement(fflush(TagFile.fp);)
+ }
+}
+
+extern void initTagEntry(tagEntryInfo *const e, const char *const name) {
+ Assert(File.source.name != NULL);
+ memset(e, 0, sizeof(tagEntryInfo));
+ e->lineNumberEntry = (boolean)(Option.locate == EX_LINENUM);
+ e->lineNumber = getSourceLineNumber();
+ e->language = getSourceLanguageName();
+ e->filePosition = getInputFilePosition();
+ e->sourceFileName = getSourceFileTagPath();
+ e->name = name;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/entry.h b/third_party/ctags/entry.h
new file mode 100644
index 000000000..cde8e1f40
--- /dev/null
+++ b/third_party/ctags/entry.h
@@ -0,0 +1,93 @@
+#ifndef _ENTRY_H
+#define _ENTRY_H
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/stdio/stdio.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * MACROS
+ */
+#define WHOLE_FILE -1L
+
+/*
+ * DATA DECLARATIONS
+ */
+
+/* Maintains the state of the tag file.
+ */
+typedef struct eTagFile {
+ char *name;
+ char *directory;
+ FILE *fp;
+ struct sNumTags {
+ unsigned long added, prev;
+ } numTags;
+ struct sMax {
+ size_t line, tag, file;
+ } max;
+ struct sEtags {
+ char *name;
+ FILE *fp;
+ size_t byteCount;
+ } etags;
+ vString *vLine;
+} tagFile;
+
+typedef struct sTagFields {
+ unsigned int count; /* number of additional extension flags */
+ const char *const *label; /* list of labels for extension flags */
+ const char *const *value; /* list of values for extension flags */
+} tagFields;
+
+/* Information about the current tag candidate.
+ */
+typedef struct sTagEntryInfo {
+ boolean lineNumberEntry; /* pattern or line number entry */
+ unsigned long lineNumber; /* line number of tag */
+ fpos_t filePosition; /* file position of line containing tag */
+ const char *language; /* language of source file */
+ boolean isFileScope; /* is tag visibile only within source file? */
+ boolean isFileEntry; /* is this just an entry for a file name? */
+ boolean truncateLine; /* truncate tag line at end of tag name? */
+ const char *sourceFileName; /* name of source file */
+ const char *name; /* name of the tag */
+ const char *kindName; /* kind of tag */
+ char kind; /* single character representation of kind */
+ struct {
+ const char *access;
+ const char *fileScope;
+ const char *implementation;
+ const char *inheritance;
+ const char *scope[2]; /* value and key */
+ const char *signature;
+
+ /* type (union/struct/etc.) and name for a variable or typedef. */
+ const char *typeRef[2]; /* e.g., "struct" and struct name */
+
+ } extensionFields; /* list of extension fields*/
+} tagEntryInfo;
+
+/*
+ * GLOBAL VARIABLES
+ */
+extern tagFile TagFile;
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+extern void freeTagFileResources(void);
+extern const char *tagFileName(void);
+extern void copyBytes(FILE *const fromFp, FILE *const toFp, const long size);
+extern void copyFile(const char *const from, const char *const to,
+ const long size);
+extern void openTagFile(void);
+extern void closeTagFile(const boolean resize);
+extern void beginEtagsFile(void);
+extern void endEtagsFile(const char *const name);
+extern void makeTagEntry(const tagEntryInfo *const tag);
+extern void initTagEntry(tagEntryInfo *const e, const char *const name);
+
+#endif /* _ENTRY_H */
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/erlang.c b/third_party/ctags/erlang.c
new file mode 100644
index 000000000..dc83fa95d
--- /dev/null
+++ b/third_party/ctags/erlang.c
@@ -0,0 +1,164 @@
+/*
+ * $Id: erlang.c 443 2006-05-30 04:37:13Z darren $
+ *
+ * Copyright (c) 2003, Brent Fulgham
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for Erlang language
+ * files. Some of the parsing constructs are based on the Emacs 'etags'
+ * program by Francesco Potori
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/str/str.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum { K_MACRO, K_FUNCTION, K_MODULE, K_RECORD } erlangKind;
+
+static kindOption ErlangKinds[] = {
+ {TRUE, 'd', "macro", "macro definitions"},
+ {TRUE, 'f', "function", "functions"},
+ {TRUE, 'm', "module", "modules"},
+ {TRUE, 'r', "record", "record definitions"},
+};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+/* tagEntryInfo and vString should be preinitialized/preallocated but not
+ * necessary. If successful you will find class name in vString
+ */
+
+static boolean isIdentifierFirstCharacter(int c) {
+ return (boolean)(isalpha(c));
+}
+
+static boolean isIdentifierCharacter(int c) {
+ return (boolean)(isalnum(c) || c == '_' || c == ':');
+}
+
+static const unsigned char *skipSpace(const unsigned char *cp) {
+ while (isspace((int)*cp)) ++cp;
+ return cp;
+}
+
+static const unsigned char *parseIdentifier(const unsigned char *cp,
+ vString *const identifier) {
+ vStringClear(identifier);
+ while (isIdentifierCharacter((int)*cp)) {
+ vStringPut(identifier, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(identifier);
+ return cp;
+}
+
+static void makeMemberTag(vString *const identifier, erlangKind kind,
+ vString *const module) {
+ if (ErlangKinds[kind].enabled && vStringLength(identifier) > 0) {
+ tagEntryInfo tag;
+ initTagEntry(&tag, vStringValue(identifier));
+ tag.kindName = ErlangKinds[kind].name;
+ tag.kind = ErlangKinds[kind].letter;
+
+ if (module != NULL && vStringLength(module) > 0) {
+ tag.extensionFields.scope[0] = "module";
+ tag.extensionFields.scope[1] = vStringValue(module);
+ }
+ makeTagEntry(&tag);
+ }
+}
+
+static void parseModuleTag(const unsigned char *cp, vString *const module) {
+ vString *const identifier = vStringNew();
+ parseIdentifier(cp, identifier);
+ makeSimpleTag(identifier, ErlangKinds, K_MODULE);
+
+ /* All further entries go in the new module */
+ vStringCopy(module, identifier);
+ vStringDelete(identifier);
+}
+
+static void parseSimpleTag(const unsigned char *cp, erlangKind kind) {
+ vString *const identifier = vStringNew();
+ parseIdentifier(cp, identifier);
+ makeSimpleTag(identifier, ErlangKinds, kind);
+ vStringDelete(identifier);
+}
+
+static void parseFunctionTag(const unsigned char *cp, vString *const module) {
+ vString *const identifier = vStringNew();
+ parseIdentifier(cp, identifier);
+ makeMemberTag(identifier, K_FUNCTION, module);
+ vStringDelete(identifier);
+}
+
+/*
+ * Directives are of the form:
+ * -module(foo)
+ * -define(foo, bar)
+ * -record(graph, {vtab = notable, cyclic = true}).
+ */
+static void parseDirective(const unsigned char *cp, vString *const module) {
+ /*
+ * A directive will be either a record definition or a directive.
+ * Record definitions are handled separately
+ */
+ vString *const directive = vStringNew();
+ const char *const drtv = vStringValue(directive);
+ cp = parseIdentifier(cp, directive);
+ cp = skipSpace(cp);
+ if (*cp == '(') ++cp;
+
+ if (strcmp(drtv, "record") == 0)
+ parseSimpleTag(cp, K_RECORD);
+ else if (strcmp(drtv, "define") == 0)
+ parseSimpleTag(cp, K_MACRO);
+ else if (strcmp(drtv, "module") == 0)
+ parseModuleTag(cp, module);
+ /* Otherwise, it was an import, export, etc. */
+
+ vStringDelete(directive);
+}
+
+static void findErlangTags(void) {
+ vString *const module = vStringNew();
+ const unsigned char *line;
+
+ while ((line = fileReadLine()) != NULL) {
+ const unsigned char *cp = line;
+
+ if (*cp == '%') /* skip initial comment */
+ continue;
+ if (*cp == '"') /* strings sometimes start in column one */
+ continue;
+
+ if (*cp == '-') {
+ ++cp; /* Move off of the '-' */
+ parseDirective(cp, module);
+ } else if (isIdentifierFirstCharacter((int)*cp))
+ parseFunctionTag(cp, module);
+ }
+ vStringDelete(module);
+}
+
+extern parserDefinition *ErlangParser(void) {
+ static const char *const extensions[] = {"erl", "ERL", "hrl", "HRL", NULL};
+ parserDefinition *def = parserNew("Erlang");
+ def->kinds = ErlangKinds;
+ def->kindCount = KIND_COUNT(ErlangKinds);
+ def->extensions = extensions;
+ def->parser = findErlangTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/flex.c b/third_party/ctags/flex.c
new file mode 100644
index 000000000..891eed776
--- /dev/null
+++ b/third_party/ctags/flex.c
@@ -0,0 +1,2105 @@
+/*
+ * $Id: flex.c 666 2008-05-15 17:47:31Z dfishburn $
+ *
+ * Copyright (c) 2008, David Fishburn
+ *
+ * This source code is released for free distribution under the
+ * terms of the GNU General Public License.
+ *
+ * This module contains functions for generating tags for Adobe languages.
+ * There are a number of different ones, but this will begin with:
+ * Flex
+ * MXML files (*.mMacromedia XML)
+ * ActionScript files (*.as)
+ *
+ * Flex 3 language reference
+ * http://livedocs.adobe.com/flex/3/langref/index.html
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/calls/calls.h"
+#include "libc/runtime/runtime.h"
+#include "libc/str/str.h"
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * MACROS
+ */
+#define isType(token, t) (boolean)((token)->type == (t))
+#define isKeyword(token, k) (boolean)((token)->keyword == (k))
+
+/*
+ * DATA DECLARATIONS
+ */
+
+typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
+
+/*
+ * Tracks class and function names already created
+ */
+static stringList *ClassNames;
+static stringList *FunctionNames;
+
+/* Used to specify type of keyword.
+ */
+typedef enum eKeywordId {
+ KEYWORD_NONE = -1,
+ KEYWORD_function,
+ KEYWORD_capital_function,
+ KEYWORD_object,
+ KEYWORD_capital_object,
+ KEYWORD_prototype,
+ KEYWORD_var,
+ KEYWORD_new,
+ KEYWORD_this,
+ KEYWORD_for,
+ KEYWORD_while,
+ KEYWORD_do,
+ KEYWORD_if,
+ KEYWORD_else,
+ KEYWORD_switch,
+ KEYWORD_try,
+ KEYWORD_catch,
+ KEYWORD_finally,
+ KEYWORD_public,
+ KEYWORD_private,
+ KEYWORD_static,
+ KEYWORD_class,
+ KEYWORD_id,
+ KEYWORD_name,
+ KEYWORD_script,
+ KEYWORD_cdata,
+ KEYWORD_mx,
+ KEYWORD_fx,
+ KEYWORD_override
+} keywordId;
+
+/* Used to determine whether keyword is valid for the token language and
+ * what its ID is.
+ */
+typedef struct sKeywordDesc {
+ const char *name;
+ keywordId id;
+} keywordDesc;
+
+typedef enum eTokenType {
+ TOKEN_UNDEFINED,
+ TOKEN_CHARACTER,
+ TOKEN_CLOSE_PAREN,
+ TOKEN_SEMICOLON,
+ TOKEN_COLON,
+ TOKEN_COMMA,
+ TOKEN_KEYWORD,
+ TOKEN_OPEN_PAREN,
+ TOKEN_OPERATOR,
+ TOKEN_IDENTIFIER,
+ TOKEN_STRING,
+ TOKEN_PERIOD,
+ TOKEN_OPEN_CURLY,
+ TOKEN_CLOSE_CURLY,
+ TOKEN_EQUAL_SIGN,
+ TOKEN_EXCLAMATION,
+ TOKEN_FORWARD_SLASH,
+ TOKEN_OPEN_SQUARE,
+ TOKEN_CLOSE_SQUARE,
+ TOKEN_OPEN_MXML,
+ TOKEN_CLOSE_MXML,
+ TOKEN_CLOSE_SGML,
+ TOKEN_LESS_THAN,
+ TOKEN_GREATER_THAN,
+ TOKEN_QUESTION_MARK,
+ TOKEN_OPEN_NAMESPACE
+} tokenType;
+
+typedef struct sTokenInfo {
+ tokenType type;
+ keywordId keyword;
+ vString *string;
+ vString *scope;
+ unsigned long lineNumber;
+ fpos_t filePosition;
+ int nestLevel;
+ boolean ignoreTag;
+ boolean isClass;
+} tokenInfo;
+
+/*
+ * DATA DEFINITIONS
+ */
+
+static langType Lang_js;
+
+static jmp_buf Exception;
+
+typedef enum {
+ FLEXTAG_FUNCTION,
+ FLEXTAG_CLASS,
+ FLEXTAG_METHOD,
+ FLEXTAG_PROPERTY,
+ FLEXTAG_VARIABLE,
+ FLEXTAG_MXTAG,
+ FLEXTAG_COUNT
+} flexKind;
+
+static kindOption FlexKinds[] = {{TRUE, 'f', "function", "functions"},
+ {TRUE, 'c', "class", "classes"},
+ {TRUE, 'm', "method", "methods"},
+ {TRUE, 'p', "property", "properties"},
+ {TRUE, 'v', "variable", "global variables"},
+ {TRUE, 'x', "mxtag", "mxtags"}};
+
+static const keywordDesc FlexKeywordTable[] = {
+ /* keyword keyword ID */
+ {"function", KEYWORD_function},
+ {"Function", KEYWORD_capital_function},
+ {"object", KEYWORD_object},
+ {"Object", KEYWORD_capital_object},
+ {"prototype", KEYWORD_prototype},
+ {"var", KEYWORD_var},
+ {"new", KEYWORD_new},
+ {"this", KEYWORD_this},
+ {"for", KEYWORD_for},
+ {"while", KEYWORD_while},
+ {"do", KEYWORD_do},
+ {"if", KEYWORD_if},
+ {"else", KEYWORD_else},
+ {"switch", KEYWORD_switch},
+ {"try", KEYWORD_try},
+ {"catch", KEYWORD_catch},
+ {"finally", KEYWORD_finally},
+ {"public", KEYWORD_public},
+ {"private", KEYWORD_private},
+ {"static", KEYWORD_static},
+ {"class", KEYWORD_class},
+ {"id", KEYWORD_id},
+ {"name", KEYWORD_name},
+ {"script", KEYWORD_script},
+ {"cdata", KEYWORD_cdata},
+ {"mx", KEYWORD_mx},
+ {"fx", KEYWORD_fx},
+ {"override", KEYWORD_override}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+/* Recursive functions */
+static void parseFunction(tokenInfo *const token);
+static boolean parseBlock(tokenInfo *const token, tokenInfo *const parent);
+static boolean parseLine(tokenInfo *const token);
+static boolean parseActionScript(tokenInfo *const token);
+static boolean parseMXML(tokenInfo *const token);
+
+static boolean isIdentChar(const int c) {
+ return (boolean)(isalpha(c) || isdigit(c) || c == '$' || c == '@' ||
+ c == '_' || c == '#');
+}
+
+static void buildFlexKeywordHash(void) {
+ const size_t count = sizeof(FlexKeywordTable) / sizeof(FlexKeywordTable[0]);
+ size_t i;
+ for (i = 0; i < count; ++i) {
+ const keywordDesc *const p = &FlexKeywordTable[i];
+ addKeyword(p->name, Lang_js, (int)p->id);
+ }
+}
+
+static tokenInfo *newToken(void) {
+ tokenInfo *const token = xMalloc(1, tokenInfo);
+
+ token->type = TOKEN_UNDEFINED;
+ token->keyword = KEYWORD_NONE;
+ token->string = vStringNew();
+ token->scope = vStringNew();
+ token->nestLevel = 0;
+ token->isClass = FALSE;
+ token->ignoreTag = FALSE;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+
+ return token;
+}
+
+static void deleteToken(tokenInfo *const token) {
+ vStringDelete(token->string);
+ vStringDelete(token->scope);
+ eFree(token);
+}
+
+/*
+ * Tag generation functions
+ */
+
+static void makeConstTag(tokenInfo *const token, const flexKind kind) {
+ if (FlexKinds[kind].enabled && !token->ignoreTag) {
+ const char *const name = vStringValue(token->string);
+ tagEntryInfo e;
+ initTagEntry(&e, name);
+
+ e.lineNumber = token->lineNumber;
+ e.filePosition = token->filePosition;
+ e.kindName = FlexKinds[kind].name;
+ e.kind = FlexKinds[kind].letter;
+
+ makeTagEntry(&e);
+ }
+}
+
+static void makeFlexTag(tokenInfo *const token, flexKind kind) {
+ vString *fulltag;
+
+ if (FlexKinds[kind].enabled && !token->ignoreTag) {
+ DebugStatement(
+ debugPrintf(
+ DEBUG_PARSE,
+ "\n makeFlexTag start: token isClass:%d scope:%s name:%s\n",
+ token->isClass, vStringValue(token->scope),
+ vStringValue(token->string)););
+ if (kind == FLEXTAG_FUNCTION && token->isClass) {
+ kind = FLEXTAG_METHOD;
+ }
+ /*
+ * If a scope has been added to the token, change the token
+ * string to include the scope when making the tag.
+ */
+ if (vStringLength(token->scope) > 0) {
+ fulltag = vStringNew();
+ vStringCopy(fulltag, token->scope);
+ vStringCatS(fulltag, ".");
+ vStringCatS(fulltag, vStringValue(token->string));
+ vStringTerminate(fulltag);
+ vStringCopy(token->string, fulltag);
+ vStringDelete(fulltag);
+ }
+ makeConstTag(token, kind);
+ }
+}
+
+static void makeClassTag(tokenInfo *const token) {
+ vString *fulltag;
+
+ if (!token->ignoreTag) {
+ fulltag = vStringNew();
+ if (vStringLength(token->scope) > 0) {
+ vStringCopy(fulltag, token->scope);
+ vStringCatS(fulltag, ".");
+ vStringCatS(fulltag, vStringValue(token->string));
+ } else {
+ vStringCopy(fulltag, token->string);
+ }
+ vStringTerminate(fulltag);
+ if (!stringListHas(ClassNames, vStringValue(fulltag))) {
+ stringListAdd(ClassNames, vStringNewCopy(fulltag));
+ makeFlexTag(token, FLEXTAG_CLASS);
+ }
+ vStringDelete(fulltag);
+ }
+}
+
+static void makeMXTag(tokenInfo *const token) {
+ vString *fulltag;
+
+ if (!token->ignoreTag) {
+ fulltag = vStringNew();
+ if (vStringLength(token->scope) > 0) {
+ vStringCopy(fulltag, token->scope);
+ vStringCatS(fulltag, ".");
+ vStringCatS(fulltag, vStringValue(token->string));
+ } else {
+ vStringCopy(fulltag, token->string);
+ }
+ vStringTerminate(fulltag);
+ makeFlexTag(token, FLEXTAG_MXTAG);
+ vStringDelete(fulltag);
+ }
+}
+
+static void makeFunctionTag(tokenInfo *const token) {
+ vString *fulltag;
+
+ if (!token->ignoreTag) {
+ fulltag = vStringNew();
+ if (vStringLength(token->scope) > 0) {
+ vStringCopy(fulltag, token->scope);
+ vStringCatS(fulltag, ".");
+ vStringCatS(fulltag, vStringValue(token->string));
+ } else {
+ vStringCopy(fulltag, token->string);
+ }
+ vStringTerminate(fulltag);
+ if (!stringListHas(FunctionNames, vStringValue(fulltag))) {
+ stringListAdd(FunctionNames, vStringNewCopy(fulltag));
+ makeFlexTag(token, FLEXTAG_FUNCTION);
+ }
+ vStringDelete(fulltag);
+ }
+}
+
+/*
+ * Parsing functions
+ */
+
+static void parseString(vString *const string, const int delimiter) {
+ boolean end = FALSE;
+ while (!end) {
+ int c = fileGetc();
+ if (c == EOF)
+ end = TRUE;
+ else if (c == '\\') {
+ c = fileGetc(); /* This maybe a ' or ". */
+ vStringPut(string, c);
+ } else if (c == delimiter)
+ end = TRUE;
+ else
+ vStringPut(string, c);
+ }
+ vStringTerminate(string);
+}
+
+/* Read a C identifier beginning with "firstChar" and places it into
+ * "name".
+ */
+static void parseIdentifier(vString *const string, const int firstChar) {
+ int c = firstChar;
+ Assert(isIdentChar(c));
+ do {
+ vStringPut(string, c);
+ c = fileGetc();
+ } while (isIdentChar(c));
+ vStringTerminate(string);
+ if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */
+}
+
+static void readToken(tokenInfo *const token) {
+ int c;
+
+ token->type = TOKEN_UNDEFINED;
+ token->keyword = KEYWORD_NONE;
+ vStringClear(token->string);
+
+getNextChar:
+ do {
+ c = fileGetc();
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ } while (c == '\t' || c == ' ' || c == '\n');
+
+ switch (c) {
+ case EOF:
+ longjmp(Exception, (int)ExceptionEOF);
+ break;
+ case '(':
+ token->type = TOKEN_OPEN_PAREN;
+ break;
+ case ')':
+ token->type = TOKEN_CLOSE_PAREN;
+ break;
+ case ';':
+ token->type = TOKEN_SEMICOLON;
+ break;
+ case ',':
+ token->type = TOKEN_COMMA;
+ break;
+ case '.':
+ token->type = TOKEN_PERIOD;
+ break;
+ case ':':
+ token->type = TOKEN_COLON;
+ break;
+ case '{':
+ token->type = TOKEN_OPEN_CURLY;
+ break;
+ case '}':
+ token->type = TOKEN_CLOSE_CURLY;
+ break;
+ case '=':
+ token->type = TOKEN_EQUAL_SIGN;
+ break;
+ case '[':
+ token->type = TOKEN_OPEN_SQUARE;
+ break;
+ case ']':
+ token->type = TOKEN_CLOSE_SQUARE;
+ break;
+ case '?':
+ token->type = TOKEN_QUESTION_MARK;
+ break;
+
+ case '\'':
+ case '"':
+ token->type = TOKEN_STRING;
+ parseString(token->string, c);
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+
+ case '\\':
+ c = fileGetc();
+ if (c != '\\' && c != '"' && !isspace(c)) fileUngetc(c);
+ token->type = TOKEN_CHARACTER;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+
+ case '/': {
+ int d = fileGetc();
+ if ((d != '*') && /* is this the start of a comment? */
+ (d != '/') && /* is a one line comment? */
+ (d != '>')) /* is this a close XML tag? */
+ {
+ fileUngetc(d);
+ token->type = TOKEN_FORWARD_SLASH;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ } else {
+ if (d == '*') {
+ do {
+ fileSkipToCharacter('*');
+ c = fileGetc();
+ if (c == '/')
+ break;
+ else
+ fileUngetc(c);
+ } while (c != EOF && c != '\0');
+ goto getNextChar;
+ } else if (d == '/') /* is this the start of a comment? */
+ {
+ fileSkipToCharacter('\n');
+ goto getNextChar;
+ } else if (d == '>') /* is this the start of a comment? */
+ {
+ token->type = TOKEN_CLOSE_SGML;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ }
+ }
+ break;
+ }
+
+ case '<': {
+ /*
+ * An XML comment looks like this
+ *
+ */
+ int d = fileGetc();
+
+ if ((d != '!') && /* is this the start of a comment? */
+ (d != '/') && /* is this the start of a closing mx tag */
+ (d != 'm') && /* is this the start of a mx tag */
+ (d != 'f') && /* is this the start of a fx tag */
+ (d != 's')) /* is this the start of a spark tag */
+ {
+ fileUngetc(d);
+ token->type = TOKEN_LESS_THAN;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+ } else {
+ if (d == '!') {
+ int e = fileGetc();
+ if (e != '-') /* is this the start of a comment? */
+ {
+ fileUngetc(e);
+ fileUngetc(d);
+ token->type = TOKEN_LESS_THAN;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ } else {
+ if (e == '-') {
+ int f = fileGetc();
+ if (f != '-') /* is this the start of a comment? */
+ {
+ fileUngetc(f);
+ fileUngetc(e);
+ fileUngetc(d);
+ token->type = TOKEN_LESS_THAN;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ } else {
+ if (f == '-') {
+ do {
+ fileSkipToCharacter('-');
+ c = fileGetc();
+ if (c == '-') {
+ d = fileGetc();
+ if (d == '>')
+ break;
+ else {
+ fileUngetc(d);
+ fileUngetc(c);
+ }
+ break;
+ } else
+ fileUngetc(c);
+ } while (c != EOF && c != '\0');
+ goto getNextChar;
+ }
+ }
+ }
+ }
+ } else if (d == 'm' || d == 'f' || d == 's') {
+ int e = fileGetc();
+ if ((d == 'm' || d == 'f') &&
+ e != 'x') /* continuing an mx or fx tag */
+ {
+ fileUngetc(e);
+ fileUngetc(d);
+ token->type = TOKEN_LESS_THAN;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+ } else {
+ if ((d == 'm' || d == 'f') && e == 'x') {
+ int f = fileGetc();
+ if (f != ':') /* start of the tag */
+ {
+ fileUngetc(f);
+ fileUngetc(e);
+ fileUngetc(d);
+ token->type = TOKEN_LESS_THAN;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+ } else {
+ token->type = TOKEN_OPEN_MXML;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+ }
+ }
+ if (d == 's' && e == ':') /* continuing a spark tag */
+ {
+ token->type = TOKEN_OPEN_MXML;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+ } else {
+ fileUngetc(e);
+ fileUngetc(d);
+ token->type = TOKEN_LESS_THAN;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+ }
+ }
+ } else if (d == '/') {
+ int e = fileGetc();
+ if (!(e == 'm' || e == 'f' || e == 's')) {
+ fileUngetc(e);
+ fileUngetc(d);
+ token->type = TOKEN_LESS_THAN;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+ } else {
+ int f = fileGetc();
+ if ((e == 'm' || e == 'f') &&
+ f != 'x') /* continuing an mx or fx tag */
+ {
+ fileUngetc(f);
+ fileUngetc(e);
+ token->type = TOKEN_LESS_THAN;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+ } else {
+ if (f == 'x') {
+ int g = fileGetc();
+ if (g != ':') /* is this the start of a comment? */
+ {
+ fileUngetc(g);
+ fileUngetc(f);
+ fileUngetc(e);
+ token->type = TOKEN_LESS_THAN;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+ } else {
+ token->type = TOKEN_CLOSE_MXML;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+ }
+ }
+ if (e == 's' && f == ':') /* continuing a spark tag */
+ {
+ token->type = TOKEN_CLOSE_MXML;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+ } else {
+ fileUngetc(f);
+ fileUngetc(e);
+ token->type = TOKEN_LESS_THAN;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case '>':
+ token->type = TOKEN_GREATER_THAN;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+
+ case '!':
+ token->type = TOKEN_EXCLAMATION;
+ /*token->lineNumber = getSourceLineNumber ();
+ token->filePosition = getInputFilePosition ();*/
+ break;
+
+ default:
+ if (!isIdentChar(c))
+ token->type = TOKEN_UNDEFINED;
+ else {
+ parseIdentifier(token->string, c);
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ token->keyword = analyzeToken(token->string, Lang_js);
+ if (isKeyword(token, KEYWORD_NONE))
+ token->type = TOKEN_IDENTIFIER;
+ else
+ token->type = TOKEN_KEYWORD;
+ }
+ break;
+ }
+}
+
+static void copyToken(tokenInfo *const dest, tokenInfo *const src) {
+ dest->nestLevel = src->nestLevel;
+ dest->lineNumber = src->lineNumber;
+ dest->filePosition = src->filePosition;
+ dest->type = src->type;
+ dest->keyword = src->keyword;
+ dest->isClass = src->isClass;
+ vStringCopy(dest->string, src->string);
+ vStringCopy(dest->scope, src->scope);
+}
+
+/*
+ * Token parsing functions
+ */
+
+static void skipArgumentList(tokenInfo *const token) {
+ int nest_level = 0;
+
+ /*
+ * Other databases can have arguments with fully declared
+ * datatypes:
+ * ( name varchar(30), text binary(10) )
+ * So we must check for nested open and closing parantheses
+ */
+
+ if (isType(token, TOKEN_OPEN_PAREN)) /* arguments? */
+ {
+ nest_level++;
+ while (!(isType(token, TOKEN_CLOSE_PAREN) && (nest_level == 0))) {
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ nest_level++;
+ }
+ if (isType(token, TOKEN_CLOSE_PAREN)) {
+ if (nest_level > 0) {
+ nest_level--;
+ }
+ }
+ }
+ readToken(token);
+ }
+}
+
+static void skipArrayList(tokenInfo *const token) {
+ int nest_level = 0;
+
+ /*
+ * Handle square brackets
+ * var name[1]
+ * So we must check for nested open and closing square brackets
+ */
+
+ if (isType(token, TOKEN_OPEN_SQUARE)) /* arguments? */
+ {
+ nest_level++;
+ while (!(isType(token, TOKEN_CLOSE_SQUARE) && (nest_level == 0))) {
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_SQUARE)) {
+ nest_level++;
+ }
+ if (isType(token, TOKEN_CLOSE_SQUARE)) {
+ if (nest_level > 0) {
+ nest_level--;
+ }
+ }
+ }
+ readToken(token);
+ }
+}
+
+static void addContext(tokenInfo *const parent, const tokenInfo *const child) {
+ if (vStringLength(parent->string) > 0) {
+ vStringCatS(parent->string, ".");
+ }
+ vStringCatS(parent->string, vStringValue(child->string));
+ vStringTerminate(parent->string);
+}
+
+static void addToScope(tokenInfo *const token, vString *const extra) {
+ if (vStringLength(token->scope) > 0) {
+ vStringCatS(token->scope, ".");
+ }
+ vStringCatS(token->scope, vStringValue(extra));
+ vStringTerminate(token->scope);
+}
+
+/*
+ * Scanning functions
+ */
+
+static void findCmdTerm(tokenInfo *const token) {
+ /*
+ * Read until we find either a semicolon or closing brace.
+ * Any nested braces will be handled within.
+ */
+ while (
+ !(isType(token, TOKEN_SEMICOLON) || isType(token, TOKEN_CLOSE_CURLY))) {
+ /* Handle nested blocks */
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ parseBlock(token, token);
+ } else if (isType(token, TOKEN_OPEN_PAREN)) {
+ skipArgumentList(token);
+ } else {
+ readToken(token);
+ }
+ }
+}
+
+static void parseSwitch(tokenInfo *const token) {
+ /*
+ * switch (expression){
+ * case value1:
+ * statement;
+ * break;
+ * case value2:
+ * statement;
+ * break;
+ * default : statement;
+ * }
+ */
+
+ readToken(token);
+
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ skipArgumentList(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ do {
+ readToken(token);
+ } while (!(
+ isType(token, TOKEN_CLOSE_SGML) || isType(token, TOKEN_CLOSE_MXML) ||
+ isType(token, TOKEN_CLOSE_CURLY) || isType(token, TOKEN_GREATER_THAN)));
+ }
+}
+
+static void parseLoop(tokenInfo *const token) {
+ /*
+ * Handles these statements
+ * for (x=0; x<3; x++)
+ * document.write("This text is repeated three times
");
+ *
+ * for (x=0; x<3; x++)
+ * {
+ * document.write("This text is repeated three times
");
+ * }
+ *
+ * while (number<5){
+ * document.write(number+"
");
+ * number++;
+ * }
+ *
+ * do{
+ * document.write(number+"
");
+ * number++;
+ * }
+ * while (number<5);
+ */
+
+ if (isKeyword(token, KEYWORD_for) || isKeyword(token, KEYWORD_while)) {
+ readToken(token);
+
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ /*
+ * Handle nameless functions, these will only
+ * be considered methods.
+ */
+ skipArgumentList(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * This will be either a function or a class.
+ * We can only determine this by checking the body
+ * of the function. If we find a "this." we know
+ * it is a class, otherwise it is a function.
+ */
+ parseBlock(token, token);
+ } else {
+ parseLine(token);
+ }
+ } else if (isKeyword(token, KEYWORD_do)) {
+ readToken(token);
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * This will be either a function or a class.
+ * We can only determine this by checking the body
+ * of the function. If we find a "this." we know
+ * it is a class, otherwise it is a function.
+ */
+ parseBlock(token, token);
+ } else {
+ parseLine(token);
+ }
+
+ readToken(token);
+
+ if (isKeyword(token, KEYWORD_while)) {
+ readToken(token);
+
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ /*
+ * Handle nameless functions, these will only
+ * be considered methods.
+ */
+ skipArgumentList(token);
+ }
+ }
+ }
+}
+
+static boolean parseIf(tokenInfo *const token) {
+ boolean read_next_token = TRUE;
+ /*
+ * If statements have two forms
+ * if ( ... )
+ * one line;
+ *
+ * if ( ... )
+ * statement;
+ * else
+ * statement
+ *
+ * if ( ... ) {
+ * multiple;
+ * statements;
+ * }
+ *
+ *
+ * if ( ... ) {
+ * return elem
+ * }
+ *
+ * This example if correctly written, but the
+ * else contains only 1 statement without a terminator
+ * since the function finishes with the closing brace.
+ *
+ * function a(flag){
+ * if(flag)
+ * test(1);
+ * else
+ * test(2)
+ * }
+ *
+ * TODO: Deal with statements that can optional end
+ * without a semi-colon. Currently this messes up
+ * the parsing of blocks.
+ * Need to somehow detect this has happened, and either
+ * backup a token, or skip reading the next token if
+ * that is possible from all code locations.
+ *
+ */
+
+ readToken(token);
+
+ if (isKeyword(token, KEYWORD_if)) {
+ /*
+ * Check for an "else if" and consume the "if"
+ */
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ /*
+ * Handle nameless functions, these will only
+ * be considered methods.
+ */
+ skipArgumentList(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * This will be either a function or a class.
+ * We can only determine this by checking the body
+ * of the function. If we find a "this." we know
+ * it is a class, otherwise it is a function.
+ */
+ parseBlock(token, token);
+ } else {
+ findCmdTerm(token);
+
+ /*
+ * The IF could be followed by an ELSE statement.
+ * This too could have two formats, a curly braced
+ * multiline section, or another single line.
+ */
+
+ if (isType(token, TOKEN_CLOSE_CURLY)) {
+ /*
+ * This statement did not have a line terminator.
+ */
+ read_next_token = FALSE;
+ } else {
+ readToken(token);
+
+ if (isType(token, TOKEN_CLOSE_CURLY)) {
+ /*
+ * This statement did not have a line terminator.
+ */
+ read_next_token = FALSE;
+ } else {
+ if (isKeyword(token, KEYWORD_else)) read_next_token = parseIf(token);
+ }
+ }
+ }
+ return read_next_token;
+}
+
+static void parseFunction(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+
+ /*
+ * This deals with these formats
+ * private static function ioErrorHandler( event:IOErrorEvent ):void {
+ */
+
+ if (isKeyword(token, KEYWORD_function)) {
+ readToken(token);
+ }
+
+ copyToken(name, token);
+ /* Add scope in case this is an INNER function
+ addToScope(name, token->scope);
+ */
+
+ DebugStatement(
+ debugPrintf(DEBUG_PARSE,
+ "\n parseFunction: token isClass:%d scope:%s name:%s\n",
+ token->isClass, vStringValue(token->scope),
+ vStringValue(token->string)););
+ DebugStatement(
+ debugPrintf(DEBUG_PARSE,
+ "\n parseFunction: name isClass:%d scope:%s name:%s\n",
+ name->isClass, vStringValue(name->scope),
+ vStringValue(name->string)););
+
+ readToken(token);
+
+ if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token);
+
+ if (isType(token, TOKEN_COLON)) {
+ /*
+ * function fname ():ReturnType
+ */
+ readToken(token);
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ DebugStatement(
+ debugPrintf(
+ DEBUG_PARSE,
+ "\n parseFunction end: name isClass:%d scope:%s name:%s\n",
+ name->isClass, vStringValue(name->scope),
+ vStringValue(name->string)););
+ parseBlock(token, name);
+ DebugStatement(
+ debugPrintf(
+ DEBUG_PARSE,
+ "\n parseFunction end2: token isClass:%d scope:%s name:%s\n",
+ token->isClass, vStringValue(token->scope),
+ vStringValue(token->string)););
+ DebugStatement(
+ debugPrintf(
+ DEBUG_PARSE,
+ "\n parseFunction end2: token isClass:%d scope:%s name:%s\n",
+ token->isClass, vStringValue(token->scope),
+ vStringValue(token->string)););
+ DebugStatement(
+ debugPrintf(
+ DEBUG_PARSE,
+ "\n parseFunction end3: name isClass:%d scope:%s name:%s\n",
+ name->isClass, vStringValue(name->scope),
+ vStringValue(name->string)););
+ makeFunctionTag(name);
+ }
+
+ findCmdTerm(token);
+
+ deleteToken(name);
+}
+
+static boolean parseBlock(tokenInfo *const token, tokenInfo *const parent) {
+ boolean read_next_token = TRUE;
+ vString *saveScope = vStringNew();
+
+ vStringClear(saveScope);
+ vStringCopy(saveScope, token->scope);
+ token->nestLevel++;
+ DebugStatement(
+ debugPrintf(DEBUG_PARSE,
+ "\n parseBlock start: token isClass:%d scope:%s name:%s\n",
+ token->isClass, vStringValue(token->scope),
+ vStringValue(token->string)););
+ /*
+ * Make this routine a bit more forgiving.
+ * If called on an open_curly advance it
+ */
+ if (isType(token, TOKEN_OPEN_CURLY) && isKeyword(token, KEYWORD_NONE))
+ readToken(token);
+
+ if (!isType(token, TOKEN_CLOSE_CURLY)) {
+ /*
+ * Read until we find the closing brace,
+ * any nested braces will be handled within
+ */
+ do {
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /* Handle nested blocks */
+ parseBlock(token, parent);
+ } else {
+ /*
+ * It is possible for a line to have no terminator
+ * if the following line is a closing brace.
+ * parseLine will detect this case and indicate
+ * whether we should read an additional token.
+ */
+ read_next_token = parseLine(token);
+ }
+
+ /*
+ * Always read a new token unless we find a statement without
+ * a ending terminator
+ */
+ if (read_next_token) readToken(token);
+
+ /*
+ * If we find a statement without a terminator consider the
+ * block finished, otherwise the stack will be off by one.
+ */
+ } while (!isType(token, TOKEN_CLOSE_CURLY) && read_next_token);
+ }
+
+ vStringDelete(saveScope);
+ token->nestLevel--;
+
+ DebugStatement(
+ debugPrintf(DEBUG_PARSE,
+ "\n parseBlock end: token isClass:%d scope:%s name:%s\n",
+ token->isClass, vStringValue(token->scope),
+ vStringValue(token->string)););
+ return FALSE;
+}
+
+static void parseMethods(tokenInfo *const token, tokenInfo *const class) {
+ tokenInfo *const name = newToken();
+
+ /*
+ * This deals with these formats
+ * validProperty : 2,
+ * validMethod : function(a,b) {}
+ * 'validMethod2' : function(a,b) {}
+ * container.dirtyTab = {'url': false, 'title':false, 'snapshot':false,
+ *'*': false}
+ */
+
+ do {
+ readToken(token);
+ if (isType(token, TOKEN_STRING) || isKeyword(token, KEYWORD_NONE)) {
+ copyToken(name, token);
+
+ readToken(token);
+ if (isType(token, TOKEN_COLON)) {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_function)) {
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ skipArgumentList(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ addToScope(name, class->string);
+ makeFlexTag(name, FLEXTAG_METHOD);
+ parseBlock(token, name);
+
+ /*
+ * Read to the closing curly, check next
+ * token, if a comma, we must loop again
+ */
+ readToken(token);
+ }
+ } else {
+ addToScope(name, class->string);
+ makeFlexTag(name, FLEXTAG_PROPERTY);
+
+ /*
+ * Read the next token, if a comma
+ * we must loop again
+ */
+ readToken(token);
+ }
+ }
+ }
+ } while (isType(token, TOKEN_COMMA));
+
+ findCmdTerm(token);
+
+ deleteToken(name);
+}
+
+static boolean parseVar(tokenInfo *const token, boolean is_public) {
+ tokenInfo *const name = newToken();
+ tokenInfo *const secondary_name = newToken();
+ vString *saveScope = vStringNew();
+ boolean is_terminated = TRUE;
+
+ vStringClear(saveScope);
+ vStringCopy(saveScope, token->scope);
+ /*
+ * Variables are defined as:
+ * private static var lastFaultMessage:Date = new Date( 0 );
+ * private static var webRequests:ArrayCollection = new ArrayCollection();
+ */
+
+ if (isKeyword(token, KEYWORD_var)) {
+ readToken(token);
+ }
+
+ /* Variable name */
+ copyToken(name, token);
+ readToken(token);
+
+ if (isType(token, TOKEN_COLON)) {
+ /*
+ * var vname ():DataType = new Date();
+ * var vname ():DataType;
+ */
+ readToken(token);
+ readToken(token);
+ }
+
+ while (!isType(token, TOKEN_SEMICOLON)) {
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_SEMICOLON)) {
+ /*
+ * Only create variables for global scope
+ */
+ /* if ( token->nestLevel == 0 && is_global ) */
+ if (is_public) {
+ if (isType(token, TOKEN_SEMICOLON)) makeFlexTag(name, FLEXTAG_VARIABLE);
+ }
+ }
+
+ vStringCopy(token->scope, saveScope);
+ deleteToken(name);
+ deleteToken(secondary_name);
+ vStringDelete(saveScope);
+
+ return is_terminated;
+}
+
+static boolean parseClass(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+ vString *saveScope = vStringNew();
+ boolean saveIsClass = token->isClass;
+
+ vStringClear(saveScope);
+ vStringCopy(saveScope, token->scope);
+ /*
+ * Variables are defined as:
+ * private static var lastFaultMessage:Date = new Date( 0 );
+ * private static var webRequests:ArrayCollection = new ArrayCollection();
+ */
+
+ if (isKeyword(token, KEYWORD_class)) {
+ readToken(token);
+ }
+
+ token->isClass = TRUE;
+ /* Add class name to scope */
+ addToScope(token, token->string);
+ /* Class name */
+ copyToken(name, token);
+ readToken(token);
+
+ DebugStatement(
+ debugPrintf(DEBUG_PARSE,
+ "\n parseClass start: token isClass:%d scope:%s name:%s\n",
+ token->isClass, vStringValue(token->scope),
+ vStringValue(token->string)););
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ makeClassTag(name);
+ parseBlock(token, name);
+ }
+
+ DebugStatement(
+ debugPrintf(DEBUG_PARSE,
+ "\n parseClass end: token isClass:%d scope:%s name:%s\n",
+ token->isClass, vStringValue(token->scope),
+ vStringValue(token->string)););
+ vStringCopy(token->scope, saveScope);
+ token->isClass = saveIsClass;
+ deleteToken(name);
+ vStringDelete(saveScope);
+
+ return TRUE;
+}
+
+static boolean parseStatement(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+ tokenInfo *const secondary_name = newToken();
+ vString *saveScope = vStringNew();
+ boolean is_public = FALSE;
+ boolean is_class = FALSE;
+ boolean is_terminated = TRUE;
+ boolean is_global = FALSE;
+ boolean is_prototype = FALSE;
+ vString *fulltag;
+
+ vStringClear(saveScope);
+ vStringCopy(saveScope, token->scope);
+ DebugStatement(
+ debugPrintf(DEBUG_PARSE,
+ "\n parseStatement: token isClass:%d scope:%s name:%s\n",
+ token->isClass, vStringValue(token->scope),
+ vStringValue(token->string)););
+ /*
+ * Functions can be named or unnamed.
+ * This deals with these formats:
+ * Function
+ * validFunctionOne = function(a,b) {}
+ * testlib.validFunctionFive = function(a,b) {}
+ * var innerThree = function(a,b) {}
+ * var innerFour = (a,b) {}
+ * var D2 = secondary_fcn_name(a,b) {}
+ * var D3 = new Function("a", "b", "return a+b;");
+ * Class
+ * testlib.extras.ValidClassOne = function(a,b) {
+ * this.a = a;
+ * }
+ * Class Methods
+ * testlib.extras.ValidClassOne.prototype = {
+ * 'validMethodOne' : function(a,b) {},
+ * 'validMethodTwo' : function(a,b) {}
+ * }
+ * ValidClassTwo = function ()
+ * {
+ * this.validMethodThree = function() {}
+ * // unnamed method
+ * this.validMethodFour = () {}
+ * }
+ * Database.prototype.validMethodThree = Database_getTodaysDate;
+ */
+
+ if (isKeyword(token, KEYWORD_public)) {
+ is_public = TRUE;
+ readToken(token);
+ }
+
+ if (isKeyword(token, KEYWORD_private)) {
+ readToken(token);
+ }
+
+ if (isKeyword(token, KEYWORD_static)) {
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_KEYWORD)) {
+ switch (token->keyword) {
+ case KEYWORD_for:
+ case KEYWORD_while:
+ case KEYWORD_do:
+ parseLoop(token);
+ break;
+ case KEYWORD_if:
+ case KEYWORD_else:
+ case KEYWORD_try:
+ case KEYWORD_catch:
+ case KEYWORD_finally:
+ /* Common semantics */
+ is_terminated = parseIf(token);
+ break;
+ case KEYWORD_switch:
+ parseSwitch(token);
+ break;
+ case KEYWORD_class:
+ parseClass(token);
+ return is_terminated;
+ break;
+ case KEYWORD_function:
+ parseFunction(token);
+ return is_terminated;
+ break;
+ case KEYWORD_var:
+ parseVar(token, is_public);
+ return is_terminated;
+ break;
+ default:
+ readToken(token);
+ break;
+ }
+ }
+
+ copyToken(name, token);
+
+ while (!isType(token, TOKEN_CLOSE_CURLY) && !isType(token, TOKEN_SEMICOLON) &&
+ !isType(token, TOKEN_EQUAL_SIGN)) {
+ /* Potentially the name of the function */
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ /*
+ * Cannot be a global variable is it has dot references in the name
+ */
+ is_global = FALSE;
+ do {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_NONE)) {
+ if (is_class) {
+ vStringCopy(saveScope, token->scope);
+ addToScope(token, name->string);
+ } else
+ addContext(name, token);
+ } else if (isKeyword(token, KEYWORD_prototype)) {
+ /*
+ * When we reach the "prototype" tag, we infer:
+ * "BindAgent" is a class
+ * "build" is a method
+ *
+ * function BindAgent( repeatableIdName, newParentIdName ) {
+ * }
+ *
+ * CASE 1
+ * Specified function name: "build"
+ * BindAgent.prototype.build = function( mode ) {
+ * ignore everything within this function
+ * }
+ *
+ * CASE 2
+ * Prototype listing
+ * ValidClassOne.prototype = {
+ * 'validMethodOne' : function(a,b) {},
+ * 'validMethodTwo' : function(a,b) {}
+ * }
+ *
+ */
+ makeClassTag(name);
+ is_class = TRUE;
+ is_prototype = TRUE;
+
+ /*
+ * There should a ".function_name" next.
+ */
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ /*
+ * Handle CASE 1
+ */
+ readToken(token);
+ if (isKeyword(token, KEYWORD_NONE)) {
+ vStringCopy(saveScope, token->scope);
+ addToScope(token, name->string);
+
+ makeFlexTag(token, FLEXTAG_METHOD);
+ /*
+ * We can read until the end of the block / statement.
+ * We need to correctly parse any nested blocks, but
+ * we do NOT want to create any tags based on what is
+ * within the blocks.
+ */
+ token->ignoreTag = TRUE;
+ /*
+ * Find to the end of the statement
+ */
+ findCmdTerm(token);
+ token->ignoreTag = FALSE;
+ is_terminated = TRUE;
+ goto cleanUp;
+ }
+ } else if (isType(token, TOKEN_EQUAL_SIGN)) {
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * Handle CASE 2
+ *
+ * Creates tags for each of these class methods
+ * ValidClassOne.prototype = {
+ * 'validMethodOne' : function(a,b) {},
+ * 'validMethodTwo' : function(a,b) {}
+ * }
+ */
+ parseMethods(token, name);
+ /*
+ * Find to the end of the statement
+ */
+ findCmdTerm(token);
+ token->ignoreTag = FALSE;
+ is_terminated = TRUE;
+ goto cleanUp;
+ }
+ }
+ }
+ readToken(token);
+ } while (isType(token, TOKEN_PERIOD));
+ }
+
+ if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token);
+
+ if (isType(token, TOKEN_COLON)) {
+ /*
+ * Functions are of this form:
+ * function fname ():ReturnType {
+ */
+ readToken(token);
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_SQUARE)) skipArrayList(token);
+ }
+
+ if (isType(token, TOKEN_CLOSE_CURLY)) {
+ /*
+ * Reaching this section without having
+ * processed an open curly brace indicates
+ * the statement is most likely not terminated.
+ */
+ is_terminated = FALSE;
+ goto cleanUp;
+ }
+
+ if (isType(token, TOKEN_SEMICOLON)) {
+ /*
+ * Only create variables for global scope
+ */
+ if (token->nestLevel == 0 && is_global) {
+ /*
+ * Handles this syntax:
+ * var g_var2;
+ */
+ if (isType(token, TOKEN_SEMICOLON)) makeFlexTag(name, FLEXTAG_VARIABLE);
+ }
+ /*
+ * Statement has ended.
+ * This deals with calls to functions, like:
+ * alert(..);
+ */
+ goto cleanUp;
+ }
+
+ if (isType(token, TOKEN_EQUAL_SIGN)) {
+ readToken(token);
+
+ if (isKeyword(token, KEYWORD_function)) {
+ readToken(token);
+
+ if (isKeyword(token, KEYWORD_NONE) && !isType(token, TOKEN_OPEN_PAREN)) {
+ /*
+ * Functions of this format:
+ * var D2A = function theAdd(a, b)
+ * {
+ * return a+b;
+ * }
+ * Are really two separate defined functions and
+ * can be referenced in two ways:
+ * alert( D2A(1,2) ); // produces 3
+ * alert( theAdd(1,2) ); // also produces 3
+ * So it must have two tags:
+ * D2A
+ * theAdd
+ * Save the reference to the name for later use, once
+ * we have established this is a valid function we will
+ * create the secondary reference to it.
+ */
+ copyToken(secondary_name, token);
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token);
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * This will be either a function or a class.
+ * We can only determine this by checking the body
+ * of the function. If we find a "this." we know
+ * it is a class, otherwise it is a function.
+ */
+ if (token->isClass) {
+ makeFlexTag(name, FLEXTAG_METHOD);
+ if (vStringLength(secondary_name->string) > 0)
+ makeFunctionTag(secondary_name);
+ parseBlock(token, name);
+ } else {
+ parseBlock(token, name);
+ makeFunctionTag(name);
+
+ if (vStringLength(secondary_name->string) > 0)
+ makeFunctionTag(secondary_name);
+
+ /*
+ * Find to the end of the statement
+ */
+ goto cleanUp;
+ }
+ }
+ } else if (isType(token, TOKEN_OPEN_PAREN)) {
+ /*
+ * Handle nameless functions
+ * this.method_name = () {}
+ */
+ skipArgumentList(token);
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * Nameless functions are only setup as methods.
+ */
+ makeFlexTag(name, FLEXTAG_METHOD);
+ parseBlock(token, name);
+ }
+ } else if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * Creates tags for each of these class methods
+ * ValidClassOne.prototype = {
+ * 'validMethodOne' : function(a,b) {},
+ * 'validMethodTwo' : function(a,b) {}
+ * }
+ */
+ parseMethods(token, name);
+ if (isType(token, TOKEN_CLOSE_CURLY)) {
+ /*
+ * Assume the closing parantheses terminates
+ * this statements.
+ */
+ is_terminated = TRUE;
+ }
+ } else if (isKeyword(token, KEYWORD_new)) {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_function) ||
+ isKeyword(token, KEYWORD_capital_function) ||
+ isKeyword(token, KEYWORD_object) ||
+ isKeyword(token, KEYWORD_capital_object)) {
+ if (isKeyword(token, KEYWORD_object) ||
+ isKeyword(token, KEYWORD_capital_object))
+ is_class = TRUE;
+
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token);
+
+ if (isType(token, TOKEN_SEMICOLON)) {
+ if (token->nestLevel == 0) {
+ if (is_class) {
+ makeClassTag(name);
+ } else {
+ makeFunctionTag(name);
+ }
+ }
+ }
+ }
+ } else if (isKeyword(token, KEYWORD_NONE)) {
+ /*
+ * Only create variables for global scope
+ */
+ if (token->nestLevel == 0 && is_global) {
+ /*
+ * A pointer can be created to the function.
+ * If we recognize the function/class name ignore the variable.
+ * This format looks identical to a variable definition.
+ * A variable defined outside of a block is considered
+ * a global variable:
+ * var g_var1 = 1;
+ * var g_var2;
+ * This is not a global variable:
+ * var g_var = function;
+ * This is a global variable:
+ * var g_var = different_var_name;
+ */
+ fulltag = vStringNew();
+ if (vStringLength(token->scope) > 0) {
+ vStringCopy(fulltag, token->scope);
+ vStringCatS(fulltag, ".");
+ vStringCatS(fulltag, vStringValue(token->string));
+ } else {
+ vStringCopy(fulltag, token->string);
+ }
+ vStringTerminate(fulltag);
+ if (!stringListHas(FunctionNames, vStringValue(fulltag)) &&
+ !stringListHas(ClassNames, vStringValue(fulltag))) {
+ findCmdTerm(token);
+ if (isType(token, TOKEN_SEMICOLON))
+ makeFlexTag(name, FLEXTAG_VARIABLE);
+ }
+ vStringDelete(fulltag);
+ }
+ }
+ }
+ findCmdTerm(token);
+
+ /*
+ * Statements can be optionally terminated in the case of
+ * statement prior to a close curly brace as in the
+ * document.write line below:
+ *
+ * function checkForUpdate() {
+ * if( 1==1 ) {
+ * document.write("hello from checkForUpdate
")
+ * }
+ * return 1;
+ * }
+ */
+ if (!is_terminated && isType(token, TOKEN_CLOSE_CURLY)) is_terminated = FALSE;
+
+cleanUp:
+ vStringCopy(token->scope, saveScope);
+ deleteToken(name);
+ deleteToken(secondary_name);
+ vStringDelete(saveScope);
+
+ return is_terminated;
+}
+
+static boolean parseLine(tokenInfo *const token) {
+ boolean is_terminated = TRUE;
+ /*
+ * Detect the common statements, if, while, for, do, ...
+ * This is necessary since the last statement within a block "{}"
+ * can be optionally terminated.
+ *
+ * If the statement is not terminated, we need to tell
+ * the calling routine to prevent reading an additional token
+ * looking for the end of the statement.
+ */
+
+ if (isType(token, TOKEN_KEYWORD)) {
+ switch (token->keyword) {
+ case KEYWORD_for:
+ case KEYWORD_while:
+ case KEYWORD_do:
+ parseLoop(token);
+ break;
+ case KEYWORD_if:
+ case KEYWORD_else:
+ case KEYWORD_try:
+ case KEYWORD_catch:
+ case KEYWORD_finally:
+ /* Common semantics */
+ is_terminated = parseIf(token);
+ break;
+ case KEYWORD_switch:
+ parseSwitch(token);
+ break;
+ default:
+ parseStatement(token);
+ break;
+ }
+ } else {
+ /*
+ * Special case where single line statements may not be
+ * SEMICOLON terminated. parseBlock needs to know this
+ * so that it does not read the next token.
+ */
+ is_terminated = parseStatement(token);
+ }
+ return is_terminated;
+}
+
+static boolean parseCDATA(tokenInfo *const token) {
+ if (isType(token, TOKEN_LESS_THAN)) {
+ /*
+ * Handle these tags
+ *
+ */
+ readToken(token);
+ if (isType(token, TOKEN_EXCLAMATION)) {
+ /*
+ * Not sure why I had to comment these out, but I did.
+ * readToken (token);
+ * if (isType (token, TOKEN_OPEN_SQUARE))
+ * {
+ */
+ readToken(token);
+ if (isKeyword(token, KEYWORD_cdata)) {
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_SQUARE)) {
+ parseActionScript(token);
+ if (isType(token, TOKEN_CLOSE_SQUARE)) {
+ readToken(token);
+ if (isType(token, TOKEN_CLOSE_SQUARE)) {
+ readToken(token);
+ }
+ }
+ }
+ }
+ /*} Not sure */
+ }
+ } else {
+ parseActionScript(token);
+ }
+ return TRUE;
+}
+
+static boolean parseNamespace(tokenInfo *const token) {
+ /*
+ * If we have found a <, we know it is not a TOKEN_OPEN_MXML
+ * but it could potentially be a different namespace.
+ * This means it will also have a closing tag, which will
+ * mess up the parser if we do not properly recurse
+ * through these tags.
+ */
+
+ if (isType(token, TOKEN_LESS_THAN)) {
+ readToken(token);
+ }
+
+ /*
+ * Check if we have reached a other namespace tag
+ *
+ * or
+ *
+ *
+ */
+ if (isType(token, TOKEN_IDENTIFIER)) {
+ readToken(token);
+ if (isType(token, TOKEN_COLON)) {
+ readToken(token);
+ if (!isType(token, TOKEN_IDENTIFIER)) {
+ return TRUE;
+ }
+ } else {
+ return TRUE;
+ }
+ } else {
+ return TRUE;
+ }
+
+ /*
+ * Confirmed we are inside a namespace tag, so
+ * process it until the close tag.
+ *
+ * But also check for new tags, which will either
+ * be recursive namespaces or MXML tags
+ */
+ do {
+ if (isType(token, TOKEN_LESS_THAN)) {
+ parseNamespace(token);
+ readToken(token);
+ }
+ if (isType(token, TOKEN_OPEN_MXML)) {
+ parseMXML(token);
+ } else {
+ readToken(token);
+ }
+ } while (
+ !(isType(token, TOKEN_CLOSE_SGML) || isType(token, TOKEN_CLOSE_MXML)));
+
+ return TRUE;
+}
+
+static boolean parseMXML(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+ tokenInfo *const type = newToken();
+ boolean inside_attributes = TRUE;
+ /*
+ * Detect the common statements, if, while, for, do, ...
+ * This is necessary since the last statement within a block "{}"
+ * can be optionally terminated.
+ *
+ * If the statement is not terminated, we need to tell
+ * the calling routine to prevent reading an additional token
+ * looking for the end of the statement.
+ */
+
+ readToken(token);
+
+ if (isKeyword(token, KEYWORD_script)) {
+ /*
+ * These tags can be of this form:
+ *
+ */
+ do {
+ readToken(token);
+ } while (!(isType(token, TOKEN_CLOSE_SGML) ||
+ isType(token, TOKEN_CLOSE_MXML) ||
+ isType(token, TOKEN_GREATER_THAN)));
+
+ if (isType(token, TOKEN_CLOSE_MXML)) {
+ /*
+ * We have found a tag
+ * Finish reading the "type" and ">"
+ */
+ readToken(token);
+ readToken(token);
+ goto cleanUp;
+ }
+ if (isType(token, TOKEN_CLOSE_SGML)) {
+ /*
+ * We have found a
+ */
+ goto cleanUp;
+ }
+
+ /*
+ * This is a beginning of an embedded script.
+ * These typically are of this format:
+ *
+ *
+ *
+ */
+ readToken(token);
+ parseCDATA(token);
+
+ readToken(token);
+ if (isType(token, TOKEN_CLOSE_MXML)) {
+ /*
+ * We have found a tag
+ * Finish reading the "type" and ">"
+ */
+ readToken(token);
+ readToken(token);
+ }
+ goto cleanUp;
+ }
+
+ copyToken(type, token);
+
+ readToken(token);
+ do {
+ if (isType(token, TOKEN_GREATER_THAN)) {
+ inside_attributes = FALSE;
+ }
+ if (isType(token, TOKEN_LESS_THAN)) {
+ parseNamespace(token);
+ readToken(token);
+ } else if (isType(token, TOKEN_OPEN_MXML)) {
+ parseMXML(token);
+ readToken(token);
+ } else if (inside_attributes && (isKeyword(token, KEYWORD_id) ||
+ isKeyword(token, KEYWORD_name))) {
+ if (vStringLength(name->string) == 0) {
+ /*
+ * If we have already created the tag based on either "name"
+ * or "id" do not do it again.
+ */
+ readToken(token);
+ readToken(token);
+
+ copyToken(name, token);
+ addToScope(name, type->string);
+ makeMXTag(name);
+ } else {
+ readToken(token);
+ }
+ } else {
+ readToken(token);
+ }
+ } while (
+ !(isType(token, TOKEN_CLOSE_SGML) || isType(token, TOKEN_CLOSE_MXML)));
+
+ if (isType(token, TOKEN_CLOSE_MXML)) {
+ /*
+ * We have found a tag
+ * Finish reading the "type" and ">"
+ */
+ readToken(token);
+ readToken(token);
+ }
+
+cleanUp:
+ deleteToken(name);
+ deleteToken(type);
+ return TRUE;
+}
+
+static boolean parseActionScript(tokenInfo *const token) {
+ do {
+ readToken(token);
+
+ if (isType(token, TOKEN_LESS_THAN)) {
+ /*
+ * Handle these tags
+ *
+ */
+ readToken(token);
+ if (isType(token, TOKEN_EQUAL_SIGN)) {
+ if (isType(token, TOKEN_OPEN_SQUARE)) {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_cdata)) {
+ readToken(token);
+ }
+ }
+ }
+ }
+ if (isType(token, TOKEN_CLOSE_SQUARE)) {
+ /*
+ * Handle these tags
+ *
+ */
+ readToken(token);
+ if (isType(token, TOKEN_CLOSE_SQUARE)) {
+ readToken(token);
+ if (isType(token, TOKEN_GREATER_THAN)) {
+ return TRUE;
+ }
+ }
+ } else if (isType(token, TOKEN_CLOSE_MXML)) {
+ /*
+ * Read the Script> tags
+ */
+ readToken(token);
+ readToken(token);
+ return TRUE;
+ } else if (isType(token, TOKEN_OPEN_MXML)) {
+ parseMXML(token);
+ } else {
+ if (isType(token, TOKEN_KEYWORD)) {
+ if (isKeyword(token, KEYWORD_private) ||
+ isKeyword(token, KEYWORD_public) ||
+ isKeyword(token, KEYWORD_override)) {
+ /*
+ * Methods can be defined as:
+ * private function f_name
+ * public override function f_name
+ * override private function f_name
+ * Ignore these keywords if present.
+ */
+ readToken(token);
+ }
+ if (isKeyword(token, KEYWORD_private) ||
+ isKeyword(token, KEYWORD_public) ||
+ isKeyword(token, KEYWORD_override)) {
+ /*
+ * Methods can be defined as:
+ * private function f_name
+ * public override function f_name
+ * override private function f_name
+ * Ignore these keywords if present.
+ */
+ readToken(token);
+ }
+
+ switch (token->keyword) {
+ case KEYWORD_function:
+ parseFunction(token);
+ break;
+ default:
+ parseLine(token);
+ break;
+ }
+ } else {
+ parseLine(token);
+ }
+ }
+ } while (TRUE);
+}
+
+static void parseFlexFile(tokenInfo *const token) {
+ do {
+ readToken(token);
+
+ if (isType(token, TOKEN_OPEN_MXML)) {
+ parseMXML(token);
+ } else if (isType(token, TOKEN_LESS_THAN)) {
+ readToken(token);
+ if (isType(token, TOKEN_QUESTION_MARK)) {
+ /*
+ *
+ */
+ readToken(token);
+ while (!isType(token, TOKEN_QUESTION_MARK)) {
+ readToken(token);
+ }
+ readToken(token);
+ } else if (isKeyword(token, KEYWORD_NONE)) {
+ /*
+ * This is a simple XML tag, read until the closing statement
+ *
+ *
+ */
+ readToken(token);
+ while (!isType(token, TOKEN_GREATER_THAN)) {
+ readToken(token);
+ }
+ }
+ } else {
+ parseActionScript(token);
+ }
+ } while (TRUE);
+}
+
+static void initialize(const langType language) {
+ Assert(sizeof(FlexKinds) / sizeof(FlexKinds[0]) == FLEXTAG_COUNT);
+ Lang_js = language;
+ buildFlexKeywordHash();
+}
+
+static void findFlexTags(void) {
+ tokenInfo *const token = newToken();
+ exception_t exception;
+
+ ClassNames = stringListNew();
+ FunctionNames = stringListNew();
+
+ exception = (exception_t)(setjmp(Exception));
+ while (exception == ExceptionNone) parseFlexFile(token);
+
+ stringListDelete(ClassNames);
+ stringListDelete(FunctionNames);
+ ClassNames = NULL;
+ FunctionNames = NULL;
+ deleteToken(token);
+}
+
+/* Create parser definition stucture */
+extern parserDefinition *FlexParser(void) {
+ static const char *const extensions[] = {"as", "mxml", NULL};
+ parserDefinition *const def = parserNew("Flex");
+ def->extensions = extensions;
+ /*
+ * New definitions for parsing instead of regex
+ */
+ def->kinds = FlexKinds;
+ def->kindCount = KIND_COUNT(FlexKinds);
+ def->parser = findFlexTags;
+ def->initialize = initialize;
+
+ return def;
+}
+/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */
diff --git a/third_party/ctags/fortran.c b/third_party/ctags/fortran.c
new file mode 100644
index 000000000..b8a1083d8
--- /dev/null
+++ b/third_party/ctags/fortran.c
@@ -0,0 +1,2026 @@
+/*
+ * $Id: fortran.c 660 2008-04-20 23:30:12Z elliotth $
+ *
+ * Copyright (c) 1998-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for Fortran language
+ * files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * MACROS
+ */
+#define isident(c) (isalnum(c) || (c) == '_')
+#define isBlank(c) (boolean)(c == ' ' || c == '\t')
+#define isType(token, t) (boolean)((token)->type == (t))
+#define isKeyword(token, k) (boolean)((token)->keyword == (k))
+#define isSecondaryKeyword(token, k) \
+ (boolean)((token)->secondary == NULL ? FALSE \
+ : (token)->secondary->keyword == (k))
+
+/*
+ * DATA DECLARATIONS
+ */
+
+typedef enum eException {
+ ExceptionNone,
+ ExceptionEOF,
+ ExceptionFixedFormat,
+ ExceptionLoop
+} exception_t;
+
+/* Used to designate type of line read in fixed source form.
+ */
+typedef enum eFortranLineType {
+ LTYPE_UNDETERMINED,
+ LTYPE_INVALID,
+ LTYPE_COMMENT,
+ LTYPE_CONTINUATION,
+ LTYPE_EOF,
+ LTYPE_INITIAL,
+ LTYPE_SHORT
+} lineType;
+
+/* Used to specify type of keyword.
+ */
+typedef enum eKeywordId {
+ KEYWORD_NONE = -1,
+ KEYWORD_allocatable,
+ KEYWORD_assignment,
+ KEYWORD_automatic,
+ KEYWORD_block,
+ KEYWORD_byte,
+ KEYWORD_cexternal,
+ KEYWORD_cglobal,
+ KEYWORD_character,
+ KEYWORD_common,
+ KEYWORD_complex,
+ KEYWORD_contains,
+ KEYWORD_data,
+ KEYWORD_dimension,
+ KEYWORD_dllexport,
+ KEYWORD_dllimport,
+ KEYWORD_do,
+ KEYWORD_double,
+ KEYWORD_elemental,
+ KEYWORD_end,
+ KEYWORD_entry,
+ KEYWORD_equivalence,
+ KEYWORD_external,
+ KEYWORD_format,
+ KEYWORD_function,
+ KEYWORD_if,
+ KEYWORD_implicit,
+ KEYWORD_include,
+ KEYWORD_inline,
+ KEYWORD_integer,
+ KEYWORD_intent,
+ KEYWORD_interface,
+ KEYWORD_intrinsic,
+ KEYWORD_logical,
+ KEYWORD_map,
+ KEYWORD_module,
+ KEYWORD_namelist,
+ KEYWORD_operator,
+ KEYWORD_optional,
+ KEYWORD_parameter,
+ KEYWORD_pascal,
+ KEYWORD_pexternal,
+ KEYWORD_pglobal,
+ KEYWORD_pointer,
+ KEYWORD_precision,
+ KEYWORD_private,
+ KEYWORD_program,
+ KEYWORD_public,
+ KEYWORD_pure,
+ KEYWORD_real,
+ KEYWORD_record,
+ KEYWORD_recursive,
+ KEYWORD_save,
+ KEYWORD_select,
+ KEYWORD_sequence,
+ KEYWORD_static,
+ KEYWORD_stdcall,
+ KEYWORD_structure,
+ KEYWORD_subroutine,
+ KEYWORD_target,
+ KEYWORD_then,
+ KEYWORD_type,
+ KEYWORD_union,
+ KEYWORD_use,
+ KEYWORD_value,
+ KEYWORD_virtual,
+ KEYWORD_volatile,
+ KEYWORD_where,
+ KEYWORD_while
+} keywordId;
+
+/* Used to determine whether keyword is valid for the token language and
+ * what its ID is.
+ */
+typedef struct sKeywordDesc {
+ const char *name;
+ keywordId id;
+} keywordDesc;
+
+typedef enum eTokenType {
+ TOKEN_UNDEFINED,
+ TOKEN_COMMA,
+ TOKEN_DOUBLE_COLON,
+ TOKEN_IDENTIFIER,
+ TOKEN_KEYWORD,
+ TOKEN_LABEL,
+ TOKEN_NUMERIC,
+ TOKEN_OPERATOR,
+ TOKEN_PAREN_CLOSE,
+ TOKEN_PAREN_OPEN,
+ TOKEN_PERCENT,
+ TOKEN_STATEMENT_END,
+ TOKEN_STRING
+} tokenType;
+
+typedef enum eTagType {
+ TAG_UNDEFINED = -1,
+ TAG_BLOCK_DATA,
+ TAG_COMMON_BLOCK,
+ TAG_ENTRY_POINT,
+ TAG_FUNCTION,
+ TAG_INTERFACE,
+ TAG_COMPONENT,
+ TAG_LABEL,
+ TAG_LOCAL,
+ TAG_MODULE,
+ TAG_NAMELIST,
+ TAG_PROGRAM,
+ TAG_SUBROUTINE,
+ TAG_DERIVED_TYPE,
+ TAG_VARIABLE,
+ TAG_COUNT /* must be last */
+} tagType;
+
+typedef struct sTokenInfo {
+ tokenType type;
+ keywordId keyword;
+ tagType tag;
+ vString *string;
+ struct sTokenInfo *secondary;
+ unsigned long lineNumber;
+ fpos_t filePosition;
+} tokenInfo;
+
+/*
+ * DATA DEFINITIONS
+ */
+
+static langType Lang_fortran;
+static jmp_buf Exception;
+static int Ungetc;
+static unsigned int Column;
+static boolean FreeSourceForm;
+static boolean ParsingString;
+static tokenInfo *Parent;
+
+/* indexed by tagType */
+static kindOption FortranKinds[] = {
+ {TRUE, 'b', "block data", "block data"},
+ {TRUE, 'c', "common", "common blocks"},
+ {TRUE, 'e', "entry", "entry points"},
+ {TRUE, 'f', "function", "functions"},
+ {FALSE, 'i', "interface",
+ "interface contents, generic names, and operators"},
+ {TRUE, 'k', "component", "type and structure components"},
+ {TRUE, 'l', "label", "labels"},
+ {FALSE, 'L', "local", "local, common block, and namelist variables"},
+ {TRUE, 'm', "module", "modules"},
+ {TRUE, 'n', "namelist", "namelists"},
+ {TRUE, 'p', "program", "programs"},
+ {TRUE, 's', "subroutine", "subroutines"},
+ {TRUE, 't', "type", "derived types and structures"},
+ {TRUE, 'v', "variable", "program (global) and module variables"}};
+
+/* For efinitions of Fortran 77 with extensions:
+ * http://www.fortran.com/fortran/F77_std/rjcnf0001.html
+ * http://scienide.uwaterloo.ca/MIPSpro7/007-2362-004/sgi_html/index.html
+ *
+ * For the Compaq Fortran Reference Manual:
+ * http://h18009.www1.hp.com/fortran/docs/lrm/dflrm.htm
+ */
+
+static const keywordDesc FortranKeywordTable[] = {
+ /* keyword keyword ID */
+ {"allocatable", KEYWORD_allocatable},
+ {"assignment", KEYWORD_assignment},
+ {"automatic", KEYWORD_automatic},
+ {"block", KEYWORD_block},
+ {"byte", KEYWORD_byte},
+ {"cexternal", KEYWORD_cexternal},
+ {"cglobal", KEYWORD_cglobal},
+ {"character", KEYWORD_character},
+ {"common", KEYWORD_common},
+ {"complex", KEYWORD_complex},
+ {"contains", KEYWORD_contains},
+ {"data", KEYWORD_data},
+ {"dimension", KEYWORD_dimension},
+ {"dll_export", KEYWORD_dllexport},
+ {"dll_import", KEYWORD_dllimport},
+ {"do", KEYWORD_do},
+ {"double", KEYWORD_double},
+ {"elemental", KEYWORD_elemental},
+ {"end", KEYWORD_end},
+ {"entry", KEYWORD_entry},
+ {"equivalence", KEYWORD_equivalence},
+ {"external", KEYWORD_external},
+ {"format", KEYWORD_format},
+ {"function", KEYWORD_function},
+ {"if", KEYWORD_if},
+ {"implicit", KEYWORD_implicit},
+ {"include", KEYWORD_include},
+ {"inline", KEYWORD_inline},
+ {"integer", KEYWORD_integer},
+ {"intent", KEYWORD_intent},
+ {"interface", KEYWORD_interface},
+ {"intrinsic", KEYWORD_intrinsic},
+ {"logical", KEYWORD_logical},
+ {"map", KEYWORD_map},
+ {"module", KEYWORD_module},
+ {"namelist", KEYWORD_namelist},
+ {"operator", KEYWORD_operator},
+ {"optional", KEYWORD_optional},
+ {"parameter", KEYWORD_parameter},
+ {"pascal", KEYWORD_pascal},
+ {"pexternal", KEYWORD_pexternal},
+ {"pglobal", KEYWORD_pglobal},
+ {"pointer", KEYWORD_pointer},
+ {"precision", KEYWORD_precision},
+ {"private", KEYWORD_private},
+ {"program", KEYWORD_program},
+ {"public", KEYWORD_public},
+ {"pure", KEYWORD_pure},
+ {"real", KEYWORD_real},
+ {"record", KEYWORD_record},
+ {"recursive", KEYWORD_recursive},
+ {"save", KEYWORD_save},
+ {"select", KEYWORD_select},
+ {"sequence", KEYWORD_sequence},
+ {"static", KEYWORD_static},
+ {"stdcall", KEYWORD_stdcall},
+ {"structure", KEYWORD_structure},
+ {"subroutine", KEYWORD_subroutine},
+ {"target", KEYWORD_target},
+ {"then", KEYWORD_then},
+ {"type", KEYWORD_type},
+ {"union", KEYWORD_union},
+ {"use", KEYWORD_use},
+ {"value", KEYWORD_value},
+ {"virtual", KEYWORD_virtual},
+ {"volatile", KEYWORD_volatile},
+ {"where", KEYWORD_where},
+ {"while", KEYWORD_while}};
+
+static struct {
+ unsigned int count;
+ unsigned int max;
+ tokenInfo *list;
+} Ancestors = {0, 0, NULL};
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+static void parseStructureStmt(tokenInfo *const token);
+static void parseUnionStmt(tokenInfo *const token);
+static void parseDerivedTypeDef(tokenInfo *const token);
+static void parseFunctionSubprogram(tokenInfo *const token);
+static void parseSubroutineSubprogram(tokenInfo *const token);
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void ancestorPush(tokenInfo *const token) {
+ enum { incrementalIncrease = 10 };
+ if (Ancestors.list == NULL) {
+ Assert(Ancestors.max == 0);
+ Ancestors.count = 0;
+ Ancestors.max = incrementalIncrease;
+ Ancestors.list = xMalloc(Ancestors.max, tokenInfo);
+ } else if (Ancestors.count == Ancestors.max) {
+ Ancestors.max += incrementalIncrease;
+ Ancestors.list = xRealloc(Ancestors.list, Ancestors.max, tokenInfo);
+ }
+ Ancestors.list[Ancestors.count] = *token;
+ Ancestors.list[Ancestors.count].string = vStringNewCopy(token->string);
+ Ancestors.count++;
+}
+
+static void ancestorPop(void) {
+ Assert(Ancestors.count > 0);
+ --Ancestors.count;
+ vStringDelete(Ancestors.list[Ancestors.count].string);
+
+ Ancestors.list[Ancestors.count].type = TOKEN_UNDEFINED;
+ Ancestors.list[Ancestors.count].keyword = KEYWORD_NONE;
+ Ancestors.list[Ancestors.count].secondary = NULL;
+ Ancestors.list[Ancestors.count].tag = TAG_UNDEFINED;
+ Ancestors.list[Ancestors.count].string = NULL;
+ Ancestors.list[Ancestors.count].lineNumber = 0L;
+}
+
+static const tokenInfo *ancestorScope(void) {
+ tokenInfo *result = NULL;
+ unsigned int i;
+ for (i = Ancestors.count; i > 0 && result == NULL; --i) {
+ tokenInfo *const token = Ancestors.list + i - 1;
+ if (token->type == TOKEN_IDENTIFIER && token->tag != TAG_UNDEFINED &&
+ token->tag != TAG_INTERFACE)
+ result = token;
+ }
+ return result;
+}
+
+static const tokenInfo *ancestorTop(void) {
+ Assert(Ancestors.count > 0);
+ return &Ancestors.list[Ancestors.count - 1];
+}
+
+#define ancestorCount() (Ancestors.count)
+
+static void ancestorClear(void) {
+ while (Ancestors.count > 0) ancestorPop();
+ if (Ancestors.list != NULL) eFree(Ancestors.list);
+ Ancestors.list = NULL;
+ Ancestors.count = 0;
+ Ancestors.max = 0;
+}
+
+static boolean insideInterface(void) {
+ boolean result = FALSE;
+ unsigned int i;
+ for (i = 0; i < Ancestors.count && !result; ++i) {
+ if (Ancestors.list[i].tag == TAG_INTERFACE) result = TRUE;
+ }
+ return result;
+}
+
+static void buildFortranKeywordHash(void) {
+ const size_t count =
+ sizeof(FortranKeywordTable) / sizeof(FortranKeywordTable[0]);
+ size_t i;
+ for (i = 0; i < count; ++i) {
+ const keywordDesc *const p = &FortranKeywordTable[i];
+ addKeyword(p->name, Lang_fortran, (int)p->id);
+ }
+}
+
+/*
+ * Tag generation functions
+ */
+
+static tokenInfo *newToken(void) {
+ tokenInfo *const token = xMalloc(1, tokenInfo);
+
+ token->type = TOKEN_UNDEFINED;
+ token->keyword = KEYWORD_NONE;
+ token->tag = TAG_UNDEFINED;
+ token->string = vStringNew();
+ token->secondary = NULL;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+
+ return token;
+}
+
+static tokenInfo *newTokenFrom(tokenInfo *const token) {
+ tokenInfo *result = newToken();
+ *result = *token;
+ result->string = vStringNewCopy(token->string);
+ token->secondary = NULL;
+ return result;
+}
+
+static void deleteToken(tokenInfo *const token) {
+ if (token != NULL) {
+ vStringDelete(token->string);
+ deleteToken(token->secondary);
+ token->secondary = NULL;
+ eFree(token);
+ }
+}
+
+static boolean isFileScope(const tagType type) {
+ return (boolean)(type == TAG_LABEL || type == TAG_LOCAL);
+}
+
+static boolean includeTag(const tagType type) {
+ boolean include;
+ Assert(type != TAG_UNDEFINED);
+ include = FortranKinds[(int)type].enabled;
+ if (include && isFileScope(type)) include = Option.include.fileScope;
+ return include;
+}
+
+static void makeFortranTag(tokenInfo *const token, tagType tag) {
+ token->tag = tag;
+ if (includeTag(token->tag)) {
+ const char *const name = vStringValue(token->string);
+ tagEntryInfo e;
+
+ initTagEntry(&e, name);
+
+ if (token->tag == TAG_COMMON_BLOCK)
+ e.lineNumberEntry = (boolean)(Option.locate != EX_PATTERN);
+
+ e.lineNumber = token->lineNumber;
+ e.filePosition = token->filePosition;
+ e.isFileScope = isFileScope(token->tag);
+ e.kindName = FortranKinds[token->tag].name;
+ e.kind = FortranKinds[token->tag].letter;
+ e.truncateLine = (boolean)(token->tag != TAG_LABEL);
+
+ if (ancestorCount() > 0) {
+ const tokenInfo *const scope = ancestorScope();
+ if (scope != NULL) {
+ e.extensionFields.scope[0] = FortranKinds[scope->tag].name;
+ e.extensionFields.scope[1] = vStringValue(scope->string);
+ }
+ }
+ if (!insideInterface() || includeTag(TAG_INTERFACE)) makeTagEntry(&e);
+ }
+}
+
+/*
+ * Parsing functions
+ */
+
+static int skipLine(void) {
+ int c;
+
+ do
+ c = fileGetc();
+ while (c != EOF && c != '\n');
+
+ return c;
+}
+
+static void makeLabelTag(vString *const label) {
+ tokenInfo *token = newToken();
+ token->type = TOKEN_LABEL;
+ vStringCopy(token->string, label);
+ makeFortranTag(token, TAG_LABEL);
+ deleteToken(token);
+}
+
+static lineType getLineType(void) {
+ vString *label = vStringNew();
+ int column = 0;
+ lineType type = LTYPE_UNDETERMINED;
+
+ do /* read in first 6 "margin" characters */
+ {
+ int c = fileGetc();
+
+ /* 3.2.1 Comment_Line. A comment line is any line that contains
+ * a C or an asterisk in column 1, or contains only blank characters
+ * in columns 1 through 72. A comment line that contains a C or
+ * an asterisk in column 1 may contain any character capable of
+ * representation in the processor in columns 2 through 72.
+ */
+ /* EXCEPTION! Some compilers permit '!' as a commment character here.
+ *
+ * Treat # and $ in column 1 as comment to permit preprocessor directives.
+ * Treat D and d in column 1 as comment for HP debug statements.
+ */
+ if (column == 0 && strchr("*Cc!#$Dd", c) != NULL)
+ type = LTYPE_COMMENT;
+ else if (c == '\t') /* EXCEPTION! Some compilers permit a tab here */
+ {
+ column = 8;
+ type = LTYPE_INITIAL;
+ } else if (column == 5) {
+ /* 3.2.2 Initial_Line. An initial line is any line that is not
+ * a comment line and contains the character blank or the digit 0
+ * in column 6. Columns 1 through 5 may contain a statement label
+ * (3.4), or each of the columns 1 through 5 must contain the
+ * character blank.
+ */
+ if (c == ' ' || c == '0') type = LTYPE_INITIAL;
+
+ /* 3.2.3 Continuation_Line. A continuation line is any line that
+ * contains any character of the FORTRAN character set other than
+ * the character blank or the digit 0 in column 6 and contains
+ * only blank characters in columns 1 through 5.
+ */
+ else if (vStringLength(label) == 0)
+ type = LTYPE_CONTINUATION;
+ else
+ type = LTYPE_INVALID;
+ } else if (c == ' ')
+ ;
+ else if (c == EOF)
+ type = LTYPE_EOF;
+ else if (c == '\n')
+ type = LTYPE_SHORT;
+ else if (isdigit(c))
+ vStringPut(label, c);
+ else
+ type = LTYPE_INVALID;
+
+ ++column;
+ } while (column < 6 && type == LTYPE_UNDETERMINED);
+
+ Assert(type != LTYPE_UNDETERMINED);
+
+ if (vStringLength(label) > 0) {
+ vStringTerminate(label);
+ makeLabelTag(label);
+ }
+ vStringDelete(label);
+ return type;
+}
+
+static int getFixedFormChar(void) {
+ boolean newline = FALSE;
+ lineType type;
+ int c = '\0';
+
+ if (Column > 0) {
+#ifdef STRICT_FIXED_FORM
+ /* EXCEPTION! Some compilers permit more than 72 characters per line.
+ */
+ if (Column > 71)
+ c = skipLine();
+ else
+#endif
+ {
+ c = fileGetc();
+ ++Column;
+ }
+ if (c == '\n') {
+ newline = TRUE; /* need to check for continuation line */
+ Column = 0;
+ } else if (c == '!' && !ParsingString) {
+ c = skipLine();
+ newline = TRUE; /* need to check for continuation line */
+ Column = 0;
+ } else if (c == '&') /* check for free source form */
+ {
+ const int c2 = fileGetc();
+ if (c2 == '\n')
+ longjmp(Exception, (int)ExceptionFixedFormat);
+ else
+ fileUngetc(c2);
+ }
+ }
+ while (Column == 0) {
+ type = getLineType();
+ switch (type) {
+ case LTYPE_UNDETERMINED:
+ case LTYPE_INVALID:
+ longjmp(Exception, (int)ExceptionFixedFormat);
+ break;
+
+ case LTYPE_SHORT:
+ break;
+ case LTYPE_COMMENT:
+ skipLine();
+ break;
+
+ case LTYPE_EOF:
+ Column = 6;
+ if (newline)
+ c = '\n';
+ else
+ c = EOF;
+ break;
+
+ case LTYPE_INITIAL:
+ if (newline) {
+ c = '\n';
+ Column = 6;
+ break;
+ }
+ /* fall through to next case */
+ case LTYPE_CONTINUATION:
+ Column = 5;
+ do {
+ c = fileGetc();
+ ++Column;
+ } while (isBlank(c));
+ if (c == '\n')
+ Column = 0;
+ else if (Column > 6) {
+ fileUngetc(c);
+ c = ' ';
+ }
+ break;
+
+ default:
+ Assert("Unexpected line type" == NULL);
+ }
+ }
+ return c;
+}
+
+static int skipToNextLine(void) {
+ int c = skipLine();
+ if (c != EOF) c = fileGetc();
+ return c;
+}
+
+static int getFreeFormChar(void) {
+ static boolean newline = TRUE;
+ boolean advanceLine = FALSE;
+ int c = fileGetc();
+
+ /* If the last nonblank, non-comment character of a FORTRAN 90
+ * free-format text line is an ampersand then the next non-comment
+ * line is a continuation line.
+ */
+ if (c == '&') {
+ do
+ c = fileGetc();
+ while (isspace(c) && c != '\n');
+ if (c == '\n') {
+ newline = TRUE;
+ advanceLine = TRUE;
+ } else if (c == '!')
+ advanceLine = TRUE;
+ else {
+ fileUngetc(c);
+ c = '&';
+ }
+ } else if (newline && (c == '!' || c == '#'))
+ advanceLine = TRUE;
+ while (advanceLine) {
+ while (isspace(c)) c = fileGetc();
+ if (c == '!' || (newline && c == '#')) {
+ c = skipToNextLine();
+ newline = TRUE;
+ continue;
+ }
+ if (c == '&')
+ c = fileGetc();
+ else
+ advanceLine = FALSE;
+ }
+ newline = (boolean)(c == '\n');
+ return c;
+}
+
+static int getChar(void) {
+ int c;
+
+ if (Ungetc != '\0') {
+ c = Ungetc;
+ Ungetc = '\0';
+ } else if (FreeSourceForm)
+ c = getFreeFormChar();
+ else
+ c = getFixedFormChar();
+ return c;
+}
+
+static void ungetChar(const int c) {
+ Ungetc = c;
+}
+
+/* If a numeric is passed in 'c', this is used as the first digit of the
+ * numeric being parsed.
+ */
+static vString *parseInteger(int c) {
+ vString *string = vStringNew();
+
+ if (c == '-') {
+ vStringPut(string, c);
+ c = getChar();
+ } else if (!isdigit(c))
+ c = getChar();
+ while (c != EOF && isdigit(c)) {
+ vStringPut(string, c);
+ c = getChar();
+ }
+ vStringTerminate(string);
+
+ if (c == '_') {
+ do
+ c = getChar();
+ while (c != EOF && isalpha(c));
+ }
+ ungetChar(c);
+
+ return string;
+}
+
+static vString *parseNumeric(int c) {
+ vString *string = vStringNew();
+ vString *integer = parseInteger(c);
+ vStringCopy(string, integer);
+ vStringDelete(integer);
+
+ c = getChar();
+ if (c == '.') {
+ integer = parseInteger('\0');
+ vStringPut(string, c);
+ vStringCat(string, integer);
+ vStringDelete(integer);
+ c = getChar();
+ }
+ if (tolower(c) == 'e') {
+ integer = parseInteger('\0');
+ vStringPut(string, c);
+ vStringCat(string, integer);
+ vStringDelete(integer);
+ } else
+ ungetChar(c);
+
+ vStringTerminate(string);
+
+ return string;
+}
+
+static void parseString(vString *const string, const int delimiter) {
+ const unsigned long inputLineNumber = getInputLineNumber();
+ int c;
+ ParsingString = TRUE;
+ c = getChar();
+ while (c != delimiter && c != '\n' && c != EOF) {
+ vStringPut(string, c);
+ c = getChar();
+ }
+ if (c == '\n' || c == EOF) {
+ verbose("%s: unterminated character string at line %lu\n",
+ getInputFileName(), inputLineNumber);
+ if (c == EOF)
+ longjmp(Exception, (int)ExceptionEOF);
+ else if (!FreeSourceForm)
+ longjmp(Exception, (int)ExceptionFixedFormat);
+ }
+ vStringTerminate(string);
+ ParsingString = FALSE;
+}
+
+/* Read a C identifier beginning with "firstChar" and places it into "name".
+ */
+static void parseIdentifier(vString *const string, const int firstChar) {
+ int c = firstChar;
+
+ do {
+ vStringPut(string, c);
+ c = getChar();
+ } while (isident(c));
+
+ vStringTerminate(string);
+ ungetChar(c); /* unget non-identifier character */
+}
+
+static void checkForLabel(void) {
+ tokenInfo *token = NULL;
+ int length;
+ int c;
+
+ do
+ c = getChar();
+ while (isBlank(c));
+
+ for (length = 0; isdigit(c) && length < 5; ++length) {
+ if (token == NULL) {
+ token = newToken();
+ token->type = TOKEN_LABEL;
+ }
+ vStringPut(token->string, c);
+ c = getChar();
+ }
+ if (length > 0 && token != NULL) {
+ vStringTerminate(token->string);
+ makeFortranTag(token, TAG_LABEL);
+ deleteToken(token);
+ }
+ ungetChar(c);
+}
+
+static void readIdentifier(tokenInfo *const token, const int c) {
+ parseIdentifier(token->string, c);
+ token->keyword = analyzeToken(token->string, Lang_fortran);
+ if (!isKeyword(token, KEYWORD_NONE))
+ token->type = TOKEN_KEYWORD;
+ else {
+ token->type = TOKEN_IDENTIFIER;
+ if (strncmp(vStringValue(token->string), "end", 3) == 0) {
+ vString *const sub = vStringNewInit(vStringValue(token->string) + 3);
+ const keywordId kw = analyzeToken(sub, Lang_fortran);
+ vStringDelete(sub);
+ if (kw != KEYWORD_NONE) {
+ token->secondary = newToken();
+ token->secondary->type = TOKEN_KEYWORD;
+ token->secondary->keyword = kw;
+ token->keyword = KEYWORD_end;
+ }
+ }
+ }
+}
+
+static void readToken(tokenInfo *const token) {
+ int c;
+
+ deleteToken(token->secondary);
+ token->type = TOKEN_UNDEFINED;
+ token->tag = TAG_UNDEFINED;
+ token->keyword = KEYWORD_NONE;
+ token->secondary = NULL;
+ vStringClear(token->string);
+
+getNextChar:
+ c = getChar();
+
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+
+ switch (c) {
+ case EOF:
+ longjmp(Exception, (int)ExceptionEOF);
+ break;
+ case ' ':
+ goto getNextChar;
+ case '\t':
+ goto getNextChar;
+ case ',':
+ token->type = TOKEN_COMMA;
+ break;
+ case '(':
+ token->type = TOKEN_PAREN_OPEN;
+ break;
+ case ')':
+ token->type = TOKEN_PAREN_CLOSE;
+ break;
+ case '%':
+ token->type = TOKEN_PERCENT;
+ break;
+
+ case '*':
+ case '/':
+ case '+':
+ case '-':
+ case '=':
+ case '<':
+ case '>': {
+ const char *const operatorChars = "*/+=<>";
+ do {
+ vStringPut(token->string, c);
+ c = getChar();
+ } while (strchr(operatorChars, c) != NULL);
+ ungetChar(c);
+ vStringTerminate(token->string);
+ token->type = TOKEN_OPERATOR;
+ break;
+ }
+
+ case '!':
+ if (FreeSourceForm) {
+ do
+ c = getChar();
+ while (c != '\n' && c != EOF);
+ } else {
+ skipLine();
+ Column = 0;
+ }
+ /* fall through to newline case */
+ case '\n':
+ token->type = TOKEN_STATEMENT_END;
+ if (FreeSourceForm) checkForLabel();
+ break;
+
+ case '.':
+ parseIdentifier(token->string, c);
+ c = getChar();
+ if (c == '.') {
+ vStringPut(token->string, c);
+ vStringTerminate(token->string);
+ token->type = TOKEN_OPERATOR;
+ } else {
+ ungetChar(c);
+ token->type = TOKEN_UNDEFINED;
+ }
+ break;
+
+ case '"':
+ case '\'':
+ parseString(token->string, c);
+ token->type = TOKEN_STRING;
+ break;
+
+ case ';':
+ token->type = TOKEN_STATEMENT_END;
+ break;
+
+ case ':':
+ c = getChar();
+ if (c == ':')
+ token->type = TOKEN_DOUBLE_COLON;
+ else {
+ ungetChar(c);
+ token->type = TOKEN_UNDEFINED;
+ }
+ break;
+
+ default:
+ if (isalpha(c))
+ readIdentifier(token, c);
+ else if (isdigit(c)) {
+ vString *numeric = parseNumeric(c);
+ vStringCat(token->string, numeric);
+ vStringDelete(numeric);
+ token->type = TOKEN_NUMERIC;
+ } else
+ token->type = TOKEN_UNDEFINED;
+ break;
+ }
+}
+
+static void readSubToken(tokenInfo *const token) {
+ if (token->secondary == NULL) {
+ token->secondary = newToken();
+ readToken(token->secondary);
+ }
+}
+
+/*
+ * Scanning functions
+ */
+
+static void skipToToken(tokenInfo *const token, tokenType type) {
+ while (!isType(token, type) && !isType(token, TOKEN_STATEMENT_END) &&
+ !(token->secondary != NULL &&
+ isType(token->secondary, TOKEN_STATEMENT_END)))
+ readToken(token);
+}
+
+static void skipPast(tokenInfo *const token, tokenType type) {
+ skipToToken(token, type);
+ if (!isType(token, TOKEN_STATEMENT_END)) readToken(token);
+}
+
+static void skipToNextStatement(tokenInfo *const token) {
+ do {
+ skipToToken(token, TOKEN_STATEMENT_END);
+ readToken(token);
+ } while (isType(token, TOKEN_STATEMENT_END));
+}
+
+/* skip over parenthesis enclosed contents starting at next token.
+ * Token is left at the first token following closing parenthesis. If an
+ * opening parenthesis is not found, `token' is moved to the end of the
+ * statement.
+ */
+static void skipOverParens(tokenInfo *const token) {
+ int level = 0;
+ do {
+ if (isType(token, TOKEN_STATEMENT_END))
+ break;
+ else if (isType(token, TOKEN_PAREN_OPEN))
+ ++level;
+ else if (isType(token, TOKEN_PAREN_CLOSE))
+ --level;
+ readToken(token);
+ } while (level > 0);
+}
+
+static boolean isTypeSpec(tokenInfo *const token) {
+ boolean result;
+ switch (token->keyword) {
+ case KEYWORD_byte:
+ case KEYWORD_integer:
+ case KEYWORD_real:
+ case KEYWORD_double:
+ case KEYWORD_complex:
+ case KEYWORD_character:
+ case KEYWORD_logical:
+ case KEYWORD_record:
+ case KEYWORD_type:
+ result = TRUE;
+ break;
+ default:
+ result = FALSE;
+ break;
+ }
+ return result;
+}
+
+static boolean isSubprogramPrefix(tokenInfo *const token) {
+ boolean result;
+ switch (token->keyword) {
+ case KEYWORD_elemental:
+ case KEYWORD_pure:
+ case KEYWORD_recursive:
+ case KEYWORD_stdcall:
+ result = TRUE;
+ break;
+ default:
+ result = FALSE;
+ break;
+ }
+ return result;
+}
+
+/* type-spec
+ * is INTEGER [kind-selector]
+ * or REAL [kind-selector] is ( etc. )
+ * or DOUBLE PRECISION
+ * or COMPLEX [kind-selector]
+ * or CHARACTER [kind-selector]
+ * or LOGICAL [kind-selector]
+ * or TYPE ( type-name )
+ *
+ * Note that INTEGER and REAL may be followed by "*N" where "N" is an integer
+ */
+static void parseTypeSpec(tokenInfo *const token) {
+ /* parse type-spec, leaving `token' at first token following type-spec */
+ Assert(isTypeSpec(token));
+ switch (token->keyword) {
+ case KEYWORD_character:
+ /* skip char-selector */
+ readToken(token);
+ if (isType(token, TOKEN_OPERATOR) &&
+ strcmp(vStringValue(token->string), "*") == 0)
+ readToken(token);
+ if (isType(token, TOKEN_PAREN_OPEN))
+ skipOverParens(token);
+ else if (isType(token, TOKEN_NUMERIC))
+ readToken(token);
+ break;
+
+ case KEYWORD_byte:
+ case KEYWORD_complex:
+ case KEYWORD_integer:
+ case KEYWORD_logical:
+ case KEYWORD_real:
+ readToken(token);
+ if (isType(token, TOKEN_PAREN_OPEN))
+ skipOverParens(token); /* skip kind-selector */
+ if (isType(token, TOKEN_OPERATOR) &&
+ strcmp(vStringValue(token->string), "*") == 0) {
+ readToken(token);
+ readToken(token);
+ }
+ break;
+
+ case KEYWORD_double:
+ readToken(token);
+ if (isKeyword(token, KEYWORD_complex) ||
+ isKeyword(token, KEYWORD_precision))
+ readToken(token);
+ else
+ skipToToken(token, TOKEN_STATEMENT_END);
+ break;
+
+ case KEYWORD_record:
+ readToken(token);
+ if (isType(token, TOKEN_OPERATOR) &&
+ strcmp(vStringValue(token->string), "/") == 0) {
+ readToken(token); /* skip to structure name */
+ readToken(token); /* skip to '/' */
+ readToken(token); /* skip to variable name */
+ }
+ break;
+
+ case KEYWORD_type:
+ readToken(token);
+ if (isType(token, TOKEN_PAREN_OPEN))
+ skipOverParens(token); /* skip type-name */
+ else
+ parseDerivedTypeDef(token);
+ break;
+
+ default:
+ skipToToken(token, TOKEN_STATEMENT_END);
+ break;
+ }
+}
+
+static boolean skipStatementIfKeyword(tokenInfo *const token,
+ keywordId keyword) {
+ boolean result = FALSE;
+ if (isKeyword(token, keyword)) {
+ result = TRUE;
+ skipToNextStatement(token);
+ }
+ return result;
+}
+
+/* parse a list of qualifying specifiers, leaving `token' at first token
+ * following list. Examples of such specifiers are:
+ * [[, attr-spec] ::]
+ * [[, component-attr-spec-list] ::]
+ *
+ * attr-spec
+ * is PARAMETER
+ * or access-spec (is PUBLIC or PRIVATE)
+ * or ALLOCATABLE
+ * or DIMENSION ( array-spec )
+ * or EXTERNAL
+ * or INTENT ( intent-spec )
+ * or INTRINSIC
+ * or OPTIONAL
+ * or POINTER
+ * or SAVE
+ * or TARGET
+ *
+ * component-attr-spec
+ * is POINTER
+ * or DIMENSION ( component-array-spec )
+ */
+static void parseQualifierSpecList(tokenInfo *const token) {
+ do {
+ readToken(token); /* should be an attr-spec */
+ switch (token->keyword) {
+ case KEYWORD_parameter:
+ case KEYWORD_allocatable:
+ case KEYWORD_external:
+ case KEYWORD_intrinsic:
+ case KEYWORD_optional:
+ case KEYWORD_private:
+ case KEYWORD_pointer:
+ case KEYWORD_public:
+ case KEYWORD_save:
+ case KEYWORD_target:
+ readToken(token);
+ break;
+
+ case KEYWORD_dimension:
+ case KEYWORD_intent:
+ readToken(token);
+ skipOverParens(token);
+ break;
+
+ default:
+ skipToToken(token, TOKEN_STATEMENT_END);
+ break;
+ }
+ } while (isType(token, TOKEN_COMMA));
+ if (!isType(token, TOKEN_DOUBLE_COLON))
+ skipToToken(token, TOKEN_STATEMENT_END);
+}
+
+static tagType variableTagType(void) {
+ tagType result = TAG_VARIABLE;
+ if (ancestorCount() > 0) {
+ const tokenInfo *const parent = ancestorTop();
+ switch (parent->tag) {
+ case TAG_MODULE:
+ result = TAG_VARIABLE;
+ break;
+ case TAG_DERIVED_TYPE:
+ result = TAG_COMPONENT;
+ break;
+ case TAG_FUNCTION:
+ result = TAG_LOCAL;
+ break;
+ case TAG_SUBROUTINE:
+ result = TAG_LOCAL;
+ break;
+ default:
+ result = TAG_VARIABLE;
+ break;
+ }
+ }
+ return result;
+}
+
+static void parseEntityDecl(tokenInfo *const token) {
+ Assert(isType(token, TOKEN_IDENTIFIER));
+ makeFortranTag(token, variableTagType());
+ readToken(token);
+ if (isType(token, TOKEN_PAREN_OPEN)) skipOverParens(token);
+ if (isType(token, TOKEN_OPERATOR) &&
+ strcmp(vStringValue(token->string), "*") == 0) {
+ readToken(token); /* read char-length */
+ if (isType(token, TOKEN_PAREN_OPEN))
+ skipOverParens(token);
+ else
+ readToken(token);
+ }
+ if (isType(token, TOKEN_OPERATOR)) {
+ if (strcmp(vStringValue(token->string), "/") ==
+ 0) { /* skip over initializations of structure field */
+ readToken(token);
+ skipPast(token, TOKEN_OPERATOR);
+ } else if (strcmp(vStringValue(token->string), "=") == 0) {
+ while (!isType(token, TOKEN_COMMA) &&
+ !isType(token, TOKEN_STATEMENT_END)) {
+ readToken(token);
+ if (isType(token, TOKEN_PAREN_OPEN)) skipOverParens(token);
+ }
+ }
+ }
+ /* token left at either comma or statement end */
+}
+
+static void parseEntityDeclList(tokenInfo *const token) {
+ if (isType(token, TOKEN_PERCENT))
+ skipToNextStatement(token);
+ else
+ while (isType(token, TOKEN_IDENTIFIER) ||
+ (isType(token, TOKEN_KEYWORD) &&
+ !isKeyword(token, KEYWORD_function) &&
+ !isKeyword(token, KEYWORD_subroutine))) {
+ /* compilers accept keywoeds as identifiers */
+ if (isType(token, TOKEN_KEYWORD)) token->type = TOKEN_IDENTIFIER;
+ parseEntityDecl(token);
+ if (isType(token, TOKEN_COMMA))
+ readToken(token);
+ else if (isType(token, TOKEN_STATEMENT_END)) {
+ skipToNextStatement(token);
+ break;
+ }
+ }
+}
+
+/* type-declaration-stmt is
+ * type-spec [[, attr-spec] ... ::] entity-decl-list
+ */
+static void parseTypeDeclarationStmt(tokenInfo *const token) {
+ Assert(isTypeSpec(token));
+ parseTypeSpec(token);
+ if (!isType(token, TOKEN_STATEMENT_END)) /* if not end of derived type... */
+ {
+ if (isType(token, TOKEN_COMMA)) parseQualifierSpecList(token);
+ if (isType(token, TOKEN_DOUBLE_COLON)) readToken(token);
+ parseEntityDeclList(token);
+ }
+ if (isType(token, TOKEN_STATEMENT_END)) skipToNextStatement(token);
+}
+
+/* namelist-stmt is
+ * NAMELIST /namelist-group-name/ namelist-group-object-list
+ * [[,]/[namelist-group-name]/ namelist-block-object-list]
+ *...
+ *
+ * namelist-group-object is
+ * variable-name
+ *
+ * common-stmt is
+ * COMMON [/[common-block-name]/] common-block-object-list
+ * [[,]/[common-block-name]/ common-block-object-list] ...
+ *
+ * common-block-object is
+ * variable-name [ ( explicit-shape-spec-list ) ]
+ */
+static void parseCommonNamelistStmt(tokenInfo *const token, tagType type) {
+ Assert(isKeyword(token, KEYWORD_common) ||
+ isKeyword(token, KEYWORD_namelist));
+ readToken(token);
+ do {
+ if (isType(token, TOKEN_OPERATOR) &&
+ strcmp(vStringValue(token->string), "/") == 0) {
+ readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER)) {
+ makeFortranTag(token, type);
+ readToken(token);
+ }
+ skipPast(token, TOKEN_OPERATOR);
+ }
+ if (isType(token, TOKEN_IDENTIFIER)) makeFortranTag(token, TAG_LOCAL);
+ readToken(token);
+ if (isType(token, TOKEN_PAREN_OPEN))
+ skipOverParens(token); /* skip explicit-shape-spec-list */
+ if (isType(token, TOKEN_COMMA)) readToken(token);
+ } while (!isType(token, TOKEN_STATEMENT_END));
+ skipToNextStatement(token);
+}
+
+static void parseFieldDefinition(tokenInfo *const token) {
+ if (isTypeSpec(token))
+ parseTypeDeclarationStmt(token);
+ else if (isKeyword(token, KEYWORD_structure))
+ parseStructureStmt(token);
+ else if (isKeyword(token, KEYWORD_union))
+ parseUnionStmt(token);
+ else
+ skipToNextStatement(token);
+}
+
+static void parseMap(tokenInfo *const token) {
+ Assert(isKeyword(token, KEYWORD_map));
+ skipToNextStatement(token);
+ while (!isKeyword(token, KEYWORD_end)) parseFieldDefinition(token);
+ readSubToken(token);
+ /* should be at KEYWORD_map token */
+ skipToNextStatement(token);
+}
+
+/* UNION
+ * MAP
+ * [field-definition] [field-definition] ...
+ * END MAP
+ * MAP
+ * [field-definition] [field-definition] ...
+ * END MAP
+ * [MAP
+ * [field-definition]
+ * [field-definition] ...
+ * END MAP] ...
+ * END UNION
+ * *
+ *
+ * Typed data declarations (variables or arrays) in structure declarations
+ * have the form of normal Fortran typed data declarations. Data items with
+ * different types can be freely intermixed within a structure declaration.
+ *
+ * Unnamed fields can be declared in a structure by specifying the pseudo
+ * name %FILL in place of an actual field name. You can use this mechanism to
+ * generate empty space in a record for purposes such as alignment.
+ *
+ * All mapped field declarations that are made within a UNION declaration
+ * share a common location within the containing structure. When initializing
+ * the fields within a UNION, the final initialization value assigned
+ * overlays any value previously assigned to a field definition that shares
+ * that field.
+ */
+static void parseUnionStmt(tokenInfo *const token) {
+ Assert(isKeyword(token, KEYWORD_union));
+ skipToNextStatement(token);
+ while (isKeyword(token, KEYWORD_map)) parseMap(token);
+ /* should be at KEYWORD_end token */
+ readSubToken(token);
+ /* secondary token should be KEYWORD_end token */
+ skipToNextStatement(token);
+}
+
+/* STRUCTURE [/structure-name/] [field-names]
+ * [field-definition]
+ * [field-definition] ...
+ * END STRUCTURE
+ *
+ * structure-name
+ * identifies the structure in a subsequent RECORD statement.
+ * Substructures can be established within a structure by means of
+ *either a nested STRUCTURE declaration or a RECORD statement.
+ *
+ * field-names
+ * (for substructure declarations only) one or more names having
+ *the structure of the substructure being defined.
+ *
+ * field-definition
+ * can be one or more of the following:
+ *
+ * Typed data declarations, which can optionally include one or
+ *more data initialization values.
+ *
+ * Substructure declarations (defined by either RECORD
+ *statements or subsequent STRUCTURE statements).
+ *
+ * UNION declarations, which are mapped fields defined by a
+ *block of statements. The syntax of a UNION declaration is described below.
+ *
+ * PARAMETER statements, which do not affect the form of
+ *the structure.
+ */
+static void parseStructureStmt(tokenInfo *const token) {
+ tokenInfo *name;
+ Assert(isKeyword(token, KEYWORD_structure));
+ readToken(token);
+ if (isType(token, TOKEN_OPERATOR) &&
+ strcmp(vStringValue(token->string), "/") == 0) { /* read structure name */
+ readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER))
+ makeFortranTag(token, TAG_DERIVED_TYPE);
+ name = newTokenFrom(token);
+ skipPast(token, TOKEN_OPERATOR);
+ } else { /* fake out anonymous structure */
+ name = newToken();
+ name->type = TOKEN_IDENTIFIER;
+ name->tag = TAG_DERIVED_TYPE;
+ vStringCopyS(name->string, "anonymous");
+ }
+ while (isType(token, TOKEN_IDENTIFIER)) { /* read field names */
+ makeFortranTag(token, TAG_COMPONENT);
+ readToken(token);
+ if (isType(token, TOKEN_COMMA)) readToken(token);
+ }
+ skipToNextStatement(token);
+ ancestorPush(name);
+ while (!isKeyword(token, KEYWORD_end)) parseFieldDefinition(token);
+ readSubToken(token);
+ /* secondary token should be KEYWORD_structure token */
+ skipToNextStatement(token);
+ ancestorPop();
+ deleteToken(name);
+}
+
+/* specification-stmt
+ * is access-stmt (is access-spec [[::] access-id-list)
+ * or allocatable-stmt (is ALLOCATABLE [::] array-name etc.)
+ * or common-stmt (is COMMON [ / [common-block-name] /] etc.)
+ * or data-stmt (is DATA data-stmt-list [[,] data-stmt-set] ...)
+ * or dimension-stmt (is DIMENSION [::] array-name etc.)
+ * or equivalence-stmt (is EQUIVALENCE equivalence-set-list)
+ * or external-stmt (is EXTERNAL etc.)
+ * or intent-stmt (is INTENT ( intent-spec ) [::] etc.)
+ * or instrinsic-stmt (is INTRINSIC etc.)
+ * or namelist-stmt (is NAMELIST / namelist-group-name / etc.)
+ * or optional-stmt (is OPTIONAL [::] etc.)
+ * or pointer-stmt (is POINTER [::] object-name etc.)
+ * or save-stmt (is SAVE etc.)
+ * or target-stmt (is TARGET [::] object-name etc.)
+ *
+ * access-spec is PUBLIC or PRIVATE
+ */
+static boolean parseSpecificationStmt(tokenInfo *const token) {
+ boolean result = TRUE;
+ switch (token->keyword) {
+ case KEYWORD_common:
+ parseCommonNamelistStmt(token, TAG_COMMON_BLOCK);
+ break;
+
+ case KEYWORD_namelist:
+ parseCommonNamelistStmt(token, TAG_NAMELIST);
+ break;
+
+ case KEYWORD_structure:
+ parseStructureStmt(token);
+ break;
+
+ case KEYWORD_allocatable:
+ case KEYWORD_data:
+ case KEYWORD_dimension:
+ case KEYWORD_equivalence:
+ case KEYWORD_external:
+ case KEYWORD_intent:
+ case KEYWORD_intrinsic:
+ case KEYWORD_optional:
+ case KEYWORD_pointer:
+ case KEYWORD_private:
+ case KEYWORD_public:
+ case KEYWORD_save:
+ case KEYWORD_target:
+ skipToNextStatement(token);
+ break;
+
+ default:
+ result = FALSE;
+ break;
+ }
+ return result;
+}
+
+/* component-def-stmt is
+ * type-spec [[, component-attr-spec-list] ::] component-decl-list
+ *
+ * component-decl is
+ * component-name [ ( component-array-spec ) ] [ * char-length ]
+ */
+static void parseComponentDefStmt(tokenInfo *const token) {
+ Assert(isTypeSpec(token));
+ parseTypeSpec(token);
+ if (isType(token, TOKEN_COMMA)) parseQualifierSpecList(token);
+ if (isType(token, TOKEN_DOUBLE_COLON)) readToken(token);
+ parseEntityDeclList(token);
+}
+
+/* derived-type-def is
+ * derived-type-stmt is (TYPE [[, access-spec] ::] type-name
+ * [private-sequence-stmt] ... (is PRIVATE or SEQUENCE)
+ * component-def-stmt
+ * [component-def-stmt] ...
+ * end-type-stmt
+ */
+static void parseDerivedTypeDef(tokenInfo *const token) {
+ if (isType(token, TOKEN_COMMA)) parseQualifierSpecList(token);
+ if (isType(token, TOKEN_DOUBLE_COLON)) readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER)) makeFortranTag(token, TAG_DERIVED_TYPE);
+ ancestorPush(token);
+ skipToNextStatement(token);
+ if (isKeyword(token, KEYWORD_private) || isKeyword(token, KEYWORD_sequence)) {
+ skipToNextStatement(token);
+ }
+ while (!isKeyword(token, KEYWORD_end)) {
+ if (isTypeSpec(token))
+ parseComponentDefStmt(token);
+ else
+ skipToNextStatement(token);
+ }
+ readSubToken(token);
+ /* secondary token should be KEYWORD_type token */
+ skipToToken(token, TOKEN_STATEMENT_END);
+ ancestorPop();
+}
+
+/* interface-block
+ * interface-stmt (is INTERFACE [generic-spec])
+ * [interface-body]
+ * [module-procedure-stmt] ...
+ * end-interface-stmt (is END INTERFACE)
+ *
+ * generic-spec
+ * is generic-name
+ * or OPERATOR ( defined-operator )
+ * or ASSIGNMENT ( = )
+ *
+ * interface-body
+ * is function-stmt
+ * [specification-part]
+ * end-function-stmt
+ * or subroutine-stmt
+ * [specification-part]
+ * end-subroutine-stmt
+ *
+ * module-procedure-stmt is
+ * MODULE PROCEDURE procedure-name-list
+ */
+static void parseInterfaceBlock(tokenInfo *const token) {
+ tokenInfo *name = NULL;
+ Assert(isKeyword(token, KEYWORD_interface));
+ readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER)) {
+ makeFortranTag(token, TAG_INTERFACE);
+ name = newTokenFrom(token);
+ } else if (isKeyword(token, KEYWORD_assignment) ||
+ isKeyword(token, KEYWORD_operator)) {
+ readToken(token);
+ if (isType(token, TOKEN_PAREN_OPEN)) readToken(token);
+ if (isType(token, TOKEN_OPERATOR)) {
+ makeFortranTag(token, TAG_INTERFACE);
+ name = newTokenFrom(token);
+ }
+ }
+ if (name == NULL) {
+ name = newToken();
+ name->type = TOKEN_IDENTIFIER;
+ name->tag = TAG_INTERFACE;
+ }
+ ancestorPush(name);
+ while (!isKeyword(token, KEYWORD_end)) {
+ switch (token->keyword) {
+ case KEYWORD_function:
+ parseFunctionSubprogram(token);
+ break;
+ case KEYWORD_subroutine:
+ parseSubroutineSubprogram(token);
+ break;
+
+ default:
+ if (isSubprogramPrefix(token))
+ readToken(token);
+ else if (isTypeSpec(token))
+ parseTypeSpec(token);
+ else
+ skipToNextStatement(token);
+ break;
+ }
+ }
+ readSubToken(token);
+ /* secondary token should be KEYWORD_interface token */
+ skipToNextStatement(token);
+ ancestorPop();
+ deleteToken(name);
+}
+
+/* entry-stmt is
+ * ENTRY entry-name [ ( dummy-arg-list ) ]
+ */
+static void parseEntryStmt(tokenInfo *const token) {
+ Assert(isKeyword(token, KEYWORD_entry));
+ readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER)) makeFortranTag(token, TAG_ENTRY_POINT);
+ skipToNextStatement(token);
+}
+
+/* stmt-function-stmt is
+ * function-name ([dummy-arg-name-list]) = scalar-expr
+ */
+static boolean parseStmtFunctionStmt(tokenInfo *const token) {
+ boolean result = FALSE;
+ Assert(isType(token, TOKEN_IDENTIFIER));
+#if 0 /* cannot reliably parse this yet */
+ makeFortranTag (token, TAG_FUNCTION);
+#endif
+ readToken(token);
+ if (isType(token, TOKEN_PAREN_OPEN)) {
+ skipOverParens(token);
+ result = (boolean)(isType(token, TOKEN_OPERATOR) &&
+ strcmp(vStringValue(token->string), "=") == 0);
+ }
+ skipToNextStatement(token);
+ return result;
+}
+
+static boolean isIgnoredDeclaration(tokenInfo *const token) {
+ boolean result;
+ switch (token->keyword) {
+ case KEYWORD_cexternal:
+ case KEYWORD_cglobal:
+ case KEYWORD_dllexport:
+ case KEYWORD_dllimport:
+ case KEYWORD_external:
+ case KEYWORD_format:
+ case KEYWORD_include:
+ case KEYWORD_inline:
+ case KEYWORD_parameter:
+ case KEYWORD_pascal:
+ case KEYWORD_pexternal:
+ case KEYWORD_pglobal:
+ case KEYWORD_static:
+ case KEYWORD_value:
+ case KEYWORD_virtual:
+ case KEYWORD_volatile:
+ result = TRUE;
+ break;
+
+ default:
+ result = FALSE;
+ break;
+ }
+ return result;
+}
+
+/* declaration-construct
+ * [derived-type-def]
+ * [interface-block]
+ * [type-declaration-stmt]
+ * [specification-stmt]
+ * [parameter-stmt] (is PARAMETER ( named-constant-def-list )
+ * [format-stmt] (is FORMAT format-specification)
+ * [entry-stmt]
+ * [stmt-function-stmt]
+ */
+static boolean parseDeclarationConstruct(tokenInfo *const token) {
+ boolean result = TRUE;
+ switch (token->keyword) {
+ case KEYWORD_entry:
+ parseEntryStmt(token);
+ break;
+ case KEYWORD_interface:
+ parseInterfaceBlock(token);
+ break;
+ case KEYWORD_stdcall:
+ readToken(token);
+ break;
+ /* derived type handled by parseTypeDeclarationStmt(); */
+
+ case KEYWORD_automatic:
+ readToken(token);
+ if (isTypeSpec(token))
+ parseTypeDeclarationStmt(token);
+ else
+ skipToNextStatement(token);
+ result = TRUE;
+ break;
+
+ default:
+ if (isIgnoredDeclaration(token))
+ skipToNextStatement(token);
+ else if (isTypeSpec(token)) {
+ parseTypeDeclarationStmt(token);
+ result = TRUE;
+ } else if (isType(token, TOKEN_IDENTIFIER))
+ result = parseStmtFunctionStmt(token);
+ else
+ result = parseSpecificationStmt(token);
+ break;
+ }
+ return result;
+}
+
+/* implicit-part-stmt
+ * is [implicit-stmt] (is IMPLICIT etc.)
+ * or [parameter-stmt] (is PARAMETER etc.)
+ * or [format-stmt] (is FORMAT etc.)
+ * or [entry-stmt] (is ENTRY entry-name etc.)
+ */
+static boolean parseImplicitPartStmt(tokenInfo *const token) {
+ boolean result = TRUE;
+ switch (token->keyword) {
+ case KEYWORD_entry:
+ parseEntryStmt(token);
+ break;
+
+ case KEYWORD_implicit:
+ case KEYWORD_include:
+ case KEYWORD_parameter:
+ case KEYWORD_format:
+ skipToNextStatement(token);
+ break;
+
+ default:
+ result = FALSE;
+ break;
+ }
+ return result;
+}
+
+/* specification-part is
+ * [use-stmt] ... (is USE module-name etc.)
+ * [implicit-part] (is [implicit-part-stmt] ... [implicit-stmt])
+ * [declaration-construct] ...
+ */
+static boolean parseSpecificationPart(tokenInfo *const token) {
+ boolean result = FALSE;
+ while (skipStatementIfKeyword(token, KEYWORD_use)) result = TRUE;
+ while (parseImplicitPartStmt(token)) result = TRUE;
+ while (parseDeclarationConstruct(token)) result = TRUE;
+ return result;
+}
+
+/* block-data is
+ * block-data-stmt (is BLOCK DATA [block-data-name]
+ * [specification-part]
+ * end-block-data-stmt (is END [BLOCK DATA [block-data-name]])
+ */
+static void parseBlockData(tokenInfo *const token) {
+ Assert(isKeyword(token, KEYWORD_block));
+ readToken(token);
+ if (isKeyword(token, KEYWORD_data)) {
+ readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER)) makeFortranTag(token, TAG_BLOCK_DATA);
+ }
+ ancestorPush(token);
+ skipToNextStatement(token);
+ parseSpecificationPart(token);
+ while (!isKeyword(token, KEYWORD_end)) skipToNextStatement(token);
+ readSubToken(token);
+ /* secondary token should be KEYWORD_NONE or KEYWORD_block token */
+ skipToNextStatement(token);
+ ancestorPop();
+}
+
+/* internal-subprogram-part is
+ * contains-stmt (is CONTAINS)
+ * internal-subprogram
+ * [internal-subprogram] ...
+ *
+ * internal-subprogram
+ * is function-subprogram
+ * or subroutine-subprogram
+ */
+static void parseInternalSubprogramPart(tokenInfo *const token) {
+ boolean done = FALSE;
+ if (isKeyword(token, KEYWORD_contains)) skipToNextStatement(token);
+ do {
+ switch (token->keyword) {
+ case KEYWORD_function:
+ parseFunctionSubprogram(token);
+ break;
+ case KEYWORD_subroutine:
+ parseSubroutineSubprogram(token);
+ break;
+ case KEYWORD_end:
+ done = TRUE;
+ break;
+
+ default:
+ if (isSubprogramPrefix(token))
+ readToken(token);
+ else if (isTypeSpec(token))
+ parseTypeSpec(token);
+ else
+ readToken(token);
+ break;
+ }
+ } while (!done);
+}
+
+/* module is
+ * module-stmt (is MODULE module-name)
+ * [specification-part]
+ * [module-subprogram-part]
+ * end-module-stmt (is END [MODULE [module-name]])
+ *
+ * module-subprogram-part
+ * contains-stmt (is CONTAINS)
+ * module-subprogram
+ * [module-subprogram] ...
+ *
+ * module-subprogram
+ * is function-subprogram
+ * or subroutine-subprogram
+ */
+static void parseModule(tokenInfo *const token) {
+ Assert(isKeyword(token, KEYWORD_module));
+ readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER)) makeFortranTag(token, TAG_MODULE);
+ ancestorPush(token);
+ skipToNextStatement(token);
+ parseSpecificationPart(token);
+ if (isKeyword(token, KEYWORD_contains)) parseInternalSubprogramPart(token);
+ while (!isKeyword(token, KEYWORD_end)) skipToNextStatement(token);
+ readSubToken(token);
+ /* secondary token should be KEYWORD_NONE or KEYWORD_module token */
+ skipToNextStatement(token);
+ ancestorPop();
+}
+
+/* execution-part
+ * executable-construct
+ *
+ * executable-contstruct is
+ * execution-part-construct [execution-part-construct]
+ *
+ * execution-part-construct
+ * is executable-construct
+ * or format-stmt
+ * or data-stmt
+ * or entry-stmt
+ */
+static boolean parseExecutionPart(tokenInfo *const token) {
+ boolean result = FALSE;
+ boolean done = FALSE;
+ while (!done) {
+ switch (token->keyword) {
+ default:
+ if (isSubprogramPrefix(token))
+ readToken(token);
+ else
+ skipToNextStatement(token);
+ result = TRUE;
+ break;
+
+ case KEYWORD_entry:
+ parseEntryStmt(token);
+ result = TRUE;
+ break;
+
+ case KEYWORD_contains:
+ case KEYWORD_function:
+ case KEYWORD_subroutine:
+ done = TRUE;
+ break;
+
+ case KEYWORD_end:
+ readSubToken(token);
+ if (isSecondaryKeyword(token, KEYWORD_do) ||
+ isSecondaryKeyword(token, KEYWORD_if) ||
+ isSecondaryKeyword(token, KEYWORD_select) ||
+ isSecondaryKeyword(token, KEYWORD_where)) {
+ skipToNextStatement(token);
+ result = TRUE;
+ } else
+ done = TRUE;
+ break;
+ }
+ }
+ return result;
+}
+
+static void parseSubprogram(tokenInfo *const token, const tagType tag) {
+ Assert(isKeyword(token, KEYWORD_program) ||
+ isKeyword(token, KEYWORD_function) ||
+ isKeyword(token, KEYWORD_subroutine));
+ readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER)) makeFortranTag(token, tag);
+ ancestorPush(token);
+ skipToNextStatement(token);
+ parseSpecificationPart(token);
+ parseExecutionPart(token);
+ if (isKeyword(token, KEYWORD_contains)) parseInternalSubprogramPart(token);
+ /* should be at KEYWORD_end token */
+ readSubToken(token);
+ /* secondary token should be one of KEYWORD_NONE, KEYWORD_program,
+ * KEYWORD_function, KEYWORD_function
+ */
+ skipToNextStatement(token);
+ ancestorPop();
+}
+
+/* function-subprogram is
+ * function-stmt (is [prefix] FUNCTION function-name etc.)
+ * [specification-part]
+ * [execution-part]
+ * [internal-subprogram-part]
+ * end-function-stmt (is END [FUNCTION [function-name]])
+ *
+ * prefix
+ * is type-spec [RECURSIVE]
+ * or [RECURSIVE] type-spec
+ */
+static void parseFunctionSubprogram(tokenInfo *const token) {
+ parseSubprogram(token, TAG_FUNCTION);
+}
+
+/* subroutine-subprogram is
+ * subroutine-stmt (is [RECURSIVE] SUBROUTINE subroutine-name etc.)
+ * [specification-part]
+ * [execution-part]
+ * [internal-subprogram-part]
+ * end-subroutine-stmt (is END [SUBROUTINE [function-name]])
+ */
+static void parseSubroutineSubprogram(tokenInfo *const token) {
+ parseSubprogram(token, TAG_SUBROUTINE);
+}
+
+/* main-program is
+ * [program-stmt] (is PROGRAM program-name)
+ * [specification-part]
+ * [execution-part]
+ * [internal-subprogram-part ]
+ * end-program-stmt
+ */
+static void parseMainProgram(tokenInfo *const token) {
+ parseSubprogram(token, TAG_PROGRAM);
+}
+
+/* program-unit
+ * is main-program
+ * or external-subprogram (is function-subprogram or subroutine-subprogram)
+ * or module
+ * or block-data
+ */
+static void parseProgramUnit(tokenInfo *const token) {
+ readToken(token);
+ do {
+ if (isType(token, TOKEN_STATEMENT_END))
+ readToken(token);
+ else
+ switch (token->keyword) {
+ case KEYWORD_block:
+ parseBlockData(token);
+ break;
+ case KEYWORD_end:
+ skipToNextStatement(token);
+ break;
+ case KEYWORD_function:
+ parseFunctionSubprogram(token);
+ break;
+ case KEYWORD_module:
+ parseModule(token);
+ break;
+ case KEYWORD_program:
+ parseMainProgram(token);
+ break;
+ case KEYWORD_subroutine:
+ parseSubroutineSubprogram(token);
+ break;
+
+ default:
+ if (isSubprogramPrefix(token))
+ readToken(token);
+ else {
+ boolean one = parseSpecificationPart(token);
+ boolean two = parseExecutionPart(token);
+ if (!(one || two)) readToken(token);
+ }
+ break;
+ }
+ } while (TRUE);
+}
+
+static boolean findFortranTags(const unsigned int passCount) {
+ tokenInfo *token;
+ exception_t exception;
+ boolean retry;
+
+ Assert(passCount < 3);
+ Parent = newToken();
+ token = newToken();
+ FreeSourceForm = (boolean)(passCount > 1);
+ Column = 0;
+ exception = (exception_t)setjmp(Exception);
+ if (exception == ExceptionEOF)
+ retry = FALSE;
+ else if (exception == ExceptionFixedFormat && !FreeSourceForm) {
+ verbose("%s: not fixed source form; retry as free source form\n",
+ getInputFileName());
+ retry = TRUE;
+ } else {
+ parseProgramUnit(token);
+ retry = FALSE;
+ }
+ ancestorClear();
+ deleteToken(token);
+ deleteToken(Parent);
+
+ return retry;
+}
+
+static void initialize(const langType language) {
+ Lang_fortran = language;
+ buildFortranKeywordHash();
+}
+
+extern parserDefinition *FortranParser(void) {
+ static const char *const extensions[] = {
+ "f", "for", "ftn", "f77", "f90", "f95",
+#ifndef CASE_INSENSITIVE_FILENAMES
+ "F", "FOR", "FTN", "F77", "F90", "F95",
+#endif
+ NULL};
+ parserDefinition *def = parserNew("Fortran");
+ def->kinds = FortranKinds;
+ def->kindCount = KIND_COUNT(FortranKinds);
+ def->extensions = extensions;
+ def->parser2 = findFortranTags;
+ def->initialize = initialize;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/general.h b/third_party/ctags/general.h
new file mode 100644
index 000000000..7ce39a985
--- /dev/null
+++ b/third_party/ctags/general.h
@@ -0,0 +1,90 @@
+#ifndef _GENERAL_H
+#define _GENERAL_H
+#include "libc/calls/calls.h"
+#include "libc/runtime/runtime.h"
+#include "libc/str/str.h"
+#include "third_party/ctags/config.h"
+
+/* Define standard error destination
+ */
+#ifndef errout
+#define errout stderr
+#endif
+
+/* Define regex if supported */
+#if (defined(HAVE_REGCOMP) && !defined(REGCOMP_BROKEN))
+#define HAVE_REGEX 1
+#endif
+
+/* This is a helpful internal feature of later versions (> 2.7) of GCC
+ * to prevent warnings about unused variables.
+ */
+#if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7)) && \
+ !defined(__GNUG__)
+#define __unused__
+#define __printf__(s, f) __attribute__((__format__(printf, s, f)))
+#else
+#define __unused__
+#define __printf__(s, f)
+#endif
+
+/*
+ * Portability macros
+ */
+#if !defined(HAVE_STRCASECMP) && !defined(strcasecmp)
+#ifdef HAVE_STRICMP
+#define strcasecmp(s1, s2) stricmp(s1, s2)
+#else
+#define strcasecmp(s1, s2) struppercmp(s1, s2)
+#endif
+#endif
+
+#if !defined(HAVE_STRNCASECMP) && !defined(strncasecmp)
+#ifdef HAVE_STRNICMP
+#define strncasecmp(s1, s2, n) strnicmp(s1, s2, n)
+#else
+#define strncasecmp(s1, s2, n) strnuppercmp(s1, s2, n)
+#endif
+#endif
+
+/*
+ * DATA DECLARATIONS
+ */
+
+#undef FALSE
+#undef TRUE
+#ifdef VAXC
+typedef enum { FALSE, TRUE } booleanType;
+typedef int boolean;
+#else
+#ifdef __cplusplus
+typedef bool boolean;
+#define FALSE false
+#define TRUE true
+#else
+typedef enum { FALSE, TRUE } boolean;
+#endif
+#endif
+
+#if !defined(HAVE_FGETPOS) && !defined(fpos_t)
+#define fpos_t long
+#endif
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+
+#if defined(NEED_PROTO_REMOVE) && defined(HAVE_REMOVE)
+extern int remove(const char *);
+#endif
+
+#if defined(NEED_PROTO_UNLINK) && !defined(HAVE_REMOVE)
+extern void *unlink(const char *);
+#endif
+
+#ifdef NEED_PROTO_GETENV
+extern char *getenv(const char *);
+#endif
+
+/* vi:set tabstop=4 shiftwidth=4: */
+#endif /* _GENERAL_H */
diff --git a/third_party/ctags/get.c b/third_party/ctags/get.c
new file mode 100644
index 000000000..5796312bf
--- /dev/null
+++ b/third_party/ctags/get.c
@@ -0,0 +1,614 @@
+/*
+ * $Id: get.c 559 2007-06-17 03:30:09Z elliotth $
+ *
+ * Copyright (c) 1996-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains the high level source read functions (preprocessor
+ * directives are handled within this level).
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/calls/calls.h"
+#include "libc/str/str.h"
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/get.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * MACROS
+ */
+#define stringMatch(s1, s2) (strcmp(s1, s2) == 0)
+#define isspacetab(c) ((c) == SPACE || (c) == TAB)
+
+/*
+ * DATA DECLARATIONS
+ */
+typedef enum { COMMENT_NONE, COMMENT_C, COMMENT_CPLUS } Comment;
+
+enum eCppLimits { MaxCppNestingLevel = 20, MaxDirectiveName = 10 };
+
+/* Defines the one nesting level of a preprocessor conditional.
+ */
+typedef struct sConditionalInfo {
+ boolean ignoreAllBranches; /* ignoring parent conditional branch */
+ boolean singleBranch; /* choose only one branch */
+ boolean branchChosen; /* branch already selected */
+ boolean ignoring; /* current ignore state */
+} conditionalInfo;
+
+enum eState {
+ DRCTV_NONE, /* no known directive - ignore to end of line */
+ DRCTV_DEFINE, /* "#define" encountered */
+ DRCTV_HASH, /* initial '#' read; determine directive */
+ DRCTV_IF, /* "#if" or "#ifdef" encountered */
+ DRCTV_PRAGMA, /* #pragma encountered */
+ DRCTV_UNDEF /* "#undef" encountered */
+};
+
+/* Defines the current state of the pre-processor.
+ */
+typedef struct sCppState {
+ int ungetch, ungetch2; /* ungotten characters, if any */
+ boolean resolveRequired; /* must resolve if/else/elif/endif branch */
+ boolean hasAtLiteralStrings; /* supports @"c:\" strings */
+ struct sDirective {
+ enum eState state; /* current directive being processed */
+ boolean accept; /* is a directive syntactically permitted? */
+ vString *name; /* macro name */
+ unsigned int nestLevel; /* level 0 is not used */
+ conditionalInfo ifdef[MaxCppNestingLevel];
+ } directive;
+} cppState;
+
+/*
+ * DATA DEFINITIONS
+ */
+
+/* Use brace formatting to detect end of block.
+ */
+static boolean BraceFormat = FALSE;
+
+static cppState Cpp = {
+ '\0',
+ '\0', /* ungetch characters */
+ FALSE, /* resolveRequired */
+ FALSE, /* hasAtLiteralStrings */
+ {
+ DRCTV_NONE, /* state */
+ FALSE, /* accept */
+ NULL, /* tag name */
+ 0, /* nestLevel */
+ {{FALSE, FALSE, FALSE, FALSE}} /* ifdef array */
+ } /* directive */
+};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+extern boolean isBraceFormat(void) {
+ return BraceFormat;
+}
+
+extern unsigned int getDirectiveNestLevel(void) {
+ return Cpp.directive.nestLevel;
+}
+
+extern void cppInit(const boolean state, const boolean hasAtLiteralStrings) {
+ BraceFormat = state;
+
+ Cpp.ungetch = '\0';
+ Cpp.ungetch2 = '\0';
+ Cpp.resolveRequired = FALSE;
+ Cpp.hasAtLiteralStrings = hasAtLiteralStrings;
+
+ Cpp.directive.state = DRCTV_NONE;
+ Cpp.directive.accept = TRUE;
+ Cpp.directive.nestLevel = 0;
+
+ Cpp.directive.ifdef[0].ignoreAllBranches = FALSE;
+ Cpp.directive.ifdef[0].singleBranch = FALSE;
+ Cpp.directive.ifdef[0].branchChosen = FALSE;
+ Cpp.directive.ifdef[0].ignoring = FALSE;
+
+ if (Cpp.directive.name == NULL)
+ Cpp.directive.name = vStringNew();
+ else
+ vStringClear(Cpp.directive.name);
+}
+
+extern void cppTerminate(void) {
+ if (Cpp.directive.name != NULL) {
+ vStringDelete(Cpp.directive.name);
+ Cpp.directive.name = NULL;
+ }
+}
+
+extern void cppBeginStatement(void) {
+ Cpp.resolveRequired = TRUE;
+}
+
+extern void cppEndStatement(void) {
+ Cpp.resolveRequired = FALSE;
+}
+
+/*
+ * Scanning functions
+ *
+ * This section handles preprocessor directives. It strips out all
+ * directives and may emit a tag for #define directives.
+ */
+
+/* This puts a character back into the input queue for the source File.
+ * Up to two characters may be ungotten.
+ */
+extern void cppUngetc(const int c) {
+ Assert(Cpp.ungetch2 == '\0');
+ Cpp.ungetch2 = Cpp.ungetch;
+ Cpp.ungetch = c;
+}
+
+/* Reads a directive, whose first character is given by "c", into "name".
+ */
+static boolean readDirective(int c, char *const name, unsigned int maxLength) {
+ unsigned int i;
+
+ for (i = 0; i < maxLength - 1; ++i) {
+ if (i > 0) {
+ c = fileGetc();
+ if (c == EOF || !isalpha(c)) {
+ fileUngetc(c);
+ break;
+ }
+ }
+ name[i] = c;
+ }
+ name[i] = '\0'; /* null terminate */
+
+ return (boolean)isspacetab(c);
+}
+
+/* Reads an identifier, whose first character is given by "c", into "tag",
+ * together with the file location and corresponding line number.
+ */
+static void readIdentifier(int c, vString *const name) {
+ vStringClear(name);
+ do {
+ vStringPut(name, c);
+ } while (c = fileGetc(), (c != EOF && isident(c)));
+ fileUngetc(c);
+ vStringTerminate(name);
+}
+
+static conditionalInfo *currentConditional(void) {
+ return &Cpp.directive.ifdef[Cpp.directive.nestLevel];
+}
+
+static boolean isIgnore(void) {
+ return Cpp.directive.ifdef[Cpp.directive.nestLevel].ignoring;
+}
+
+static boolean setIgnore(const boolean ignore) {
+ return Cpp.directive.ifdef[Cpp.directive.nestLevel].ignoring = ignore;
+}
+
+static boolean isIgnoreBranch(void) {
+ conditionalInfo *const ifdef = currentConditional();
+
+ /* Force a single branch if an incomplete statement is discovered
+ * en route. This may have allowed earlier branches containing complete
+ * statements to be followed, but we must follow no further branches.
+ */
+ if (Cpp.resolveRequired && !BraceFormat) ifdef->singleBranch = TRUE;
+
+ /* We will ignore this branch in the following cases:
+ *
+ * 1. We are ignoring all branches (conditional was within an ignored
+ * branch of the parent conditional)
+ * 2. A branch has already been chosen and either of:
+ * a. A statement was incomplete upon entering the conditional
+ * b. A statement is incomplete upon encountering a branch
+ */
+ return (boolean)(ifdef->ignoreAllBranches ||
+ (ifdef->branchChosen && ifdef->singleBranch));
+}
+
+static void chooseBranch(void) {
+ if (!BraceFormat) {
+ conditionalInfo *const ifdef = currentConditional();
+
+ ifdef->branchChosen = (boolean)(ifdef->singleBranch || Cpp.resolveRequired);
+ }
+}
+
+/* Pushes one nesting level for an #if directive, indicating whether or not
+ * the branch should be ignored and whether a branch has already been chosen.
+ */
+static boolean pushConditional(const boolean firstBranchChosen) {
+ const boolean ignoreAllBranches = isIgnore(); /* current ignore */
+ boolean ignoreBranch = FALSE;
+
+ if (Cpp.directive.nestLevel < (unsigned int)MaxCppNestingLevel - 1) {
+ conditionalInfo *ifdef;
+
+ ++Cpp.directive.nestLevel;
+ ifdef = currentConditional();
+
+ /* We take a snapshot of whether there is an incomplete statement in
+ * progress upon encountering the preprocessor conditional. If so,
+ * then we will flag that only a single branch of the conditional
+ * should be followed.
+ */
+ ifdef->ignoreAllBranches = ignoreAllBranches;
+ ifdef->singleBranch = Cpp.resolveRequired;
+ ifdef->branchChosen = firstBranchChosen;
+ ifdef->ignoring =
+ (boolean)(ignoreAllBranches || (!firstBranchChosen && !BraceFormat &&
+ (ifdef->singleBranch || !Option.if0)));
+ ignoreBranch = ifdef->ignoring;
+ }
+ return ignoreBranch;
+}
+
+/* Pops one nesting level for an #endif directive.
+ */
+static boolean popConditional(void) {
+ if (Cpp.directive.nestLevel > 0) --Cpp.directive.nestLevel;
+
+ return isIgnore();
+}
+
+static void makeDefineTag(const char *const name) {
+ const boolean isFileScope = (boolean)(!isHeaderFile());
+
+ if (includingDefineTags() && (!isFileScope || Option.include.fileScope)) {
+ tagEntryInfo e;
+ initTagEntry(&e, name);
+ e.lineNumberEntry = (boolean)(Option.locate != EX_PATTERN);
+ e.isFileScope = isFileScope;
+ e.truncateLine = TRUE;
+ e.kindName = "macro";
+ e.kind = 'd';
+ makeTagEntry(&e);
+ }
+}
+
+static void directiveDefine(const int c) {
+ if (isident1(c)) {
+ readIdentifier(c, Cpp.directive.name);
+ if (!isIgnore()) makeDefineTag(vStringValue(Cpp.directive.name));
+ }
+ Cpp.directive.state = DRCTV_NONE;
+}
+
+static void directivePragma(int c) {
+ if (isident1(c)) {
+ readIdentifier(c, Cpp.directive.name);
+ if (stringMatch(vStringValue(Cpp.directive.name), "weak")) {
+ /* generate macro tag for weak name */
+ do {
+ c = fileGetc();
+ } while (c == SPACE);
+ if (isident1(c)) {
+ readIdentifier(c, Cpp.directive.name);
+ makeDefineTag(vStringValue(Cpp.directive.name));
+ }
+ }
+ }
+ Cpp.directive.state = DRCTV_NONE;
+}
+
+static boolean directiveIf(const int c) {
+ DebugStatement(const boolean ignore0 = isIgnore();) const boolean ignore =
+ pushConditional((boolean)(c != '0'));
+
+ Cpp.directive.state = DRCTV_NONE;
+ DebugStatement(debugCppNest(TRUE, Cpp.directive.nestLevel);
+ if (ignore != ignore0) debugCppIgnore(ignore);)
+
+ return ignore;
+}
+
+static boolean directiveHash(const int c) {
+ boolean ignore = FALSE;
+ char directive[MaxDirectiveName];
+ DebugStatement(const boolean ignore0 = isIgnore();)
+
+ readDirective(c, directive, MaxDirectiveName);
+ if (stringMatch(directive, "define"))
+ Cpp.directive.state = DRCTV_DEFINE;
+ else if (stringMatch(directive, "undef"))
+ Cpp.directive.state = DRCTV_UNDEF;
+ else if (strncmp(directive, "if", (size_t)2) == 0)
+ Cpp.directive.state = DRCTV_IF;
+ else if (stringMatch(directive, "elif") || stringMatch(directive, "else")) {
+ ignore = setIgnore(isIgnoreBranch());
+ if (!ignore && stringMatch(directive, "else")) chooseBranch();
+ Cpp.directive.state = DRCTV_NONE;
+ DebugStatement(if (ignore != ignore0) debugCppIgnore(ignore);)
+ } else if (stringMatch(directive, "endif")) {
+ DebugStatement(debugCppNest(FALSE, Cpp.directive.nestLevel);) ignore =
+ popConditional();
+ Cpp.directive.state = DRCTV_NONE;
+ DebugStatement(if (ignore != ignore0) debugCppIgnore(ignore);)
+ } else if (stringMatch(directive, "pragma"))
+ Cpp.directive.state = DRCTV_PRAGMA;
+ else
+ Cpp.directive.state = DRCTV_NONE;
+
+ return ignore;
+}
+
+/* Handles a pre-processor directive whose first character is given by "c".
+ */
+static boolean handleDirective(const int c) {
+ boolean ignore = isIgnore();
+
+ switch (Cpp.directive.state) {
+ case DRCTV_NONE:
+ ignore = isIgnore();
+ break;
+ case DRCTV_DEFINE:
+ directiveDefine(c);
+ break;
+ case DRCTV_HASH:
+ ignore = directiveHash(c);
+ break;
+ case DRCTV_IF:
+ ignore = directiveIf(c);
+ break;
+ case DRCTV_PRAGMA:
+ directivePragma(c);
+ break;
+ case DRCTV_UNDEF:
+ directiveDefine(c);
+ break;
+ }
+ return ignore;
+}
+
+/* Called upon reading of a slash ('/') characters, determines whether a
+ * comment is encountered, and its type.
+ */
+static Comment isComment(void) {
+ Comment comment;
+ const int next = fileGetc();
+
+ if (next == '*')
+ comment = COMMENT_C;
+ else if (next == '/')
+ comment = COMMENT_CPLUS;
+ else {
+ fileUngetc(next);
+ comment = COMMENT_NONE;
+ }
+ return comment;
+}
+
+/* Skips over a C style comment. According to ANSI specification a comment
+ * is treated as white space, so we perform this substitution.
+ */
+int skipOverCComment(void) {
+ int c = fileGetc();
+
+ while (c != EOF) {
+ if (c != '*')
+ c = fileGetc();
+ else {
+ const int next = fileGetc();
+
+ if (next != '/')
+ c = next;
+ else {
+ c = SPACE; /* replace comment with space */
+ break;
+ }
+ }
+ }
+ return c;
+}
+
+/* Skips over a C++ style comment.
+ */
+static int skipOverCplusComment(void) {
+ int c;
+
+ while ((c = fileGetc()) != EOF) {
+ if (c == BACKSLASH)
+ fileGetc(); /* throw away next character, too */
+ else if (c == NEWLINE)
+ break;
+ }
+ return c;
+}
+
+/* Skips to the end of a string, returning a special character to
+ * symbolically represent a generic string.
+ */
+static int skipToEndOfString(boolean ignoreBackslash) {
+ int c;
+
+ while ((c = fileGetc()) != EOF) {
+ if (c == BACKSLASH && !ignoreBackslash)
+ fileGetc(); /* throw away next character, too */
+ else if (c == DOUBLE_QUOTE)
+ break;
+ }
+ return STRING_SYMBOL; /* symbolic representation of string */
+}
+
+/* Skips to the end of the three (possibly four) 'c' sequence, returning a
+ * special character to symbolically represent a generic character.
+ * Also detects Vera numbers that include a base specifier (ie. 'b1010).
+ */
+static int skipToEndOfChar(void) {
+ int c;
+ int count = 0, veraBase = '\0';
+
+ while ((c = fileGetc()) != EOF) {
+ ++count;
+ if (c == BACKSLASH)
+ fileGetc(); /* throw away next character, too */
+ else if (c == SINGLE_QUOTE)
+ break;
+ else if (c == NEWLINE) {
+ fileUngetc(c);
+ break;
+ } else if (count == 1 && strchr("DHOB", toupper(c)) != NULL)
+ veraBase = c;
+ else if (veraBase != '\0' && !isalnum(c)) {
+ fileUngetc(c);
+ break;
+ }
+ }
+ return CHAR_SYMBOL; /* symbolic representation of character */
+}
+
+/* This function returns the next character, stripping out comments,
+ * C pre-processor directives, and the contents of single and double
+ * quoted strings. In short, strip anything which places a burden upon
+ * the tokenizer.
+ */
+extern int cppGetc(void) {
+ boolean directive = FALSE;
+ boolean ignore = FALSE;
+ int c;
+
+ if (Cpp.ungetch != '\0') {
+ c = Cpp.ungetch;
+ Cpp.ungetch = Cpp.ungetch2;
+ Cpp.ungetch2 = '\0';
+ return c; /* return here to avoid re-calling debugPutc () */
+ } else
+ do {
+ c = fileGetc();
+ process:
+ switch (c) {
+ case EOF:
+ ignore = FALSE;
+ directive = FALSE;
+ break;
+
+ case TAB:
+ case SPACE:
+ break; /* ignore most white space */
+
+ case NEWLINE:
+ if (directive && !ignore) directive = FALSE;
+ Cpp.directive.accept = TRUE;
+ break;
+
+ case DOUBLE_QUOTE:
+ Cpp.directive.accept = FALSE;
+ c = skipToEndOfString(FALSE);
+ break;
+
+ case '#':
+ if (Cpp.directive.accept) {
+ directive = TRUE;
+ Cpp.directive.state = DRCTV_HASH;
+ Cpp.directive.accept = FALSE;
+ }
+ break;
+
+ case SINGLE_QUOTE:
+ Cpp.directive.accept = FALSE;
+ c = skipToEndOfChar();
+ break;
+
+ case '/': {
+ const Comment comment = isComment();
+
+ if (comment == COMMENT_C)
+ c = skipOverCComment();
+ else if (comment == COMMENT_CPLUS) {
+ c = skipOverCplusComment();
+ if (c == NEWLINE) fileUngetc(c);
+ } else
+ Cpp.directive.accept = FALSE;
+ break;
+ }
+
+ case BACKSLASH: {
+ int next = fileGetc();
+
+ if (next == NEWLINE)
+ continue;
+ else if (next == '?')
+ cppUngetc(next);
+ else
+ fileUngetc(next);
+ break;
+ }
+
+ case '?': {
+ int next = fileGetc();
+ if (next != '?')
+ fileUngetc(next);
+ else {
+ next = fileGetc();
+ switch (next) {
+ case '(':
+ c = '[';
+ break;
+ case ')':
+ c = ']';
+ break;
+ case '<':
+ c = '{';
+ break;
+ case '>':
+ c = '}';
+ break;
+ case '/':
+ c = BACKSLASH;
+ goto process;
+ case '!':
+ c = '|';
+ break;
+ case SINGLE_QUOTE:
+ c = '^';
+ break;
+ case '-':
+ c = '~';
+ break;
+ case '=':
+ c = '#';
+ goto process;
+ default:
+ fileUngetc(next);
+ cppUngetc('?');
+ break;
+ }
+ }
+ } break;
+
+ default:
+ if (c == '@' && Cpp.hasAtLiteralStrings) {
+ int next = fileGetc();
+ if (next == DOUBLE_QUOTE) {
+ Cpp.directive.accept = FALSE;
+ c = skipToEndOfString(TRUE);
+ break;
+ }
+ }
+ Cpp.directive.accept = FALSE;
+ if (directive) ignore = handleDirective(c);
+ break;
+ }
+ } while (directive || ignore);
+
+ DebugStatement(debugPutc(DEBUG_CPP, c);)
+ DebugStatement(if (c == NEWLINE) debugPrintf(
+ DEBUG_CPP, "%6ld: ", getInputLineNumber() + 1);)
+
+ return c;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/get.h b/third_party/ctags/get.h
new file mode 100644
index 000000000..421c7e81b
--- /dev/null
+++ b/third_party/ctags/get.h
@@ -0,0 +1,50 @@
+/*
+* $Id: get.h 525 2007-05-28 01:50:41Z elliotth $
+*
+* Copyright (c) 1998-2002, Darren Hiebert
+*
+* This source code is released for free distribution under the terms of the
+* GNU General Public License.
+*
+* External interface to get.c
+*/
+#ifndef _GET_H
+#define _GET_H
+
+/*
+* INCLUDE FILES
+*/
+#include "third_party/ctags/general.h" /* must always come first */
+
+#include "third_party/ctags/ctags.h" /* to define langType */
+
+/*
+* MACROS
+*/
+/* Is the character valid as a character of a C identifier?
+ * VMS allows '$' in identifiers.
+ */
+#define isident(c) (isalnum(c) || (c) == '_' || (c) == '$')
+
+/* Is the character valid as the first character of a C identifier?
+ * C++ allows '~' in destructors.
+ * VMS allows '$' in identifiers.
+ */
+#define isident1(c) (isalpha(c) || (c) == '_' || (c) == '~' || (c) == '$')
+
+/*
+* FUNCTION PROTOTYPES
+*/
+extern boolean isBraceFormat (void);
+extern unsigned int getDirectiveNestLevel (void);
+extern void cppInit (const boolean state, const boolean hasAtLiteralStrings);
+extern void cppTerminate (void);
+extern void cppBeginStatement (void);
+extern void cppEndStatement (void);
+extern void cppUngetc (const int c);
+extern int cppGetc (void);
+extern int skipOverCComment (void);
+
+#endif /* _GET_H */
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/go.c b/third_party/ctags/go.c
new file mode 100644
index 000000000..037afda79
--- /dev/null
+++ b/third_party/ctags/go.c
@@ -0,0 +1,596 @@
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/calls/calls.h"
+#include "libc/runtime/runtime.h"
+#include "libc/str/str.h"
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/main.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * MACROS
+ */
+#define isType(token, t) (boolean)((token)->type == (t))
+#define isKeyword(token, k) (boolean)((token)->keyword == (k))
+
+/*
+ * DATA DECLARATIONS
+ */
+
+typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
+
+typedef enum eKeywordId {
+ KEYWORD_NONE = -1,
+ KEYWORD_package,
+ KEYWORD_import,
+ KEYWORD_const,
+ KEYWORD_type,
+ KEYWORD_var,
+ KEYWORD_func,
+ KEYWORD_struct,
+ KEYWORD_interface,
+ KEYWORD_map,
+ KEYWORD_chan
+} keywordId;
+
+/* Used to determine whether keyword is valid for the current language and
+ * what its ID is.
+ */
+typedef struct sKeywordDesc {
+ const char *name;
+ keywordId id;
+} keywordDesc;
+
+typedef enum eTokenType {
+ TOKEN_NONE = -1,
+ TOKEN_CHARACTER,
+ // Don't need TOKEN_FORWARD_SLASH
+ TOKEN_FORWARD_SLASH,
+ TOKEN_KEYWORD,
+ TOKEN_IDENTIFIER,
+ TOKEN_STRING,
+ TOKEN_OPEN_PAREN,
+ TOKEN_CLOSE_PAREN,
+ TOKEN_OPEN_CURLY,
+ TOKEN_CLOSE_CURLY,
+ TOKEN_OPEN_SQUARE,
+ TOKEN_CLOSE_SQUARE,
+ TOKEN_SEMICOLON,
+ TOKEN_STAR,
+ TOKEN_LEFT_ARROW,
+ TOKEN_DOT,
+ TOKEN_COMMA
+} tokenType;
+
+typedef struct sTokenInfo {
+ tokenType type;
+ keywordId keyword;
+ vString *string; /* the name of the token */
+ unsigned long lineNumber; /* line number of tag */
+ fpos_t filePosition; /* file position of line containing name */
+} tokenInfo;
+
+/*
+ * DATA DEFINITIONS
+ */
+
+static int Lang_go;
+static jmp_buf Exception;
+static vString *scope;
+
+typedef enum {
+ GOTAG_UNDEFINED = -1,
+ GOTAG_PACKAGE,
+ GOTAG_FUNCTION,
+ GOTAG_CONST,
+ GOTAG_TYPE,
+ GOTAG_VAR,
+} goKind;
+
+static kindOption GoKinds[] = {{TRUE, 'p', "package", "packages"},
+ {TRUE, 'f', "func", "functions"},
+ {TRUE, 'c', "const", "constants"},
+ {TRUE, 't', "type", "types"},
+ {TRUE, 'v', "var", "variables"}};
+
+static keywordDesc GoKeywordTable[] = {
+ {"package", KEYWORD_package}, {"import", KEYWORD_import},
+ {"const", KEYWORD_const}, {"type", KEYWORD_type},
+ {"var", KEYWORD_var}, {"func", KEYWORD_func},
+ {"struct", KEYWORD_struct}, {"interface", KEYWORD_interface},
+ {"map", KEYWORD_map}, {"chan", KEYWORD_chan}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+// XXX UTF-8
+static boolean isIdentChar(const int c) {
+ return (boolean)(isalpha(c) || isdigit(c) || c == '$' || c == '@' ||
+ c == '_' || c == '#' || c > 128);
+}
+
+static void initialize(const langType language) {
+ size_t i;
+ const size_t count = sizeof(GoKeywordTable) / sizeof(GoKeywordTable[0]);
+ Lang_go = language;
+ for (i = 0; i < count; ++i) {
+ const keywordDesc *const p = &GoKeywordTable[i];
+ addKeyword(p->name, language, (int)p->id);
+ }
+}
+
+static tokenInfo *newToken(void) {
+ tokenInfo *const token = xMalloc(1, tokenInfo);
+ token->type = TOKEN_NONE;
+ token->keyword = KEYWORD_NONE;
+ token->string = vStringNew();
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ return token;
+}
+
+static void deleteToken(tokenInfo *const token) {
+ if (token != NULL) {
+ vStringDelete(token->string);
+ eFree(token);
+ }
+}
+
+/*
+ * Parsing functions
+ */
+
+static void parseString(vString *const string, const int delimiter) {
+ boolean end = FALSE;
+ while (!end) {
+ int c = fileGetc();
+ if (c == EOF)
+ end = TRUE;
+ else if (c == '\\' && delimiter != '`') {
+ c = fileGetc(); /* This maybe a ' or ". */
+ vStringPut(string, c);
+ } else if (c == delimiter)
+ end = TRUE;
+ else
+ vStringPut(string, c);
+ }
+ vStringTerminate(string);
+}
+
+static void parseIdentifier(vString *const string, const int firstChar) {
+ int c = firstChar;
+ // Assert (isIdentChar (c));
+ do {
+ vStringPut(string, c);
+ c = fileGetc();
+ } while (isIdentChar(c));
+ vStringTerminate(string);
+ fileUngetc(c); /* always unget, LF might add a semicolon */
+}
+
+static void readToken(tokenInfo *const token) {
+ int c;
+ static tokenType lastTokenType = TOKEN_NONE;
+
+ token->type = TOKEN_NONE;
+ token->keyword = KEYWORD_NONE;
+ vStringClear(token->string);
+
+getNextChar:
+ do {
+ c = fileGetc();
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ if (c == '\n' &&
+ (lastTokenType == TOKEN_IDENTIFIER || lastTokenType == TOKEN_STRING ||
+ lastTokenType == TOKEN_CLOSE_PAREN ||
+ lastTokenType == TOKEN_CLOSE_CURLY ||
+ lastTokenType == TOKEN_CLOSE_SQUARE)) {
+ token->type = TOKEN_SEMICOLON;
+ goto done;
+ }
+ } while (c == '\t' || c == ' ' || c == '\r' || c == '\n');
+
+ switch (c) {
+ case EOF:
+ longjmp(Exception, (int)ExceptionEOF);
+ break;
+
+ case '/': {
+ boolean hasNewline = FALSE;
+ int d = fileGetc();
+ switch (d) {
+ case '/':
+ fileSkipToCharacter('\n');
+ /* Line comments start with the
+ * character sequence // and
+ * continue through the next
+ * newline. A line comment acts
+ * like a newline. */
+ fileUngetc('\n');
+ goto getNextChar;
+ case '*':
+ do {
+ int d;
+ do {
+ d = fileGetc();
+ if (d == '\n') {
+ hasNewline = TRUE;
+ }
+ } while (d != EOF && d != '*');
+
+ c = fileGetc();
+ if (c == '/')
+ break;
+ else
+ fileUngetc(c);
+ } while (c != EOF && c != '\0');
+
+ fileUngetc(hasNewline ? '\n' : ' ');
+ goto getNextChar;
+ default:
+ token->type = TOKEN_FORWARD_SLASH;
+ fileUngetc(d);
+ break;
+ }
+ } break;
+
+ case '"':
+ case '\'':
+ case '`':
+ token->type = TOKEN_STRING;
+ parseString(token->string, c);
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+
+ case '<': {
+ int d = fileGetc();
+ if (d == '-') {
+ token->type = TOKEN_LEFT_ARROW;
+ break;
+ } else
+ goto getNextChar;
+ }
+
+ case '(':
+ token->type = TOKEN_OPEN_PAREN;
+ break;
+
+ case ')':
+ token->type = TOKEN_CLOSE_PAREN;
+ break;
+
+ case '{':
+ token->type = TOKEN_OPEN_CURLY;
+ break;
+
+ case '}':
+ token->type = TOKEN_CLOSE_CURLY;
+ break;
+
+ case '[':
+ token->type = TOKEN_OPEN_SQUARE;
+ break;
+
+ case ']':
+ token->type = TOKEN_CLOSE_SQUARE;
+ break;
+
+ case '*':
+ token->type = TOKEN_STAR;
+ break;
+
+ case '.':
+ token->type = TOKEN_DOT;
+ break;
+
+ case ',':
+ token->type = TOKEN_COMMA;
+ break;
+
+ default:
+ parseIdentifier(token->string, c);
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ token->keyword = lookupKeyword(vStringValue(token->string), Lang_go);
+ if (isKeyword(token, KEYWORD_NONE))
+ token->type = TOKEN_IDENTIFIER;
+ else
+ token->type = TOKEN_KEYWORD;
+ break;
+ }
+
+done:
+ lastTokenType = token->type;
+}
+
+static void skipToMatched(tokenInfo *const token) {
+ int nest_level = 0;
+ tokenType open_token;
+ tokenType close_token;
+
+ switch (token->type) {
+ case TOKEN_OPEN_PAREN:
+ open_token = TOKEN_OPEN_PAREN;
+ close_token = TOKEN_CLOSE_PAREN;
+ break;
+ case TOKEN_OPEN_CURLY:
+ open_token = TOKEN_OPEN_CURLY;
+ close_token = TOKEN_CLOSE_CURLY;
+ break;
+ case TOKEN_OPEN_SQUARE:
+ open_token = TOKEN_OPEN_SQUARE;
+ close_token = TOKEN_CLOSE_SQUARE;
+ break;
+ default:
+ return;
+ }
+
+ /*
+ * This routine will skip to a matching closing token.
+ * It will also handle nested tokens like the (, ) below.
+ * ( name varchar(30), text binary(10) )
+ */
+ if (isType(token, open_token)) {
+ nest_level++;
+ while (!(isType(token, close_token) && (nest_level == 0))) {
+ readToken(token);
+ if (isType(token, open_token)) {
+ nest_level++;
+ }
+ if (isType(token, close_token)) {
+ if (nest_level > 0) {
+ nest_level--;
+ }
+ }
+ }
+ readToken(token);
+ }
+}
+
+static void skipType(tokenInfo *const token) {
+again:
+ // Type = TypeName | TypeLit | "(" Type ")" .
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ skipToMatched(token);
+ return;
+ }
+
+ // TypeName = QualifiedIdent.
+ // QualifiedIdent = [ PackageName "." ] identifier .
+ // PackageName = identifier .
+ if (isType(token, TOKEN_IDENTIFIER)) {
+ readToken(token);
+ if (isType(token, TOKEN_DOT)) {
+ readToken(token);
+ Assert(isType(token, TOKEN_IDENTIFIER));
+ readToken(token);
+ }
+ return;
+ }
+
+ // StructType = "struct" "{" { FieldDecl ";" } "}"
+ // InterfaceType = "interface" "{" { MethodSpec ";" } "}" .
+ if (isKeyword(token, KEYWORD_struct) || isKeyword(token, KEYWORD_interface)) {
+ readToken(token);
+ Assert(isType(token, TOKEN_OPEN_CURLY));
+ skipToMatched(token);
+ return;
+ }
+
+ // ArrayType = "[" ArrayLength "]" ElementType .
+ // SliceType = "[" "]" ElementType .
+ // ElementType = Type .
+ if (isType(token, TOKEN_OPEN_SQUARE)) {
+ skipToMatched(token);
+ goto again;
+ }
+
+ // PointerType = "*" BaseType .
+ // BaseType = Type .
+ // ChannelType = ( "chan" [ "<-" ] | "<-" "chan" ) ElementType .
+ if (isType(token, TOKEN_STAR) || isKeyword(token, KEYWORD_chan) ||
+ isType(token, TOKEN_LEFT_ARROW)) {
+ readToken(token);
+ goto again;
+ }
+
+ // MapType = "map" "[" KeyType "]" ElementType .
+ // KeyType = Type .
+ if (isKeyword(token, KEYWORD_map)) {
+ readToken(token);
+ Assert(isType(token, TOKEN_OPEN_SQUARE));
+ skipToMatched(token);
+ goto again;
+ }
+
+ // FunctionType = "func" Signature .
+ // Signature = Parameters [ Result ] .
+ // Result = Parameters | Type .
+ // Parameters = "(" [ ParameterList [ "," ] ] ")" .
+ if (isKeyword(token, KEYWORD_func)) {
+ readToken(token);
+ Assert(isType(token, TOKEN_OPEN_PAREN));
+ // Parameters
+ skipToMatched(token);
+ // Result is parameters or type or nothing. skipType treats anything
+ // surrounded by parentheses as a type, and does nothing if what
+ // follows is not a type.
+ goto again;
+ }
+}
+
+// Skip to the next semicolon, skipping over matching brackets.
+static void skipToTopLevelSemicolon(tokenInfo *const token) {
+ while (!isType(token, TOKEN_SEMICOLON)) {
+ readToken(token);
+ skipToMatched(token);
+ }
+}
+
+static void makeTag(tokenInfo *const token, const goKind kind) {
+ const char *const name = vStringValue(token->string);
+
+ tagEntryInfo e;
+ initTagEntry(&e, name);
+
+ if (!GoKinds[kind].enabled) return;
+
+ e.lineNumber = token->lineNumber;
+ e.filePosition = token->filePosition;
+ e.kindName = GoKinds[kind].name;
+ e.kind = GoKinds[kind].letter;
+
+ makeTagEntry(&e);
+
+ if (scope && Option.include.qualifiedTags) {
+ vString *qualifiedName = vStringNew();
+ vStringCopy(qualifiedName, scope);
+ vStringCatS(qualifiedName, ".");
+ vStringCat(qualifiedName, token->string);
+ e.name = vStringValue(qualifiedName);
+ makeTagEntry(&e);
+ vStringDelete(qualifiedName);
+ }
+}
+
+static void parsePackage(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+
+ readToken(name);
+ Assert(isType(name, TOKEN_IDENTIFIER));
+ makeTag(name, GOTAG_PACKAGE);
+ if (!scope && Option.include.qualifiedTags) {
+ scope = vStringNew();
+ vStringCopy(scope, name->string);
+ }
+
+ deleteToken(name);
+}
+
+static void parseFunctionOrMethod(tokenInfo *const token) {
+ // FunctionDecl = "func" identifier Signature [ Body ] .
+ // Body = Block.
+ //
+ // MethodDecl = "func" Receiver MethodName Signature [ Body ] .
+ // Receiver = "(" [ identifier ] [ "*" ] BaseTypeName ")" .
+ // BaseTypeName = identifier .
+ tokenInfo *const name = newToken();
+
+ // Skip over receiver.
+ readToken(name);
+ if (isType(name, TOKEN_OPEN_PAREN)) skipToMatched(name);
+
+ Assert(isType(name, TOKEN_IDENTIFIER));
+
+ // Skip over parameters.
+ readToken(token);
+ skipToMatched(token);
+
+ // Skip over result.
+ skipType(token);
+
+ // Skip over function body.
+ if (isType(token, TOKEN_OPEN_CURLY)) skipToMatched(token);
+
+ makeTag(name, GOTAG_FUNCTION);
+
+ deleteToken(name);
+}
+
+static void parseConstTypeVar(tokenInfo *const token, goKind kind) {
+ // ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
+ // ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] .
+ // IdentifierList = identifier { "," identifier } .
+ // ExpressionList = Expression { "," Expression } .
+ // TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
+ // TypeSpec = identifier Type .
+ // VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
+ // VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "="
+ // ExpressionList ) .
+ tokenInfo *const name = newToken();
+ boolean usesParens = FALSE;
+
+ readToken(name);
+
+ if (isType(name, TOKEN_OPEN_PAREN)) {
+ usesParens = TRUE;
+ readToken(name);
+ }
+
+again:
+ while (1) {
+ makeTag(name, kind);
+ readToken(token);
+ if (!isType(token, TOKEN_COMMA) && !isType(token, TOKEN_CLOSE_PAREN)) break;
+ readToken(name);
+ }
+
+ skipType(token);
+ skipToTopLevelSemicolon(token);
+
+ if (usesParens) {
+ readToken(name);
+ if (!isType(name, TOKEN_CLOSE_PAREN)) goto again;
+ }
+
+ deleteToken(name);
+}
+
+static void parseGoFile(tokenInfo *const token) {
+ do {
+ readToken(token);
+
+ if (isType(token, TOKEN_KEYWORD)) {
+ switch (token->keyword) {
+ case KEYWORD_package:
+ parsePackage(token);
+ break;
+ case KEYWORD_func:
+ parseFunctionOrMethod(token);
+ break;
+ case KEYWORD_const:
+ parseConstTypeVar(token, GOTAG_CONST);
+ break;
+ case KEYWORD_type:
+ parseConstTypeVar(token, GOTAG_TYPE);
+ break;
+ case KEYWORD_var:
+ parseConstTypeVar(token, GOTAG_VAR);
+ break;
+ default:
+ break;
+ }
+ }
+ } while (TRUE);
+}
+
+static void findGoTags(void) {
+ tokenInfo *const token = newToken();
+ exception_t exception;
+
+ exception = (exception_t)(setjmp(Exception));
+ while (exception == ExceptionNone) parseGoFile(token);
+
+ deleteToken(token);
+ vStringDelete(scope);
+ scope = NULL;
+}
+
+extern parserDefinition *GoParser(void) {
+ static const char *const extensions[] = {"go", NULL};
+ parserDefinition *def = parserNew("Go");
+ def->kinds = GoKinds;
+ def->kindCount = KIND_COUNT(GoKinds);
+ def->extensions = extensions;
+ def->parser = findGoTags;
+ def->initialize = initialize;
+ return def;
+}
diff --git a/third_party/ctags/html.c b/third_party/ctags/html.c
new file mode 100644
index 000000000..8eb4a8c2a
--- /dev/null
+++ b/third_party/ctags/html.c
@@ -0,0 +1,41 @@
+/*
+ * $Id: html.c 443 2006-05-30 04:37:13Z darren $
+ *
+ * Copyright (c) 2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for HTML language
+ * files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parse.h"
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void installHtmlRegex(const langType language) {
+#define POSSIBLE_ATTRIBUTES "([ \t]+[a-z]+=\"?[^>\"]*\"?)*"
+ addTagRegex(language,
+ "\"]+)\"?" POSSIBLE_ATTRIBUTES "[ \t]*>",
+ "\\2", "a,anchor,named anchors", "i");
+
+ addTagRegex(language, "^[ \t]*function[ \t]*([A-Za-z0-9_]+)[ \t]*\\(", "\\1",
+ "f,function,JavaScript functions", NULL);
+}
+
+/* Create parser definition stucture */
+extern parserDefinition *HtmlParser(void) {
+ static const char *const extensions[] = {"htm", "html", NULL};
+ parserDefinition *const def = parserNew("HTML");
+ def->extensions = extensions;
+ def->initialize = installHtmlRegex;
+ def->regex = TRUE;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/jscript.c b/third_party/ctags/jscript.c
new file mode 100644
index 000000000..539a3bd3a
--- /dev/null
+++ b/third_party/ctags/jscript.c
@@ -0,0 +1,1496 @@
+/*
+ * $Id: jscript.c 763 2010-07-28 14:22:42Z dfishburn $
+ *
+ * Copyright (c) 2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the
+ * terms of the GNU General Public License.
+ *
+ * This module contains functions for generating tags for
+ * JavaScript language files.
+ *
+ * This is a good reference for different forms of the function statement:
+ * http://www.permadi.com/tutorial/jsFunc/
+ * Another good reference:
+ * http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * MACROS
+ */
+#define isType(token, t) (boolean)((token)->type == (t))
+#define isKeyword(token, k) (boolean)((token)->keyword == (k))
+
+/*
+ * DATA DECLARATIONS
+ */
+
+typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
+
+/*
+ * Tracks class and function names already created
+ */
+static stringList *ClassNames;
+static stringList *FunctionNames;
+
+/* Used to specify type of keyword.
+ */
+typedef enum eKeywordId {
+ KEYWORD_NONE = -1,
+ KEYWORD_function,
+ KEYWORD_capital_function,
+ KEYWORD_object,
+ KEYWORD_capital_object,
+ KEYWORD_prototype,
+ KEYWORD_var,
+ KEYWORD_new,
+ KEYWORD_this,
+ KEYWORD_for,
+ KEYWORD_while,
+ KEYWORD_do,
+ KEYWORD_if,
+ KEYWORD_else,
+ KEYWORD_switch,
+ KEYWORD_try,
+ KEYWORD_catch,
+ KEYWORD_finally
+} keywordId;
+
+/* Used to determine whether keyword is valid for the token language and
+ * what its ID is.
+ */
+typedef struct sKeywordDesc {
+ const char *name;
+ keywordId id;
+} keywordDesc;
+
+typedef enum eTokenType {
+ TOKEN_UNDEFINED,
+ TOKEN_CHARACTER,
+ TOKEN_CLOSE_PAREN,
+ TOKEN_SEMICOLON,
+ TOKEN_COLON,
+ TOKEN_COMMA,
+ TOKEN_KEYWORD,
+ TOKEN_OPEN_PAREN,
+ TOKEN_OPERATOR,
+ TOKEN_IDENTIFIER,
+ TOKEN_STRING,
+ TOKEN_PERIOD,
+ TOKEN_OPEN_CURLY,
+ TOKEN_CLOSE_CURLY,
+ TOKEN_EQUAL_SIGN,
+ TOKEN_FORWARD_SLASH,
+ TOKEN_OPEN_SQUARE,
+ TOKEN_CLOSE_SQUARE
+} tokenType;
+
+typedef struct sTokenInfo {
+ tokenType type;
+ keywordId keyword;
+ vString *string;
+ vString *scope;
+ unsigned long lineNumber;
+ fpos_t filePosition;
+ int nestLevel;
+ boolean ignoreTag;
+} tokenInfo;
+
+/*
+ * DATA DEFINITIONS
+ */
+
+static langType Lang_js;
+
+static jmp_buf Exception;
+
+typedef enum {
+ JSTAG_FUNCTION,
+ JSTAG_CLASS,
+ JSTAG_METHOD,
+ JSTAG_PROPERTY,
+ JSTAG_VARIABLE,
+ JSTAG_COUNT
+} jsKind;
+
+static kindOption JsKinds[] = {{TRUE, 'f', "function", "functions"},
+ {TRUE, 'c', "class", "classes"},
+ {TRUE, 'm', "method", "methods"},
+ {TRUE, 'p', "property", "properties"},
+ {TRUE, 'v', "variable", "global variables"}};
+
+static const keywordDesc JsKeywordTable[] = {
+ /* keyword keyword ID */
+ {"function", KEYWORD_function},
+ {"Function", KEYWORD_capital_function},
+ {"object", KEYWORD_object},
+ {"Object", KEYWORD_capital_object},
+ {"prototype", KEYWORD_prototype},
+ {"var", KEYWORD_var},
+ {"new", KEYWORD_new},
+ {"this", KEYWORD_this},
+ {"for", KEYWORD_for},
+ {"while", KEYWORD_while},
+ {"do", KEYWORD_do},
+ {"if", KEYWORD_if},
+ {"else", KEYWORD_else},
+ {"switch", KEYWORD_switch},
+ {"try", KEYWORD_try},
+ {"catch", KEYWORD_catch},
+ {"finally", KEYWORD_finally}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+/* Recursive functions */
+static void parseFunction(tokenInfo *const token);
+static boolean parseBlock(tokenInfo *const token, tokenInfo *const parent);
+static boolean parseLine(tokenInfo *const token, boolean is_inside_class);
+
+static boolean isIdentChar(const int c) {
+ return (boolean)(isalpha(c) || isdigit(c) || c == '$' || c == '@' ||
+ c == '_' || c == '#');
+}
+
+static void buildJsKeywordHash(void) {
+ const size_t count = sizeof(JsKeywordTable) / sizeof(JsKeywordTable[0]);
+ size_t i;
+ for (i = 0; i < count; ++i) {
+ const keywordDesc *const p = &JsKeywordTable[i];
+ addKeyword(p->name, Lang_js, (int)p->id);
+ }
+}
+
+static tokenInfo *newToken(void) {
+ tokenInfo *const token = xMalloc(1, tokenInfo);
+
+ token->type = TOKEN_UNDEFINED;
+ token->keyword = KEYWORD_NONE;
+ token->string = vStringNew();
+ token->scope = vStringNew();
+ token->nestLevel = 0;
+ token->ignoreTag = FALSE;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+
+ return token;
+}
+
+static void deleteToken(tokenInfo *const token) {
+ vStringDelete(token->string);
+ vStringDelete(token->scope);
+ eFree(token);
+}
+
+/*
+ * Tag generation functions
+ */
+
+/*
+static void makeConstTag (tokenInfo *const token, const jsKind kind)
+{
+ if (JsKinds [kind].enabled && ! token->ignoreTag )
+ {
+ const char *const name = vStringValue (token->string);
+ tagEntryInfo e;
+ initTagEntry (&e, name);
+
+ e.lineNumber = token->lineNumber;
+ e.filePosition = token->filePosition;
+ e.kindName = JsKinds [kind].name;
+ e.kind = JsKinds [kind].letter;
+
+ makeTagEntry (&e);
+ }
+}
+
+static void makeJsTag (tokenInfo *const token, const jsKind kind)
+{
+ vString * fulltag;
+
+ if (JsKinds [kind].enabled && ! token->ignoreTag )
+ {
+ *
+ * If a scope has been added to the token, change the token
+ * string to include the scope when making the tag.
+ *
+ if ( vStringLength(token->scope) > 0 )
+ {
+ *
+ fulltag = vStringNew ();
+ vStringCopy(fulltag, token->scope);
+ vStringCatS (fulltag, ".");
+ vStringCatS (fulltag, vStringValue(token->string));
+ vStringTerminate(fulltag);
+ vStringCopy(token->string, fulltag);
+ vStringDelete (fulltag);
+ *
+ jsKind parent_kind = JSTAG_CLASS;
+
+ *
+ * if we're creating a function (and not a method),
+ * guess we're inside another function
+ *
+ if (kind == JSTAG_FUNCTION)
+ parent_kind = JSTAG_FUNCTION;
+
+ e.extensionFields.scope[0] = JsKinds [parent_kind].name;
+ e.extensionFields.scope[1] = vStringValue
+(token->scope);
+ }
+ * makeConstTag (token, kind); *
+ makeTagEntry (&e);
+ }
+}
+*/
+
+static void makeJsTag(tokenInfo *const token, const jsKind kind) {
+ if (JsKinds[kind].enabled && !token->ignoreTag) {
+ const char *const name = vStringValue(token->string);
+ tagEntryInfo e;
+ initTagEntry(&e, name);
+
+ e.lineNumber = token->lineNumber;
+ e.filePosition = token->filePosition;
+ e.kindName = JsKinds[kind].name;
+ e.kind = JsKinds[kind].letter;
+
+ if (vStringLength(token->scope) > 0) {
+ jsKind parent_kind = JSTAG_CLASS;
+
+ /*
+ * If we're creating a function (and not a method),
+ * guess we're inside another function
+ */
+ if (kind == JSTAG_FUNCTION) parent_kind = JSTAG_FUNCTION;
+
+ e.extensionFields.scope[0] = JsKinds[parent_kind].name;
+ e.extensionFields.scope[1] = vStringValue(token->scope);
+ }
+
+ makeTagEntry(&e);
+ }
+}
+
+static void makeClassTag(tokenInfo *const token) {
+ vString *fulltag;
+
+ if (!token->ignoreTag) {
+ fulltag = vStringNew();
+ if (vStringLength(token->scope) > 0) {
+ vStringCopy(fulltag, token->scope);
+ vStringCatS(fulltag, ".");
+ vStringCatS(fulltag, vStringValue(token->string));
+ } else {
+ vStringCopy(fulltag, token->string);
+ }
+ vStringTerminate(fulltag);
+ if (!stringListHas(ClassNames, vStringValue(fulltag))) {
+ stringListAdd(ClassNames, vStringNewCopy(fulltag));
+ makeJsTag(token, JSTAG_CLASS);
+ }
+ vStringDelete(fulltag);
+ }
+}
+
+static void makeFunctionTag(tokenInfo *const token) {
+ vString *fulltag;
+
+ if (!token->ignoreTag) {
+ fulltag = vStringNew();
+ if (vStringLength(token->scope) > 0) {
+ vStringCopy(fulltag, token->scope);
+ vStringCatS(fulltag, ".");
+ vStringCatS(fulltag, vStringValue(token->string));
+ } else {
+ vStringCopy(fulltag, token->string);
+ }
+ vStringTerminate(fulltag);
+ if (!stringListHas(FunctionNames, vStringValue(fulltag))) {
+ stringListAdd(FunctionNames, vStringNewCopy(fulltag));
+ makeJsTag(token, JSTAG_FUNCTION);
+ }
+ vStringDelete(fulltag);
+ }
+}
+
+/*
+ * Parsing functions
+ */
+
+static void parseString(vString *const string, const int delimiter) {
+ boolean end = FALSE;
+ while (!end) {
+ int c = fileGetc();
+ if (c == EOF)
+ end = TRUE;
+ else if (c == '\\') {
+ c = fileGetc(); /* This maybe a ' or ". */
+ vStringPut(string, c);
+ } else if (c == delimiter)
+ end = TRUE;
+ else
+ vStringPut(string, c);
+ }
+ vStringTerminate(string);
+}
+
+/* Read a C identifier beginning with "firstChar" and places it into
+ * "name".
+ */
+static void parseIdentifier(vString *const string, const int firstChar) {
+ int c = firstChar;
+ Assert(isIdentChar(c));
+ do {
+ vStringPut(string, c);
+ c = fileGetc();
+ } while (isIdentChar(c));
+ vStringTerminate(string);
+ if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */
+}
+
+static void readToken(tokenInfo *const token) {
+ int c;
+
+ token->type = TOKEN_UNDEFINED;
+ token->keyword = KEYWORD_NONE;
+ vStringClear(token->string);
+
+getNextChar:
+ do {
+ c = fileGetc();
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ } while (c == '\t' || c == ' ' || c == '\n');
+
+ switch (c) {
+ case EOF:
+ longjmp(Exception, (int)ExceptionEOF);
+ break;
+ case '(':
+ token->type = TOKEN_OPEN_PAREN;
+ break;
+ case ')':
+ token->type = TOKEN_CLOSE_PAREN;
+ break;
+ case ';':
+ token->type = TOKEN_SEMICOLON;
+ break;
+ case ',':
+ token->type = TOKEN_COMMA;
+ break;
+ case '.':
+ token->type = TOKEN_PERIOD;
+ break;
+ case ':':
+ token->type = TOKEN_COLON;
+ break;
+ case '{':
+ token->type = TOKEN_OPEN_CURLY;
+ break;
+ case '}':
+ token->type = TOKEN_CLOSE_CURLY;
+ break;
+ case '=':
+ token->type = TOKEN_EQUAL_SIGN;
+ break;
+ case '[':
+ token->type = TOKEN_OPEN_SQUARE;
+ break;
+ case ']':
+ token->type = TOKEN_CLOSE_SQUARE;
+ break;
+
+ case '\'':
+ case '"':
+ token->type = TOKEN_STRING;
+ parseString(token->string, c);
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+
+ case '\\':
+ c = fileGetc();
+ if (c != '\\' && c != '"' && !isspace(c)) fileUngetc(c);
+ token->type = TOKEN_CHARACTER;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+
+ case '/': {
+ int d = fileGetc();
+ if ((d != '*') && /* is this the start of a comment? */
+ (d != '/')) /* is a one line comment? */
+ {
+ token->type = TOKEN_FORWARD_SLASH;
+ fileUngetc(d);
+ } else {
+ if (d == '*') {
+ do {
+ fileSkipToCharacter('*');
+ c = fileGetc();
+ if (c == '/')
+ break;
+ else
+ fileUngetc(c);
+ } while (c != EOF && c != '\0');
+ goto getNextChar;
+ } else if (d == '/') /* is this the start of a comment? */
+ {
+ fileSkipToCharacter('\n');
+ goto getNextChar;
+ }
+ }
+ break;
+ }
+
+ default:
+ if (!isIdentChar(c))
+ token->type = TOKEN_UNDEFINED;
+ else {
+ parseIdentifier(token->string, c);
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ token->keyword = analyzeToken(token->string, Lang_js);
+ if (isKeyword(token, KEYWORD_NONE))
+ token->type = TOKEN_IDENTIFIER;
+ else
+ token->type = TOKEN_KEYWORD;
+ }
+ break;
+ }
+}
+
+static void copyToken(tokenInfo *const dest, tokenInfo *const src) {
+ dest->nestLevel = src->nestLevel;
+ dest->lineNumber = src->lineNumber;
+ dest->filePosition = src->filePosition;
+ dest->type = src->type;
+ dest->keyword = src->keyword;
+ vStringCopy(dest->string, src->string);
+ vStringCopy(dest->scope, src->scope);
+}
+
+/*
+ * Token parsing functions
+ */
+
+static void skipArgumentList(tokenInfo *const token) {
+ int nest_level = 0;
+
+ /*
+ * Other databases can have arguments with fully declared
+ * datatypes:
+ * ( name varchar(30), text binary(10) )
+ * So we must check for nested open and closing parantheses
+ */
+
+ if (isType(token, TOKEN_OPEN_PAREN)) /* arguments? */
+ {
+ nest_level++;
+ while (!(isType(token, TOKEN_CLOSE_PAREN) && (nest_level == 0))) {
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ nest_level++;
+ }
+ if (isType(token, TOKEN_CLOSE_PAREN)) {
+ if (nest_level > 0) {
+ nest_level--;
+ }
+ }
+ }
+ readToken(token);
+ }
+}
+
+static void skipArrayList(tokenInfo *const token) {
+ int nest_level = 0;
+
+ /*
+ * Handle square brackets
+ * var name[1]
+ * So we must check for nested open and closing square brackets
+ */
+
+ if (isType(token, TOKEN_OPEN_SQUARE)) /* arguments? */
+ {
+ nest_level++;
+ while (!(isType(token, TOKEN_CLOSE_SQUARE) && (nest_level == 0))) {
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_SQUARE)) {
+ nest_level++;
+ }
+ if (isType(token, TOKEN_CLOSE_SQUARE)) {
+ if (nest_level > 0) {
+ nest_level--;
+ }
+ }
+ }
+ readToken(token);
+ }
+}
+
+static void addContext(tokenInfo *const parent, const tokenInfo *const child) {
+ if (vStringLength(parent->string) > 0) {
+ vStringCatS(parent->string, ".");
+ }
+ vStringCatS(parent->string, vStringValue(child->string));
+ vStringTerminate(parent->string);
+}
+
+static void addToScope(tokenInfo *const token, vString *const extra) {
+ if (vStringLength(token->scope) > 0) {
+ vStringCatS(token->scope, ".");
+ }
+ vStringCatS(token->scope, vStringValue(extra));
+ vStringTerminate(token->scope);
+}
+
+/*
+ * Scanning functions
+ */
+
+static void findCmdTerm(tokenInfo *const token) {
+ /*
+ * Read until we find either a semicolon or closing brace.
+ * Any nested braces will be handled within.
+ */
+ while (
+ !(isType(token, TOKEN_SEMICOLON) || isType(token, TOKEN_CLOSE_CURLY))) {
+ /* Handle nested blocks */
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ parseBlock(token, token);
+ } else if (isType(token, TOKEN_OPEN_PAREN)) {
+ skipArgumentList(token);
+ } else {
+ readToken(token);
+ }
+ }
+}
+
+static void parseSwitch(tokenInfo *const token) {
+ /*
+ * switch (expression){
+ * case value1:
+ * statement;
+ * break;
+ * case value2:
+ * statement;
+ * break;
+ * default : statement;
+ * }
+ */
+
+ readToken(token);
+
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ /*
+ * Handle nameless functions, these will only
+ * be considered methods.
+ */
+ skipArgumentList(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * This will be either a function or a class.
+ * We can only determine this by checking the body
+ * of the function. If we find a "this." we know
+ * it is a class, otherwise it is a function.
+ */
+ parseBlock(token, token);
+ }
+}
+
+static void parseLoop(tokenInfo *const token) {
+ /*
+ * Handles these statements
+ * for (x=0; x<3; x++)
+ * document.write("This text is repeated three times
");
+ *
+ * for (x=0; x<3; x++)
+ * {
+ * document.write("This text is repeated three times
");
+ * }
+ *
+ * while (number<5){
+ * document.write(number+"
");
+ * number++;
+ * }
+ *
+ * do{
+ * document.write(number+"
");
+ * number++;
+ * }
+ * while (number<5);
+ */
+
+ if (isKeyword(token, KEYWORD_for) || isKeyword(token, KEYWORD_while)) {
+ readToken(token);
+
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ /*
+ * Handle nameless functions, these will only
+ * be considered methods.
+ */
+ skipArgumentList(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * This will be either a function or a class.
+ * We can only determine this by checking the body
+ * of the function. If we find a "this." we know
+ * it is a class, otherwise it is a function.
+ */
+ parseBlock(token, token);
+ } else {
+ parseLine(token, FALSE);
+ }
+ } else if (isKeyword(token, KEYWORD_do)) {
+ readToken(token);
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * This will be either a function or a class.
+ * We can only determine this by checking the body
+ * of the function. If we find a "this." we know
+ * it is a class, otherwise it is a function.
+ */
+ parseBlock(token, token);
+ } else {
+ parseLine(token, FALSE);
+ }
+
+ readToken(token);
+
+ if (isKeyword(token, KEYWORD_while)) {
+ readToken(token);
+
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ /*
+ * Handle nameless functions, these will only
+ * be considered methods.
+ */
+ skipArgumentList(token);
+ }
+ }
+ }
+}
+
+static boolean parseIf(tokenInfo *const token) {
+ boolean read_next_token = TRUE;
+ /*
+ * If statements have two forms
+ * if ( ... )
+ * one line;
+ *
+ * if ( ... )
+ * statement;
+ * else
+ * statement
+ *
+ * if ( ... ) {
+ * multiple;
+ * statements;
+ * }
+ *
+ *
+ * if ( ... ) {
+ * return elem
+ * }
+ *
+ * This example if correctly written, but the
+ * else contains only 1 statement without a terminator
+ * since the function finishes with the closing brace.
+ *
+ * function a(flag){
+ * if(flag)
+ * test(1);
+ * else
+ * test(2)
+ * }
+ *
+ * TODO: Deal with statements that can optional end
+ * without a semi-colon. Currently this messes up
+ * the parsing of blocks.
+ * Need to somehow detect this has happened, and either
+ * backup a token, or skip reading the next token if
+ * that is possible from all code locations.
+ *
+ */
+
+ readToken(token);
+
+ if (isKeyword(token, KEYWORD_if)) {
+ /*
+ * Check for an "else if" and consume the "if"
+ */
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ /*
+ * Handle nameless functions, these will only
+ * be considered methods.
+ */
+ skipArgumentList(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * This will be either a function or a class.
+ * We can only determine this by checking the body
+ * of the function. If we find a "this." we know
+ * it is a class, otherwise it is a function.
+ */
+ parseBlock(token, token);
+ } else {
+ findCmdTerm(token);
+
+ /*
+ * The IF could be followed by an ELSE statement.
+ * This too could have two formats, a curly braced
+ * multiline section, or another single line.
+ */
+
+ if (isType(token, TOKEN_CLOSE_CURLY)) {
+ /*
+ * This statement did not have a line terminator.
+ */
+ read_next_token = FALSE;
+ } else {
+ readToken(token);
+
+ if (isType(token, TOKEN_CLOSE_CURLY)) {
+ /*
+ * This statement did not have a line terminator.
+ */
+ read_next_token = FALSE;
+ } else {
+ if (isKeyword(token, KEYWORD_else)) read_next_token = parseIf(token);
+ }
+ }
+ }
+ return read_next_token;
+}
+
+static void parseFunction(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+ boolean is_class = FALSE;
+
+ /*
+ * This deals with these formats
+ * function validFunctionTwo(a,b) {}
+ */
+
+ readToken(name);
+ /* Add scope in case this is an INNER function */
+ addToScope(name, token->scope);
+
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ do {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_NONE)) {
+ addContext(name, token);
+ readToken(token);
+ }
+ } while (isType(token, TOKEN_PERIOD));
+ }
+
+ if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token);
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ is_class = parseBlock(token, name);
+ if (is_class)
+ makeClassTag(name);
+ else
+ makeFunctionTag(name);
+ }
+
+ findCmdTerm(token);
+
+ deleteToken(name);
+}
+
+static boolean parseBlock(tokenInfo *const token, tokenInfo *const parent) {
+ boolean is_class = FALSE;
+ boolean read_next_token = TRUE;
+ vString *saveScope = vStringNew();
+
+ token->nestLevel++;
+ /*
+ * Make this routine a bit more forgiving.
+ * If called on an open_curly advance it
+ */
+ if (isType(token, TOKEN_OPEN_CURLY) && isKeyword(token, KEYWORD_NONE))
+ readToken(token);
+
+ if (!isType(token, TOKEN_CLOSE_CURLY)) {
+ /*
+ * Read until we find the closing brace,
+ * any nested braces will be handled within
+ */
+ do {
+ read_next_token = TRUE;
+ if (isKeyword(token, KEYWORD_this)) {
+ /*
+ * Means we are inside a class and have found
+ * a class, not a function
+ */
+ is_class = TRUE;
+ vStringCopy(saveScope, token->scope);
+ addToScope(token, parent->string);
+
+ /*
+ * Ignore the remainder of the line
+ * findCmdTerm(token);
+ */
+ parseLine(token, is_class);
+
+ vStringCopy(token->scope, saveScope);
+ } else if (isKeyword(token, KEYWORD_var)) {
+ /*
+ * Potentially we have found an inner function.
+ * Set something to indicate the scope
+ */
+ vStringCopy(saveScope, token->scope);
+ addToScope(token, parent->string);
+ parseLine(token, is_class);
+ vStringCopy(token->scope, saveScope);
+ } else if (isKeyword(token, KEYWORD_function)) {
+ vStringCopy(saveScope, token->scope);
+ addToScope(token, parent->string);
+ parseFunction(token);
+ vStringCopy(token->scope, saveScope);
+ } else if (isType(token, TOKEN_OPEN_CURLY)) {
+ /* Handle nested blocks */
+ parseBlock(token, parent);
+ } else {
+ /*
+ * It is possible for a line to have no terminator
+ * if the following line is a closing brace.
+ * parseLine will detect this case and indicate
+ * whether we should read an additional token.
+ */
+ read_next_token = parseLine(token, is_class);
+ }
+
+ /*
+ * Always read a new token unless we find a statement without
+ * a ending terminator
+ */
+ if (read_next_token) readToken(token);
+
+ /*
+ * If we find a statement without a terminator consider the
+ * block finished, otherwise the stack will be off by one.
+ */
+ } while (!isType(token, TOKEN_CLOSE_CURLY) && read_next_token);
+ }
+
+ vStringDelete(saveScope);
+ token->nestLevel--;
+
+ return is_class;
+}
+
+static boolean parseMethods(tokenInfo *const token, tokenInfo *const class) {
+ tokenInfo *const name = newToken();
+ boolean has_methods = FALSE;
+
+ /*
+ * This deals with these formats
+ * validProperty : 2,
+ * validMethod : function(a,b) {}
+ * 'validMethod2' : function(a,b) {}
+ * container.dirtyTab = {'url': false, 'title':false, 'snapshot':false,
+ *'*': false}
+ */
+
+ do {
+ readToken(token);
+ if (isType(token, TOKEN_CLOSE_CURLY)) {
+ /*
+ * This was most likely a variable declaration of a hash table.
+ * indicate there were no methods and return.
+ */
+ has_methods = FALSE;
+ goto cleanUp;
+ }
+
+ if (isType(token, TOKEN_STRING) || isKeyword(token, KEYWORD_NONE)) {
+ copyToken(name, token);
+
+ readToken(token);
+ if (isType(token, TOKEN_COLON)) {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_function)) {
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ skipArgumentList(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ has_methods = TRUE;
+ addToScope(name, class->string);
+ makeJsTag(name, JSTAG_METHOD);
+ parseBlock(token, name);
+
+ /*
+ * Read to the closing curly, check next
+ * token, if a comma, we must loop again
+ */
+ readToken(token);
+ }
+ } else {
+ has_methods = TRUE;
+ addToScope(name, class->string);
+ makeJsTag(name, JSTAG_PROPERTY);
+
+ /*
+ * Read the next token, if a comma
+ * we must loop again
+ */
+ readToken(token);
+ }
+ }
+ }
+ } while (isType(token, TOKEN_COMMA));
+
+ findCmdTerm(token);
+
+cleanUp:
+ deleteToken(name);
+
+ return has_methods;
+}
+
+static boolean parseStatement(tokenInfo *const token, boolean is_inside_class) {
+ tokenInfo *const name = newToken();
+ tokenInfo *const secondary_name = newToken();
+ vString *saveScope = vStringNew();
+ boolean is_class = FALSE;
+ boolean is_terminated = TRUE;
+ boolean is_global = FALSE;
+ boolean is_prototype = FALSE;
+ boolean has_methods = FALSE;
+ vString *fulltag;
+
+ vStringClear(saveScope);
+ /*
+ * Functions can be named or unnamed.
+ * This deals with these formats:
+ * Function
+ * validFunctionOne = function(a,b) {}
+ * testlib.validFunctionFive = function(a,b) {}
+ * var innerThree = function(a,b) {}
+ * var innerFour = (a,b) {}
+ * var D2 = secondary_fcn_name(a,b) {}
+ * var D3 = new Function("a", "b", "return a+b;");
+ * Class
+ * testlib.extras.ValidClassOne = function(a,b) {
+ * this.a = a;
+ * }
+ * Class Methods
+ * testlib.extras.ValidClassOne.prototype = {
+ * 'validMethodOne' : function(a,b) {},
+ * 'validMethodTwo' : function(a,b) {}
+ * }
+ * ValidClassTwo = function ()
+ * {
+ * this.validMethodThree = function() {}
+ * // unnamed method
+ * this.validMethodFour = () {}
+ * }
+ * Database.prototype.validMethodThree = Database_getTodaysDate;
+ */
+
+ if (is_inside_class) is_class = TRUE;
+ /*
+ * var can preceed an inner function
+ */
+ if (isKeyword(token, KEYWORD_var)) {
+ /*
+ * Only create variables for global scope
+ */
+ if (token->nestLevel == 0) {
+ is_global = TRUE;
+ }
+ readToken(token);
+ }
+
+ if (isKeyword(token, KEYWORD_this)) {
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ readToken(token);
+ }
+ }
+
+ copyToken(name, token);
+
+ while (!isType(token, TOKEN_CLOSE_CURLY) && !isType(token, TOKEN_SEMICOLON) &&
+ !isType(token, TOKEN_EQUAL_SIGN)) {
+ /* Potentially the name of the function */
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ /*
+ * Cannot be a global variable is it has dot references in the name
+ */
+ is_global = FALSE;
+ do {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_NONE)) {
+ if (is_class) {
+ vStringCopy(saveScope, token->scope);
+ addToScope(token, name->string);
+ } else
+ addContext(name, token);
+ } else if (isKeyword(token, KEYWORD_prototype)) {
+ /*
+ * When we reach the "prototype" tag, we infer:
+ * "BindAgent" is a class
+ * "build" is a method
+ *
+ * function BindAgent( repeatableIdName, newParentIdName ) {
+ * }
+ *
+ * CASE 1
+ * Specified function name: "build"
+ * BindAgent.prototype.build = function( mode ) {
+ * ignore everything within this function
+ * }
+ *
+ * CASE 2
+ * Prototype listing
+ * ValidClassOne.prototype = {
+ * 'validMethodOne' : function(a,b) {},
+ * 'validMethodTwo' : function(a,b) {}
+ * }
+ *
+ */
+ makeClassTag(name);
+ is_class = TRUE;
+ is_prototype = TRUE;
+
+ /*
+ * There should a ".function_name" next.
+ */
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ /*
+ * Handle CASE 1
+ */
+ readToken(token);
+ if (isKeyword(token, KEYWORD_NONE)) {
+ vStringCopy(saveScope, token->scope);
+ addToScope(token, name->string);
+
+ makeJsTag(token, JSTAG_METHOD);
+ /*
+ * We can read until the end of the block / statement.
+ * We need to correctly parse any nested blocks, but
+ * we do NOT want to create any tags based on what is
+ * within the blocks.
+ */
+ token->ignoreTag = TRUE;
+ /*
+ * Find to the end of the statement
+ */
+ findCmdTerm(token);
+ token->ignoreTag = FALSE;
+ is_terminated = TRUE;
+ goto cleanUp;
+ }
+ } else if (isType(token, TOKEN_EQUAL_SIGN)) {
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * Handle CASE 2
+ *
+ * Creates tags for each of these class methods
+ * ValidClassOne.prototype = {
+ * 'validMethodOne' : function(a,b) {},
+ * 'validMethodTwo' : function(a,b) {}
+ * }
+ */
+ parseMethods(token, name);
+ /*
+ * Find to the end of the statement
+ */
+ findCmdTerm(token);
+ token->ignoreTag = FALSE;
+ is_terminated = TRUE;
+ goto cleanUp;
+ }
+ }
+ }
+ readToken(token);
+ } while (isType(token, TOKEN_PERIOD));
+ }
+
+ if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token);
+
+ if (isType(token, TOKEN_OPEN_SQUARE)) skipArrayList(token);
+
+ /*
+ if ( isType (token, TOKEN_OPEN_CURLY) )
+ {
+ is_class = parseBlock (token, name);
+ }
+ */
+ }
+
+ if (isType(token, TOKEN_CLOSE_CURLY)) {
+ /*
+ * Reaching this section without having
+ * processed an open curly brace indicates
+ * the statement is most likely not terminated.
+ */
+ is_terminated = FALSE;
+ goto cleanUp;
+ }
+
+ if (isType(token, TOKEN_SEMICOLON)) {
+ /*
+ * Only create variables for global scope
+ */
+ if (token->nestLevel == 0 && is_global) {
+ /*
+ * Handles this syntax:
+ * var g_var2;
+ */
+ if (isType(token, TOKEN_SEMICOLON)) makeJsTag(name, JSTAG_VARIABLE);
+ }
+ /*
+ * Statement has ended.
+ * This deals with calls to functions, like:
+ * alert(..);
+ */
+ goto cleanUp;
+ }
+
+ if (isType(token, TOKEN_EQUAL_SIGN)) {
+ readToken(token);
+
+ if (isKeyword(token, KEYWORD_function)) {
+ readToken(token);
+
+ if (isKeyword(token, KEYWORD_NONE) && !isType(token, TOKEN_OPEN_PAREN)) {
+ /*
+ * Functions of this format:
+ * var D2A = function theAdd(a, b)
+ * {
+ * return a+b;
+ * }
+ * Are really two separate defined functions and
+ * can be referenced in two ways:
+ * alert( D2A(1,2) ); // produces 3
+ * alert( theAdd(1,2) ); // also produces 3
+ * So it must have two tags:
+ * D2A
+ * theAdd
+ * Save the reference to the name for later use, once
+ * we have established this is a valid function we will
+ * create the secondary reference to it.
+ */
+ copyToken(secondary_name, token);
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token);
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * This will be either a function or a class.
+ * We can only determine this by checking the body
+ * of the function. If we find a "this." we know
+ * it is a class, otherwise it is a function.
+ */
+ if (is_inside_class) {
+ makeJsTag(name, JSTAG_METHOD);
+ if (vStringLength(secondary_name->string) > 0)
+ makeFunctionTag(secondary_name);
+ parseBlock(token, name);
+ } else {
+ is_class = parseBlock(token, name);
+ if (is_class)
+ makeClassTag(name);
+ else
+ makeFunctionTag(name);
+
+ if (vStringLength(secondary_name->string) > 0)
+ makeFunctionTag(secondary_name);
+
+ /*
+ * Find to the end of the statement
+ */
+ goto cleanUp;
+ }
+ }
+ } else if (isType(token, TOKEN_OPEN_PAREN)) {
+ /*
+ * Handle nameless functions
+ * this.method_name = () {}
+ */
+ skipArgumentList(token);
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * Nameless functions are only setup as methods.
+ */
+ makeJsTag(name, JSTAG_METHOD);
+ parseBlock(token, name);
+ }
+ } else if (isType(token, TOKEN_OPEN_CURLY)) {
+ /*
+ * Creates tags for each of these class methods
+ * ValidClassOne.prototype = {
+ * 'validMethodOne' : function(a,b) {},
+ * 'validMethodTwo' : function(a,b) {}
+ * }
+ * Or checks if this is a hash variable.
+ * var z = {};
+ */
+ has_methods = parseMethods(token, name);
+ if (!has_methods) {
+ /*
+ * Only create variables for global scope
+ */
+ if (token->nestLevel == 0 && is_global) {
+ /*
+ * A pointer can be created to the function.
+ * If we recognize the function/class name ignore the variable.
+ * This format looks identical to a variable definition.
+ * A variable defined outside of a block is considered
+ * a global variable:
+ * var g_var1 = 1;
+ * var g_var2;
+ * This is not a global variable:
+ * var g_var = function;
+ * This is a global variable:
+ * var g_var = different_var_name;
+ */
+ fulltag = vStringNew();
+ if (vStringLength(token->scope) > 0) {
+ vStringCopy(fulltag, token->scope);
+ vStringCatS(fulltag, ".");
+ vStringCatS(fulltag, vStringValue(token->string));
+ } else {
+ vStringCopy(fulltag, token->string);
+ }
+ vStringTerminate(fulltag);
+ if (!stringListHas(FunctionNames, vStringValue(fulltag)) &&
+ !stringListHas(ClassNames, vStringValue(fulltag))) {
+ readToken(token);
+ if (!isType(token, TOKEN_SEMICOLON)) findCmdTerm(token);
+ if (isType(token, TOKEN_SEMICOLON)) makeJsTag(name, JSTAG_VARIABLE);
+ }
+ vStringDelete(fulltag);
+ }
+ }
+ if (isType(token, TOKEN_CLOSE_CURLY)) {
+ /*
+ * Assume the closing parantheses terminates
+ * this statements.
+ */
+ is_terminated = TRUE;
+ }
+ } else if (isKeyword(token, KEYWORD_new)) {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_function) ||
+ isKeyword(token, KEYWORD_capital_function) ||
+ isKeyword(token, KEYWORD_object) ||
+ isKeyword(token, KEYWORD_capital_object)) {
+ if (isKeyword(token, KEYWORD_object) ||
+ isKeyword(token, KEYWORD_capital_object))
+ is_class = TRUE;
+
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token);
+
+ if (isType(token, TOKEN_SEMICOLON)) {
+ if (token->nestLevel == 0) {
+ if (is_class) {
+ makeClassTag(name);
+ } else {
+ makeFunctionTag(name);
+ }
+ }
+ }
+ }
+ } else if (isKeyword(token, KEYWORD_NONE)) {
+ /*
+ * Only create variables for global scope
+ */
+ if (token->nestLevel == 0 && is_global) {
+ /*
+ * A pointer can be created to the function.
+ * If we recognize the function/class name ignore the variable.
+ * This format looks identical to a variable definition.
+ * A variable defined outside of a block is considered
+ * a global variable:
+ * var g_var1 = 1;
+ * var g_var2;
+ * This is not a global variable:
+ * var g_var = function;
+ * This is a global variable:
+ * var g_var = different_var_name;
+ */
+ fulltag = vStringNew();
+ if (vStringLength(token->scope) > 0) {
+ vStringCopy(fulltag, token->scope);
+ vStringCatS(fulltag, ".");
+ vStringCatS(fulltag, vStringValue(token->string));
+ } else {
+ vStringCopy(fulltag, token->string);
+ }
+ vStringTerminate(fulltag);
+ if (!stringListHas(FunctionNames, vStringValue(fulltag)) &&
+ !stringListHas(ClassNames, vStringValue(fulltag))) {
+ findCmdTerm(token);
+ if (isType(token, TOKEN_SEMICOLON)) makeJsTag(name, JSTAG_VARIABLE);
+ }
+ vStringDelete(fulltag);
+ }
+ }
+ }
+ findCmdTerm(token);
+
+ /*
+ * Statements can be optionally terminated in the case of
+ * statement prior to a close curly brace as in the
+ * document.write line below:
+ *
+ * function checkForUpdate() {
+ * if( 1==1 ) {
+ * document.write("hello from checkForUpdate
")
+ * }
+ * return 1;
+ * }
+ */
+ if (!is_terminated && isType(token, TOKEN_CLOSE_CURLY)) is_terminated = FALSE;
+
+cleanUp:
+ vStringCopy(token->scope, saveScope);
+ deleteToken(name);
+ deleteToken(secondary_name);
+ vStringDelete(saveScope);
+
+ return is_terminated;
+}
+
+static boolean parseLine(tokenInfo *const token, boolean is_inside_class) {
+ boolean is_terminated = TRUE;
+ /*
+ * Detect the common statements, if, while, for, do, ...
+ * This is necessary since the last statement within a block "{}"
+ * can be optionally terminated.
+ *
+ * If the statement is not terminated, we need to tell
+ * the calling routine to prevent reading an additional token
+ * looking for the end of the statement.
+ */
+
+ if (isType(token, TOKEN_KEYWORD)) {
+ switch (token->keyword) {
+ case KEYWORD_for:
+ case KEYWORD_while:
+ case KEYWORD_do:
+ parseLoop(token);
+ break;
+ case KEYWORD_if:
+ case KEYWORD_else:
+ case KEYWORD_try:
+ case KEYWORD_catch:
+ case KEYWORD_finally:
+ /* Common semantics */
+ is_terminated = parseIf(token);
+ break;
+ case KEYWORD_switch:
+ parseSwitch(token);
+ break;
+ default:
+ parseStatement(token, is_inside_class);
+ break;
+ }
+ } else {
+ /*
+ * Special case where single line statements may not be
+ * SEMICOLON terminated. parseBlock needs to know this
+ * so that it does not read the next token.
+ */
+ is_terminated = parseStatement(token, is_inside_class);
+ }
+ return is_terminated;
+}
+
+static void parseJsFile(tokenInfo *const token) {
+ do {
+ readToken(token);
+
+ if (isType(token, TOKEN_KEYWORD)) {
+ switch (token->keyword) {
+ case KEYWORD_function:
+ parseFunction(token);
+ break;
+ default:
+ parseLine(token, FALSE);
+ break;
+ }
+ } else {
+ parseLine(token, FALSE);
+ }
+ } while (TRUE);
+}
+
+static void initialize(const langType language) {
+ Assert(sizeof(JsKinds) / sizeof(JsKinds[0]) == JSTAG_COUNT);
+ Lang_js = language;
+ buildJsKeywordHash();
+}
+
+static void findJsTags(void) {
+ tokenInfo *const token = newToken();
+ exception_t exception;
+
+ ClassNames = stringListNew();
+ FunctionNames = stringListNew();
+
+ exception = (exception_t)(setjmp(Exception));
+ while (exception == ExceptionNone) parseJsFile(token);
+
+ stringListDelete(ClassNames);
+ stringListDelete(FunctionNames);
+ ClassNames = NULL;
+ FunctionNames = NULL;
+ deleteToken(token);
+}
+
+/* Create parser definition stucture */
+extern parserDefinition *JavaScriptParser(void) {
+ static const char *const extensions[] = {"js", NULL};
+ parserDefinition *const def = parserNew("JavaScript");
+ def->extensions = extensions;
+ /*
+ * New definitions for parsing instead of regex
+ */
+ def->kinds = JsKinds;
+ def->kindCount = KIND_COUNT(JsKinds);
+ def->parser = findJsTags;
+ def->initialize = initialize;
+
+ return def;
+}
+/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */
diff --git a/third_party/ctags/keyword.c b/third_party/ctags/keyword.c
new file mode 100644
index 000000000..8db16e0f7
--- /dev/null
+++ b/third_party/ctags/keyword.c
@@ -0,0 +1,222 @@
+/*
+ * $Id: keyword.c 715 2009-07-06 03:31:00Z dhiebert $
+ *
+ * Copyright (c) 1998-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * Manages a keyword hash.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/routines.h"
+
+/*
+ * MACROS
+ */
+#define HASH_EXPONENT 7 /* must be less than 17 */
+
+/*
+ * DATA DECLARATIONS
+ */
+typedef struct sHashEntry {
+ struct sHashEntry *next;
+ const char *string;
+ langType language;
+ int value;
+} hashEntry;
+
+/*
+ * DATA DEFINITIONS
+ */
+static const unsigned int TableSize = 1 << HASH_EXPONENT;
+static hashEntry **HashTable = NULL;
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static hashEntry **getHashTable(void) {
+ static boolean allocated = FALSE;
+
+ if (!allocated) {
+ unsigned int i;
+
+ HashTable = xMalloc(TableSize, hashEntry *);
+
+ for (i = 0; i < TableSize; ++i) HashTable[i] = NULL;
+
+ allocated = TRUE;
+ }
+ return HashTable;
+}
+
+static hashEntry *getHashTableEntry(unsigned long hashedValue) {
+ hashEntry **const table = getHashTable();
+ hashEntry *entry;
+
+ Assert(hashedValue < TableSize);
+ entry = table[hashedValue];
+
+ return entry;
+}
+
+static unsigned long hashValue(const char *const string) {
+ unsigned long value = 0;
+ const unsigned char *p;
+
+ Assert(string != NULL);
+
+ /* We combine the various words of the multiword key using the method
+ * described on page 512 of Vol. 3 of "The Art of Computer Programming".
+ */
+ for (p = (const unsigned char *)string; *p != '\0'; ++p) {
+ value <<= 1;
+ if (value & 0x00000100L) value = (value & 0x000000ffL) + 1L;
+ value ^= *p;
+ }
+ /* Algorithm from page 509 of Vol. 3 of "The Art of Computer Programming"
+ * Treats "value" as a 16-bit integer plus 16-bit fraction.
+ */
+ value *= 40503L; /* = 2^16 * 0.6180339887 ("golden ratio") */
+ value &= 0x0000ffffL; /* keep fractional part */
+ value >>= 16 - HASH_EXPONENT; /* scale up by hash size and move down */
+
+ return value;
+}
+
+static hashEntry *newEntry(const char *const string, langType language,
+ int value) {
+ hashEntry *const entry = xMalloc(1, hashEntry);
+
+ entry->next = NULL;
+ entry->string = string;
+ entry->language = language;
+ entry->value = value;
+
+ return entry;
+}
+
+/* Note that it is assumed that a "value" of zero means an undefined keyword
+ * and clients of this function should observe this. Also, all keywords added
+ * should be added in lower case. If we encounter a case-sensitive language
+ * whose keywords are in upper case, we will need to redesign this.
+ */
+extern void addKeyword(const char *const string, langType language, int value) {
+ const unsigned long hashedValue = hashValue(string);
+ hashEntry *entry = getHashTableEntry(hashedValue);
+
+ if (entry == NULL) {
+ hashEntry **const table = getHashTable();
+ table[hashedValue] = newEntry(string, language, value);
+ } else {
+ hashEntry *prev = NULL;
+
+ while (entry != NULL) {
+ if (language == entry->language && strcmp(string, entry->string) == 0) {
+ Assert(("Already in table" == NULL));
+ }
+ prev = entry;
+ entry = entry->next;
+ }
+ if (entry == NULL) {
+ Assert(prev != NULL);
+ prev->next = newEntry(string, language, value);
+ }
+ }
+}
+
+extern int lookupKeyword(const char *const string, langType language) {
+ const unsigned long hashedValue = hashValue(string);
+ hashEntry *entry = getHashTableEntry(hashedValue);
+ int result = -1;
+
+ while (entry != NULL) {
+ if (language == entry->language && strcmp(string, entry->string) == 0) {
+ result = entry->value;
+ break;
+ }
+ entry = entry->next;
+ }
+ return result;
+}
+
+extern void freeKeywordTable(void) {
+ if (HashTable != NULL) {
+ unsigned int i;
+
+ for (i = 0; i < TableSize; ++i) {
+ hashEntry *entry = HashTable[i];
+
+ while (entry != NULL) {
+ hashEntry *next = entry->next;
+ eFree(entry);
+ entry = next;
+ }
+ }
+ eFree(HashTable);
+ }
+}
+
+extern int analyzeToken(vString *const name, langType language) {
+ vString *keyword = vStringNew();
+ int result;
+ vStringCopyToLower(keyword, name);
+ result = lookupKeyword(vStringValue(keyword), language);
+ vStringDelete(keyword);
+ return result;
+}
+
+#ifdef DEBUG
+
+static void printEntry(const hashEntry *const entry) {
+ printf(" %-15s %-7s\n", entry->string, getLanguageName(entry->language));
+}
+
+static unsigned int printBucket(const unsigned int i) {
+ hashEntry **const table = getHashTable();
+ hashEntry *entry = table[i];
+ unsigned int measure = 1;
+ boolean first = TRUE;
+
+ printf("%2d:", i);
+ if (entry == NULL)
+ printf("\n");
+ else
+ while (entry != NULL) {
+ if (!first)
+ printf(" ");
+ else {
+ printf(" ");
+ first = FALSE;
+ }
+ printEntry(entry);
+ entry = entry->next;
+ measure = 2 * measure;
+ }
+ return measure - 1;
+}
+
+extern void printKeywordTable(void) {
+ unsigned long emptyBucketCount = 0;
+ unsigned long measure = 0;
+ unsigned int i;
+
+ for (i = 0; i < TableSize; ++i) {
+ const unsigned int pass = printBucket(i);
+
+ measure += pass;
+ if (pass == 0) ++emptyBucketCount;
+ }
+
+ printf("spread measure = %ld\n", measure);
+ printf("%ld empty buckets\n", emptyBucketCount);
+}
+
+#endif
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/keyword.h b/third_party/ctags/keyword.h
new file mode 100644
index 000000000..337fd7b8e
--- /dev/null
+++ b/third_party/ctags/keyword.h
@@ -0,0 +1,18 @@
+#ifndef _KEYWORD_H
+#define _KEYWORD_H
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parse.h"
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+extern void addKeyword(const char *const string, langType language, int value);
+extern int lookupKeyword(const char *const string, langType language);
+extern void freeKeywordTable(void);
+#ifdef DEBUG
+extern void printKeywordTable(void);
+#endif
+extern int analyzeToken(vString *const name, langType language);
+
+#endif /* _KEYWORD_H */
diff --git a/third_party/ctags/lisp.c b/third_party/ctags/lisp.c
new file mode 100644
index 000000000..2830ed048
--- /dev/null
+++ b/third_party/ctags/lisp.c
@@ -0,0 +1,110 @@
+/*
+ * $Id: lisp.c 717 2009-07-07 03:40:50Z dhiebert $
+ *
+ * Copyright (c) 2000-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for LISP files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum { K_FUNCTION } lispKind;
+
+static kindOption LispKinds[] = {{TRUE, 'f', "function", "functions"}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+/*
+ * lisp tag functions
+ * look for (def or (DEF, quote or QUOTE
+ */
+static int L_isdef(const unsigned char *strp) {
+ return ((strp[1] == 'd' || strp[1] == 'D') &&
+ (strp[2] == 'e' || strp[2] == 'E') &&
+ (strp[3] == 'f' || strp[3] == 'F'));
+}
+
+static int L_isquote(const unsigned char *strp) {
+ return ((*(++strp) == 'q' || *strp == 'Q') &&
+ (*(++strp) == 'u' || *strp == 'U') &&
+ (*(++strp) == 'o' || *strp == 'O') &&
+ (*(++strp) == 't' || *strp == 'T') &&
+ (*(++strp) == 'e' || *strp == 'E') && isspace(*(++strp)));
+}
+
+static void L_getit(vString *const name, const unsigned char *dbp) {
+ const unsigned char *p;
+
+ if (*dbp == '\'') /* Skip prefix quote */
+ dbp++;
+ else if (*dbp == '(' && L_isquote(dbp)) /* Skip "(quote " */
+ {
+ dbp += 7;
+ while (isspace(*dbp)) dbp++;
+ }
+ for (p = dbp; *p != '\0' && *p != '(' && !isspace((int)*p) && *p != ')'; p++)
+ vStringPut(name, *p);
+ vStringTerminate(name);
+
+ if (vStringLength(name) > 0) makeSimpleTag(name, LispKinds, K_FUNCTION);
+ vStringClear(name);
+}
+
+/* Algorithm adapted from from GNU etags.
+ */
+static void findLispTags(void) {
+ vString *name = vStringNew();
+ const unsigned char *p;
+
+ while ((p = fileReadLine()) != NULL) {
+ if (*p == '(') {
+ if (L_isdef(p)) {
+ while (*p != '\0' && !isspace((int)*p)) p++;
+ while (isspace((int)*p)) p++;
+ L_getit(name, p);
+ } else {
+ /* Check for (foo::defmumble name-defined ... */
+ do
+ p++;
+ while (*p != '\0' && !isspace((int)*p) && *p != ':' && *p != '(' &&
+ *p != ')');
+ if (*p == ':') {
+ do
+ p++;
+ while (*p == ':');
+
+ if (L_isdef(p - 1)) {
+ while (*p != '\0' && !isspace((int)*p)) p++;
+ while (isspace(*p)) p++;
+ L_getit(name, p);
+ }
+ }
+ }
+ }
+ }
+ vStringDelete(name);
+}
+
+extern parserDefinition *LispParser(void) {
+ static const char *const extensions[] = {"cl", "clisp", "el", "l",
+ "lisp", "lsp", NULL};
+ parserDefinition *def = parserNew("Lisp");
+ def->kinds = LispKinds;
+ def->kindCount = KIND_COUNT(LispKinds);
+ def->extensions = extensions;
+ def->parser = findLispTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/lregex.c b/third_party/ctags/lregex.c
new file mode 100644
index 000000000..a70117495
--- /dev/null
+++ b/third_party/ctags/lregex.c
@@ -0,0 +1,616 @@
+/*
+ * $Id: lregex.c 747 2009-11-06 02:33:37Z dhiebert $
+ *
+ * Copyright (c) 2000-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for applying regular expression matching.
+ *
+ * The code for utlizing the Gnu regex package with regards to processing the
+ * regex option and checking for regex matches was adapted from routines in
+ * Gnu etags.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/regex/regex.h"
+
+#ifdef HAVE_REGEX
+
+/*
+ * MACROS
+ */
+
+/* Back-references \0 through \9 */
+#define BACK_REFERENCE_COUNT 10
+
+#if defined(HAVE_REGCOMP) && !defined(REGCOMP_BROKEN)
+#define POSIX_REGEX
+#endif
+
+#define REGEX_NAME "Regex"
+
+/*
+ * DATA DECLARATIONS
+ */
+#if defined(POSIX_REGEX)
+
+struct sKind {
+ boolean enabled;
+ char letter;
+ char* name;
+ char* description;
+};
+
+enum pType { PTRN_TAG, PTRN_CALLBACK };
+
+typedef struct {
+ regex_t* pattern;
+ enum pType type;
+ union {
+ struct {
+ char* name_pattern;
+ struct sKind kind;
+ } tag;
+ struct {
+ regexCallback function;
+ } callback;
+ } u;
+} regexPattern;
+
+#endif
+
+typedef struct {
+ regexPattern* patterns;
+ unsigned int count;
+} patternSet;
+
+/*
+ * DATA DEFINITIONS
+ */
+
+static boolean regexBroken = FALSE;
+
+/* Array of pattern sets, indexed by language */
+static patternSet* Sets = NULL;
+static int SetUpper = -1; /* upper language index in list */
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void clearPatternSet(const langType language) {
+ if (language <= SetUpper) {
+ patternSet* const set = Sets + language;
+ unsigned int i;
+ for (i = 0; i < set->count; ++i) {
+ regexPattern* p = &set->patterns[i];
+#if defined(POSIX_REGEX)
+ regfree(p->pattern);
+#endif
+ eFree(p->pattern);
+ p->pattern = NULL;
+
+ if (p->type == PTRN_TAG) {
+ eFree(p->u.tag.name_pattern);
+ p->u.tag.name_pattern = NULL;
+ eFree(p->u.tag.kind.name);
+ p->u.tag.kind.name = NULL;
+ if (p->u.tag.kind.description != NULL) {
+ eFree(p->u.tag.kind.description);
+ p->u.tag.kind.description = NULL;
+ }
+ }
+ }
+ if (set->patterns != NULL) eFree(set->patterns);
+ set->patterns = NULL;
+ set->count = 0;
+ }
+}
+
+/*
+ * Regex psuedo-parser
+ */
+
+static void makeRegexTag(const vString* const name,
+ const struct sKind* const kind) {
+ if (kind->enabled) {
+ tagEntryInfo e;
+ Assert(name != NULL && vStringLength(name) > 0);
+ Assert(kind != NULL);
+ initTagEntry(&e, vStringValue(name));
+ e.kind = kind->letter;
+ e.kindName = kind->name;
+ makeTagEntry(&e);
+ }
+}
+
+/*
+ * Regex pattern definition
+ */
+
+/* Take a string like "/blah/" and turn it into "blah", making sure
+ * that the first and last characters are the same, and handling
+ * quoted separator characters. Actually, stops on the occurrence of
+ * an unquoted separator. Also turns "\t" into a Tab character.
+ * Returns pointer to terminating separator. Works in place. Null
+ * terminates name string.
+ */
+static char* scanSeparators(char* name) {
+ char sep = name[0];
+ char* copyto = name;
+ boolean quoted = FALSE;
+
+ for (++name; *name != '\0'; ++name) {
+ if (quoted) {
+ if (*name == sep)
+ *copyto++ = sep;
+ else if (*name == 't')
+ *copyto++ = '\t';
+ else {
+ /* Something else is quoted, so preserve the quote. */
+ *copyto++ = '\\';
+ *copyto++ = *name;
+ }
+ quoted = FALSE;
+ } else if (*name == '\\')
+ quoted = TRUE;
+ else if (*name == sep) {
+ break;
+ } else
+ *copyto++ = *name;
+ }
+ *copyto = '\0';
+ return name;
+}
+
+/* Parse `regexp', in form "/regex/name/[k,Kind/]flags" (where the separator
+ * character is whatever the first character of `regexp' is), by breaking it
+ * up into null terminated strings, removing the separators, and expanding
+ * '\t' into tabs. When complete, `regexp' points to the line matching
+ * pattern, a pointer to the name matching pattern is written to `name', a
+ * pointer to the kinds is written to `kinds' (possibly NULL), and a pointer
+ * to the trailing flags is written to `flags'. If the pattern is not in the
+ * correct format, a false value is returned.
+ */
+static boolean parseTagRegex(char* const regexp, char** const name,
+ char** const kinds, char** const flags) {
+ boolean result = FALSE;
+ const int separator = (unsigned char)regexp[0];
+
+ *name = scanSeparators(regexp);
+ if (*regexp == '\0')
+ error(WARNING, "empty regexp");
+ else if (**name != separator)
+ error(WARNING, "%s: incomplete regexp", regexp);
+ else {
+ char* const third = scanSeparators(*name);
+ if (**name == '\0')
+ error(WARNING, "%s: regexp missing name pattern", regexp);
+ if ((*name)[strlen(*name) - 1] == '\\')
+ error(WARNING, "error in name pattern: \"%s\"", *name);
+ if (*third != separator)
+ error(WARNING, "%s: regexp missing final separator", regexp);
+ else {
+ char* const fourth = scanSeparators(third);
+ if (*fourth == separator) {
+ *kinds = third;
+ scanSeparators(fourth);
+ *flags = fourth;
+ } else {
+ *flags = third;
+ *kinds = NULL;
+ }
+ result = TRUE;
+ }
+ }
+ return result;
+}
+
+static void addCompiledTagPattern(const langType language,
+ regex_t* const pattern, char* const name,
+ const char kind, char* const kindName,
+ char* const description) {
+ patternSet* set;
+ regexPattern* ptrn;
+ if (language > SetUpper) {
+ int i;
+ Sets = xRealloc(Sets, (language + 1), patternSet);
+ for (i = SetUpper + 1; i <= language; ++i) {
+ Sets[i].patterns = NULL;
+ Sets[i].count = 0;
+ }
+ SetUpper = language;
+ }
+ set = Sets + language;
+ set->patterns = xRealloc(set->patterns, (set->count + 1), regexPattern);
+ ptrn = &set->patterns[set->count];
+ set->count += 1;
+
+ ptrn->pattern = pattern;
+ ptrn->type = PTRN_TAG;
+ ptrn->u.tag.name_pattern = name;
+ ptrn->u.tag.kind.enabled = TRUE;
+ ptrn->u.tag.kind.letter = kind;
+ ptrn->u.tag.kind.name = kindName;
+ ptrn->u.tag.kind.description = description;
+}
+
+static void addCompiledCallbackPattern(const langType language,
+ regex_t* const pattern,
+ const regexCallback callback) {
+ patternSet* set;
+ regexPattern* ptrn;
+ if (language > SetUpper) {
+ int i;
+ Sets = xRealloc(Sets, (language + 1), patternSet);
+ for (i = SetUpper + 1; i <= language; ++i) {
+ Sets[i].patterns = NULL;
+ Sets[i].count = 0;
+ }
+ SetUpper = language;
+ }
+ set = Sets + language;
+ set->patterns = xRealloc(set->patterns, (set->count + 1), regexPattern);
+ ptrn = &set->patterns[set->count];
+ set->count += 1;
+
+ ptrn->pattern = pattern;
+ ptrn->type = PTRN_CALLBACK;
+ ptrn->u.callback.function = callback;
+}
+
+#if defined(POSIX_REGEX)
+
+static regex_t* compileRegex(const char* const regexp,
+ const char* const flags) {
+ int cflags = REG_EXTENDED | REG_NEWLINE;
+ regex_t* result = NULL;
+ int errcode;
+ int i;
+ for (i = 0; flags != NULL && flags[i] != '\0'; ++i) {
+ switch ((int)flags[i]) {
+ case 'b':
+ cflags &= ~REG_EXTENDED;
+ break;
+ case 'e':
+ cflags |= REG_EXTENDED;
+ break;
+ case 'i':
+ cflags |= REG_ICASE;
+ break;
+ default:
+ error(WARNING, "unknown regex flag: '%c'", *flags);
+ break;
+ }
+ }
+ result = xMalloc(1, regex_t);
+ errcode = regcomp(result, regexp, cflags);
+ if (errcode != 0) {
+ char errmsg[256];
+ regerror(errcode, result, errmsg, 256);
+ error(WARNING, "regcomp %s: %s", regexp, errmsg);
+ regfree(result);
+ eFree(result);
+ result = NULL;
+ }
+ return result;
+}
+
+#endif
+
+static void parseKinds(const char* const kinds, char* const kind,
+ char** const kindName, char** description) {
+ *kind = '\0';
+ *kindName = NULL;
+ *description = NULL;
+ if (kinds == NULL || kinds[0] == '\0') {
+ *kind = 'r';
+ *kindName = eStrdup("regex");
+ } else if (kinds[0] != '\0') {
+ const char* k = kinds;
+ if (k[0] != ',' && (k[1] == ',' || k[1] == '\0'))
+ *kind = *k++;
+ else
+ *kind = 'r';
+ if (*k == ',') ++k;
+ if (k[0] == '\0')
+ *kindName = eStrdup("regex");
+ else {
+ const char* const comma = strchr(k, ',');
+ if (comma == NULL)
+ *kindName = eStrdup(k);
+ else {
+ *kindName = (char*)eMalloc(comma - k + 1);
+ strncpy(*kindName, k, comma - k);
+ (*kindName)[comma - k] = '\0';
+ k = comma + 1;
+ if (k[0] != '\0') *description = eStrdup(k);
+ }
+ }
+ }
+}
+
+static void printRegexKind(const regexPattern* pat, unsigned int i,
+ boolean indent) {
+ const struct sKind* const kind = &pat[i].u.tag.kind;
+ const char* const indentation = indent ? " " : "";
+ Assert(pat[i].type == PTRN_TAG);
+ printf("%s%c %s %s\n", indentation,
+ kind->letter != '\0' ? kind->letter : '?',
+ kind->description != NULL ? kind->description : kind->name,
+ kind->enabled ? "" : " [off]");
+}
+
+static void processLanguageRegex(const langType language,
+ const char* const parameter) {
+ if (parameter == NULL || parameter[0] == '\0')
+ clearPatternSet(language);
+ else if (parameter[0] != '@')
+ addLanguageRegex(language, parameter);
+ else if (!doesFileExist(parameter + 1))
+ error(WARNING, "cannot open regex file");
+ else {
+ const char* regexfile = parameter + 1;
+ FILE* const fp = fopen(regexfile, "r");
+ if (fp == NULL)
+ error(WARNING | PERROR, "%s", regexfile);
+ else {
+ vString* const regex = vStringNew();
+ while (readLine(regex, fp))
+ addLanguageRegex(language, vStringValue(regex));
+ fclose(fp);
+ vStringDelete(regex);
+ }
+ }
+}
+
+/*
+ * Regex pattern matching
+ */
+
+#if defined(POSIX_REGEX)
+
+static vString* substitute(const char* const in, const char* out,
+ const int nmatch, const regmatch_t* const pmatch) {
+ vString* result = vStringNew();
+ const char* p;
+ for (p = out; *p != '\0'; p++) {
+ if (*p == '\\' && isdigit((int)*++p)) {
+ const int dig = *p - '0';
+ if (0 < dig && dig < nmatch && pmatch[dig].rm_so != -1) {
+ const int diglen = pmatch[dig].rm_eo - pmatch[dig].rm_so;
+ vStringNCatS(result, in + pmatch[dig].rm_so, diglen);
+ }
+ } else if (*p != '\n' && *p != '\r')
+ vStringPut(result, *p);
+ }
+ vStringTerminate(result);
+ return result;
+}
+
+static void matchTagPattern(const vString* const line,
+ const regexPattern* const patbuf,
+ const regmatch_t* const pmatch) {
+ vString* const name =
+ substitute(vStringValue(line), patbuf->u.tag.name_pattern,
+ BACK_REFERENCE_COUNT, pmatch);
+ vStringStripLeading(name);
+ vStringStripTrailing(name);
+ if (vStringLength(name) > 0)
+ makeRegexTag(name, &patbuf->u.tag.kind);
+ else
+ error(WARNING, "%s:%ld: null expansion of name pattern \"%s\"",
+ getInputFileName(), getInputLineNumber(), patbuf->u.tag.name_pattern);
+ vStringDelete(name);
+}
+
+static void matchCallbackPattern(const vString* const line,
+ const regexPattern* const patbuf,
+ const regmatch_t* const pmatch) {
+ regexMatch matches[BACK_REFERENCE_COUNT];
+ unsigned int count = 0;
+ int i;
+ for (i = 0; i < BACK_REFERENCE_COUNT && pmatch[i].rm_so != -1; ++i) {
+ matches[i].start = pmatch[i].rm_so;
+ matches[i].length = pmatch[i].rm_eo - pmatch[i].rm_so;
+ ++count;
+ }
+ patbuf->u.callback.function(vStringValue(line), matches, count);
+}
+
+static boolean matchRegexPattern(const vString* const line,
+ const regexPattern* const patbuf) {
+ boolean result = FALSE;
+ regmatch_t pmatch[BACK_REFERENCE_COUNT];
+ const int match = regexec(patbuf->pattern, vStringValue(line),
+ BACK_REFERENCE_COUNT, pmatch, 0);
+ if (match == 0) {
+ result = TRUE;
+ if (patbuf->type == PTRN_TAG)
+ matchTagPattern(line, patbuf, pmatch);
+ else if (patbuf->type == PTRN_CALLBACK)
+ matchCallbackPattern(line, patbuf, pmatch);
+ else {
+ Assert("invalid pattern type" == NULL);
+ result = FALSE;
+ }
+ }
+ return result;
+}
+
+#endif
+
+/* PUBLIC INTERFACE */
+
+/* Match against all patterns for specified language. Returns true if at least
+ * on pattern matched.
+ */
+extern boolean matchRegex(const vString* const line, const langType language) {
+ boolean result = FALSE;
+ if (language != LANG_IGNORE && language <= SetUpper &&
+ Sets[language].count > 0) {
+ const patternSet* const set = Sets + language;
+ unsigned int i;
+ for (i = 0; i < set->count; ++i)
+ if (matchRegexPattern(line, set->patterns + i)) result = TRUE;
+ }
+ return result;
+}
+
+extern void findRegexTags(void) {
+ /* merely read all lines of the file */
+ while (fileReadLine() != NULL)
+ ;
+}
+
+#endif /* HAVE_REGEX */
+
+extern void addTagRegex(const langType language __unused__,
+ const char* const regex __unused__,
+ const char* const name __unused__,
+ const char* const kinds __unused__,
+ const char* const flags __unused__) {
+#ifdef HAVE_REGEX
+ Assert(regex != NULL);
+ Assert(name != NULL);
+ if (!regexBroken) {
+ regex_t* const cp = compileRegex(regex, flags);
+ if (cp != NULL) {
+ char kind;
+ char* kindName;
+ char* description;
+ parseKinds(kinds, &kind, &kindName, &description);
+ addCompiledTagPattern(language, cp, eStrdup(name), kind, kindName,
+ description);
+ }
+ }
+#endif
+}
+
+extern void addCallbackRegex(const langType language __unused__,
+ const char* const regex __unused__,
+ const char* const flags __unused__,
+ const regexCallback callback __unused__) {
+#ifdef HAVE_REGEX
+ Assert(regex != NULL);
+ if (!regexBroken) {
+ regex_t* const cp = compileRegex(regex, flags);
+ if (cp != NULL) addCompiledCallbackPattern(language, cp, callback);
+ }
+#endif
+}
+
+extern void addLanguageRegex(const langType language __unused__,
+ const char* const regex __unused__) {
+#ifdef HAVE_REGEX
+ if (!regexBroken) {
+ char* const regex_pat = eStrdup(regex);
+ char *name, *kinds, *flags;
+ if (parseTagRegex(regex_pat, &name, &kinds, &flags)) {
+ addTagRegex(language, regex_pat, name, kinds, flags);
+ eFree(regex_pat);
+ }
+ }
+#endif
+}
+
+/*
+ * Regex option parsing
+ */
+
+extern boolean processRegexOption(const char* const option,
+ const char* const parameter __unused__) {
+ boolean handled = FALSE;
+ const char* const dash = strchr(option, '-');
+ if (dash != NULL && strncmp(option, "regex", dash - option) == 0) {
+#ifdef HAVE_REGEX
+ langType language;
+ language = getNamedLanguage(dash + 1);
+ if (language == LANG_IGNORE)
+ error(WARNING, "unknown language \"%s\" in --%s option", (dash + 1),
+ option);
+ else
+ processLanguageRegex(language, parameter);
+#else
+ error(WARNING, "regex support not available; required for --%s option",
+ option);
+#endif
+ handled = TRUE;
+ }
+ return handled;
+}
+
+extern void disableRegexKinds(const langType language __unused__) {
+#ifdef HAVE_REGEX
+ if (language <= SetUpper && Sets[language].count > 0) {
+ patternSet* const set = Sets + language;
+ unsigned int i;
+ for (i = 0; i < set->count; ++i)
+ if (set->patterns[i].type == PTRN_TAG)
+ set->patterns[i].u.tag.kind.enabled = FALSE;
+ }
+#endif
+}
+
+extern boolean enableRegexKind(const langType language __unused__,
+ const int kind __unused__,
+ const boolean mode __unused__) {
+ boolean result = FALSE;
+#ifdef HAVE_REGEX
+ if (language <= SetUpper && Sets[language].count > 0) {
+ patternSet* const set = Sets + language;
+ unsigned int i;
+ for (i = 0; i < set->count; ++i)
+ if (set->patterns[i].type == PTRN_TAG &&
+ set->patterns[i].u.tag.kind.letter == kind) {
+ set->patterns[i].u.tag.kind.enabled = mode;
+ result = TRUE;
+ }
+ }
+#endif
+ return result;
+}
+
+extern void printRegexKinds(const langType language __unused__,
+ boolean indent __unused__) {
+#ifdef HAVE_REGEX
+ if (language <= SetUpper && Sets[language].count > 0) {
+ patternSet* const set = Sets + language;
+ unsigned int i;
+ for (i = 0; i < set->count; ++i)
+ if (set->patterns[i].type == PTRN_TAG)
+ printRegexKind(set->patterns, i, indent);
+ }
+#endif
+}
+
+extern void freeRegexResources(void) {
+#ifdef HAVE_REGEX
+ int i;
+ for (i = 0; i <= SetUpper; ++i) clearPatternSet(i);
+ if (Sets != NULL) eFree(Sets);
+ Sets = NULL;
+ SetUpper = -1;
+#endif
+}
+
+/* Check for broken regcomp() on Cygwin */
+extern void checkRegex(void) {
+#if defined(HAVE_REGEX) && defined(CHECK_REGCOMP)
+ regex_t patbuf;
+ int errcode;
+ if (regcomp(&patbuf, "/hello/", 0) != 0) {
+ error(WARNING, "Disabling broken regex");
+ regexBroken = TRUE;
+ }
+#endif
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/lua.c b/third_party/ctags/lua.c
new file mode 100644
index 000000000..a7253e0dc
--- /dev/null
+++ b/third_party/ctags/lua.c
@@ -0,0 +1,108 @@
+/*
+ * $Id: lua.c 443 2006-05-30 04:37:13Z darren $
+ *
+ * Copyright (c) 2000-2001, Max Ischenko .
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for Lua language.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum { K_FUNCTION } luaKind;
+
+static kindOption LuaKinds[] = {{TRUE, 'f', "function", "functions"}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+/* for debugging purposes */
+static void __unused__ print_string(char *p, char *q) {
+ for (; p != q; p++) fprintf(errout, "%c", *p);
+ fprintf(errout, "\n");
+}
+
+/*
+ * Helper function.
+ * Returns 1 if line looks like a line of Lua code.
+ *
+ * TODO: Recognize UNIX bang notation.
+ * (Lua treat first line as a comment if it starts with #!)
+ *
+ */
+static boolean is_a_code_line(const unsigned char *line) {
+ boolean result;
+ const unsigned char *p = line;
+ while (isspace((int)*p)) p++;
+ if (p[0] == '\0')
+ result = FALSE;
+ else if (p[0] == '-' && p[1] == '-')
+ result = FALSE;
+ else
+ result = TRUE;
+ return result;
+}
+
+static void extract_name(const char *begin, const char *end, vString *name) {
+ if (begin != NULL && end != NULL && begin < end) {
+ const char *cp;
+
+ while (isspace((int)*begin)) begin++;
+ while (isspace((int)*end)) end--;
+ if (begin < end) {
+ for (cp = begin; cp != end; cp++) vStringPut(name, (int)*cp);
+ vStringTerminate(name);
+
+ makeSimpleTag(name, LuaKinds, K_FUNCTION);
+ vStringClear(name);
+ }
+ }
+}
+
+static void findLuaTags(void) {
+ vString *name = vStringNew();
+ const unsigned char *line;
+
+ while ((line = fileReadLine()) != NULL) {
+ const char *p, *q;
+
+ if (!is_a_code_line(line)) continue;
+
+ p = (const char *)strstr((const char *)line, "function");
+ if (p == NULL) continue;
+
+ q = strchr((const char *)line, '=');
+
+ if (q == NULL) {
+ p = p + 9; /* skip the `function' word */
+ q = strchr((const char *)p, '(');
+ extract_name(p, q, name);
+ } else {
+ p = (const char *)&line[0];
+ extract_name(p, q, name);
+ }
+ }
+ vStringDelete(name);
+}
+
+extern parserDefinition *LuaParser(void) {
+ static const char *const extensions[] = {"lua", NULL};
+ parserDefinition *def = parserNew("Lua");
+ def->kinds = LuaKinds;
+ def->kindCount = KIND_COUNT(LuaKinds);
+ def->extensions = extensions;
+ def->parser = findLuaTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/main.c b/third_party/ctags/main.c
new file mode 100644
index 000000000..44516154a
--- /dev/null
+++ b/third_party/ctags/main.c
@@ -0,0 +1,478 @@
+/*
+ * $Id: main.c 536 2007-06-02 06:09:00Z elliotth $
+ *
+ * Copyright (c) 1996-2003, Darren Hiebert
+ *
+ * Author: Darren Hiebert
+ * http://ctags.sourceforge.net
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License. It is provided on an as-is basis and no
+ * responsibility is accepted for its failure to perform as expected.
+ *
+ * This is a reimplementation of the ctags (1) program. It is an attempt to
+ * provide a fully featured ctags program which is free of the limitations
+ * which most (all?) others are subject to.
+ *
+ * This module contains the start-up code and routines to determine the list
+ * of files to parsed for tags.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/calls/struct/dirent.h"
+#include "libc/calls/weirdtypes.h"
+#include "libc/str/str.h"
+#include "libc/time/time.h"
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/main.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+
+/*
+ * MACROS
+ */
+#define plural(value) (((unsigned long)(value) == 1L) ? "" : "s")
+
+/*
+ * DATA DEFINITIONS
+ */
+static struct { long files, lines, bytes; } Totals = {0, 0, 0};
+
+#ifdef AMIGA
+#include "third_party/ctags/ctags.h"
+static const char *VERsion = "$VER: " PROGRAM_NAME " " PROGRAM_VERSION " "
+#ifdef __SASC
+ __AMIGADATE__
+#else
+ __DATE__
+#endif
+ " " AUTHOR_NAME " $";
+#endif
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+static boolean createTagsForEntry(const char *const entryName);
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+extern void addTotals(const unsigned int files, const long unsigned int lines,
+ const long unsigned int bytes) {
+ Totals.files += files;
+ Totals.lines += lines;
+ Totals.bytes += bytes;
+}
+
+extern boolean isDestinationStdout(void) {
+ boolean toStdout = FALSE;
+
+ if (Option.xref || Option.filter ||
+ (Option.tagFileName != NULL &&
+ (strcmp(Option.tagFileName, "-") == 0
+#if defined(VMS)
+ || strcmp(Option.tagFileName, "sys$output") == 0
+#else
+ || strcmp(Option.tagFileName, "/dev/stdout") == 0
+#endif
+ )))
+ toStdout = TRUE;
+ return toStdout;
+}
+
+#if defined(HAVE_OPENDIR)
+static boolean recurseUsingOpendir(const char *const dirName) {
+ boolean resize = FALSE;
+ DIR *const dir = opendir(dirName);
+ if (dir == NULL)
+ error(WARNING | PERROR, "cannot recurse into directory \"%s\"", dirName);
+ else {
+ struct dirent *entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
+ vString *filePath;
+ if (strcmp(dirName, ".") == 0)
+ filePath = vStringNewInit(entry->d_name);
+ else
+ filePath = combinePathAndFile(dirName, entry->d_name);
+ resize |= createTagsForEntry(vStringValue(filePath));
+ vStringDelete(filePath);
+ }
+ }
+ closedir(dir);
+ }
+ return resize;
+}
+
+#elif defined(HAVE_FINDFIRST) || defined(HAVE__FINDFIRST)
+
+static boolean createTagsForWildcardEntry(const char *const pattern,
+ const size_t dirLength,
+ const char *const entryName) {
+ boolean resize = FALSE;
+ /* we must not recurse into the directories "." or ".." */
+ if (strcmp(entryName, ".") != 0 && strcmp(entryName, "..") != 0) {
+ vString *const filePath = vStringNew();
+ vStringNCopyS(filePath, pattern, dirLength);
+ vStringCatS(filePath, entryName);
+ resize = createTagsForEntry(vStringValue(filePath));
+ vStringDelete(filePath);
+ }
+ return resize;
+}
+
+static boolean createTagsForWildcardUsingFindfirst(const char *const pattern) {
+ boolean resize = FALSE;
+ const size_t dirLength = baseFilename(pattern) - pattern;
+#if defined(HAVE_FINDFIRST)
+ struct ffblk fileInfo;
+ int result = findfirst(pattern, &fileInfo, FA_DIREC);
+ while (result == 0) {
+ const char *const entry = (const char *)fileInfo.ff_name;
+ resize |= createTagsForWildcardEntry(pattern, dirLength, entry);
+ result = findnext(&fileInfo);
+ }
+#elif defined(HAVE__FINDFIRST)
+ struct _finddata_t fileInfo;
+ findfirst_t hFile = _findfirst(pattern, &fileInfo);
+ if (hFile != -1L) {
+ do {
+ const char *const entry = (const char *)fileInfo.name;
+ resize |= createTagsForWildcardEntry(pattern, dirLength, entry);
+ } while (_findnext(hFile, &fileInfo) == 0);
+ _findclose(hFile);
+ }
+#endif
+ return resize;
+}
+
+#elif defined(AMIGA)
+
+static boolean createTagsForAmigaWildcard(const char *const pattern) {
+ boolean resize = FALSE;
+ struct AnchorPath *const anchor =
+ (struct AnchorPath *)eMalloc((size_t)ANCHOR_SIZE);
+ LONG result;
+
+ memset(anchor, 0, (size_t)ANCHOR_SIZE);
+ anchor->ap_Strlen = ANCHOR_BUF_SIZE;
+ /* Allow '.' for current directory */
+#ifdef APF_DODOT
+ anchor->ap_Flags = APF_DODOT | APF_DOWILD;
+#else
+ anchor->ap_Flags = APF_DoDot | APF_DoWild;
+#endif
+ result = MatchFirst((UBYTE *)pattern, anchor);
+ while (result == 0) {
+ resize |= createTagsForEntry((char *)anchor->ap_Buf);
+ result = MatchNext(anchor);
+ }
+ MatchEnd(anchor);
+ eFree(anchor);
+ return resize;
+}
+#endif
+
+static boolean recurseIntoDirectory(const char *const dirName) {
+ boolean resize = FALSE;
+ if (isRecursiveLink(dirName))
+ verbose("ignoring \"%s\" (recursive link)\n", dirName);
+ else if (!Option.recurse)
+ verbose("ignoring \"%s\" (directory)\n", dirName);
+ else {
+ verbose("RECURSING into directory \"%s\"\n", dirName);
+#if defined(HAVE_OPENDIR)
+ resize = recurseUsingOpendir(dirName);
+#elif defined(HAVE_FINDFIRST) || defined(HAVE__FINDFIRST)
+ {
+ vString *const pattern = vStringNew();
+ vStringCopyS(pattern, dirName);
+ vStringPut(pattern, OUTPUT_PATH_SEPARATOR);
+ vStringCatS(pattern, "*.*");
+ resize = createTagsForWildcardUsingFindfirst(vStringValue(pattern));
+ vStringDelete(pattern);
+ }
+#elif defined(AMIGA)
+ {
+ vString *const pattern = vStringNew();
+ if (*dirName != '\0' && strcmp(dirName, ".") != 0) {
+ vStringCopyS(pattern, dirName);
+ if (dirName[strlen(dirName) - 1] != '/') vStringPut(pattern, '/');
+ }
+ vStringCatS(pattern, "#?");
+ resize = createTagsForAmigaWildcard(vStringValue(pattern));
+ vStringDelete(pattern);
+ }
+#endif
+ }
+ return resize;
+}
+
+static boolean createTagsForEntry(const char *const entryName) {
+ boolean resize = FALSE;
+ fileStatus *status = eStat(entryName);
+
+ Assert(entryName != NULL);
+ if (isExcludedFile(entryName))
+ verbose("excluding \"%s\"\n", entryName);
+ else if (status->isSymbolicLink && !Option.followLinks)
+ verbose("ignoring \"%s\" (symbolic link)\n", entryName);
+ else if (!status->exists)
+ error(WARNING | PERROR, "cannot open source file \"%s\"", entryName);
+ else if (status->isDirectory)
+ resize = recurseIntoDirectory(entryName);
+ else if (!status->isNormalFile)
+ verbose("ignoring \"%s\" (special file)\n", entryName);
+ else
+ resize = parseFile(entryName);
+
+ eStatFree(status);
+ return resize;
+}
+
+#ifdef MANUAL_GLOBBING
+
+static boolean createTagsForWildcardArg(const char *const arg) {
+ boolean resize = FALSE;
+ vString *const pattern = vStringNewInit(arg);
+ char *patternS = vStringValue(pattern);
+
+#if defined(HAVE_FINDFIRST) || defined(HAVE__FINDFIRST)
+ /* We must transform the "." and ".." forms into something that can
+ * be expanded by the findfirst/_findfirst functions.
+ */
+ if (Option.recurse &&
+ (strcmp(patternS, ".") == 0 || strcmp(patternS, "..") == 0)) {
+ vStringPut(pattern, OUTPUT_PATH_SEPARATOR);
+ vStringCatS(pattern, "*.*");
+ }
+ resize |= createTagsForWildcardUsingFindfirst(patternS);
+#endif
+ vStringDelete(pattern);
+ return resize;
+}
+
+#endif
+
+static boolean createTagsForArgs(cookedArgs *const args) {
+ boolean resize = FALSE;
+
+ /* Generate tags for each argument on the command line.
+ */
+ while (!cArgOff(args)) {
+ const char *const arg = cArgItem(args);
+
+#ifdef MANUAL_GLOBBING
+ resize |= createTagsForWildcardArg(arg);
+#else
+ resize |= createTagsForEntry(arg);
+#endif
+ cArgForth(args);
+ parseOptions(args);
+ }
+ return resize;
+}
+
+/* Read from an opened file a list of file names for which to generate tags.
+ */
+static boolean createTagsFromFileInput(FILE *const fp, const boolean filter) {
+ boolean resize = FALSE;
+ if (fp != NULL) {
+ cookedArgs *args = cArgNewFromLineFile(fp);
+ parseOptions(args);
+ while (!cArgOff(args)) {
+ resize |= createTagsForEntry(cArgItem(args));
+ if (filter) {
+ if (Option.filterTerminator != NULL)
+ fputs(Option.filterTerminator, stdout);
+ fflush(stdout);
+ }
+ cArgForth(args);
+ parseOptions(args);
+ }
+ cArgDelete(args);
+ }
+ return resize;
+}
+
+/* Read from a named file a list of file names for which to generate tags.
+ */
+static boolean createTagsFromListFile(const char *const fileName) {
+ boolean resize;
+ Assert(fileName != NULL);
+ if (strcmp(fileName, "-") == 0)
+ resize = createTagsFromFileInput(stdin, FALSE);
+ else {
+ FILE *const fp = fopen(fileName, "r");
+ if (fp == NULL)
+ error(FATAL | PERROR, "cannot open list file \"%s\"", fileName);
+ resize = createTagsFromFileInput(fp, FALSE);
+ fclose(fp);
+ }
+ return resize;
+}
+
+#if defined(HAVE_CLOCK)
+#define CLOCK_AVAILABLE
+#ifndef CLOCKS_PER_SEC
+#define CLOCKS_PER_SEC 1000000
+#endif
+#elif defined(HAVE_TIMES)
+#define CLOCK_AVAILABLE
+#define CLOCKS_PER_SEC 60
+static clock_t clock(void) {
+ struct tms buf;
+
+ times(&buf);
+ return (buf.tms_utime + buf.tms_stime);
+}
+#else
+#define clock() (clock_t)0
+#endif
+
+static void printTotals(const clock_t *const timeStamps) {
+ const unsigned long totalTags = TagFile.numTags.added + TagFile.numTags.prev;
+
+ fprintf(errout, "%ld file%s, %ld line%s (%ld kB) scanned", Totals.files,
+ plural(Totals.files), Totals.lines, plural(Totals.lines),
+ Totals.bytes / 1024L);
+#ifdef CLOCK_AVAILABLE
+ {
+ const double interval =
+ ((double)(timeStamps[1] - timeStamps[0])) / CLOCKS_PER_SEC;
+
+ fprintf(errout, " in %.01f seconds", interval);
+ if (interval != (double)0.0)
+ fprintf(errout, " (%lu kB/s)",
+ (unsigned long)(Totals.bytes / interval) / 1024L);
+ }
+#endif
+ fputc('\n', errout);
+
+ fprintf(errout, "%lu tag%s added to tag file", TagFile.numTags.added,
+ plural(TagFile.numTags.added));
+ if (Option.append) fprintf(errout, " (now %lu tags)", totalTags);
+ fputc('\n', errout);
+
+ if (totalTags > 0 && Option.sorted != SO_UNSORTED) {
+ fprintf(errout, "%lu tag%s sorted", totalTags, plural(totalTags));
+#ifdef CLOCK_AVAILABLE
+ fprintf(errout, " in %.02f seconds",
+ ((double)(timeStamps[2] - timeStamps[1])) / CLOCKS_PER_SEC);
+#endif
+ fputc('\n', errout);
+ }
+
+#ifdef DEBUG
+ fprintf(errout, "longest tag line = %lu\n", (unsigned long)TagFile.max.line);
+#endif
+}
+
+static boolean etagsInclude(void) {
+ return (boolean)(Option.etags && Option.etagsInclude != NULL);
+}
+
+static void makeTags(cookedArgs *args) {
+ clock_t timeStamps[3];
+ boolean resize = FALSE;
+ boolean files =
+ (boolean)(!cArgOff(args) || Option.fileList != NULL || Option.filter);
+
+ if (!files) {
+ if (filesRequired())
+ error(FATAL, "No files specified. Try \"%s --help\".",
+ getExecutableName());
+ else if (!Option.recurse && !etagsInclude())
+ return;
+ }
+
+#define timeStamp(n) \
+ timeStamps[(n)] = (Option.printTotals ? clock() : (clock_t)0)
+ if (!Option.filter) openTagFile();
+
+ timeStamp(0);
+
+ if (!cArgOff(args)) {
+ verbose("Reading command line arguments\n");
+ resize = createTagsForArgs(args);
+ }
+ if (Option.fileList != NULL) {
+ verbose("Reading list file\n");
+ resize = (boolean)(createTagsFromListFile(Option.fileList) || resize);
+ }
+ if (Option.filter) {
+ verbose("Reading filter input\n");
+ resize = (boolean)(createTagsFromFileInput(stdin, TRUE) || resize);
+ }
+ if (!files && Option.recurse) resize = recurseIntoDirectory(".");
+
+ timeStamp(1);
+
+ if (!Option.filter) closeTagFile(resize);
+
+ timeStamp(2);
+
+ if (Option.printTotals) printTotals(timeStamps);
+#undef timeStamp
+}
+
+/*
+ * Start up code
+ */
+
+extern int main(int __unused__ argc, char **argv) {
+ cookedArgs *args;
+#ifdef VMS
+ extern int getredirection(int *ac, char ***av);
+
+ /* do wildcard expansion and I/O redirection */
+ getredirection(&argc, &argv);
+#endif
+
+#ifdef AMIGA
+ /* This program doesn't work when started from the Workbench */
+ if (argc == 0) exit(1);
+#endif
+
+#ifdef __EMX__
+ _wildcard(&argc, &argv); /* expand wildcards in argument list */
+#endif
+
+#if defined(macintosh) && BUILD_MPW_TOOL == 0
+ argc = ccommand(&argv);
+#endif
+
+ setCurrentDirectory();
+ setExecutableName(*argv++);
+ checkRegex();
+
+ args = cArgNewFromArgv(argv);
+ previewFirstOption(args);
+ testEtagsInvocation();
+ initializeParsing();
+ initOptions();
+ readOptionConfiguration();
+ verbose("Reading initial options from command line\n");
+ parseOptions(args);
+ checkOptions();
+ makeTags(args);
+
+ /* Clean up.
+ */
+ cArgDelete(args);
+ freeKeywordTable();
+ freeRoutineResources();
+ freeSourceFileResources();
+ freeTagFileResources();
+ freeOptionResources();
+ freeParserResources();
+ freeRegexResources();
+
+ exit(0);
+ return 0;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/main.h b/third_party/ctags/main.h
new file mode 100644
index 000000000..77032e2be
--- /dev/null
+++ b/third_party/ctags/main.h
@@ -0,0 +1,15 @@
+#ifndef _MAIN_H
+#define _MAIN_H
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/vstring.h"
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+extern void addTotals(const unsigned int files, const long unsigned int lines,
+ const long unsigned int bytes);
+extern boolean isDestinationStdout(void);
+extern int main(int argc, char **argv);
+
+#endif /* _MAIN_H */
diff --git a/third_party/ctags/make.c b/third_party/ctags/make.c
new file mode 100644
index 000000000..ab658d056
--- /dev/null
+++ b/third_party/ctags/make.c
@@ -0,0 +1,175 @@
+/*
+ * $Id: make.c 751 2010-02-27 17:41:57Z elliotth $
+ *
+ * Copyright (c) 2000-2005, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for makefiles.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum { K_MACRO } shKind;
+
+static kindOption MakeKinds[] = {{TRUE, 'm', "macro", "macros"}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static int nextChar(void) {
+ int c = fileGetc();
+ if (c == '\\') {
+ c = fileGetc();
+ if (c == '\n') c = fileGetc();
+ }
+ return c;
+}
+
+static void skipLine(void) {
+ int c;
+ do
+ c = nextChar();
+ while (c != EOF && c != '\n');
+ if (c == '\n') fileUngetc(c);
+}
+
+static int skipToNonWhite(void) {
+ int c;
+ do
+ c = nextChar();
+ while (c != '\n' && isspace(c));
+ return c;
+}
+
+static boolean isIdentifier(int c) {
+ return (boolean)(c != '\0' && (isalnum(c) || strchr(".-_", c) != NULL));
+}
+
+static void readIdentifier(const int first, vString *const id) {
+ int c = first;
+ vStringClear(id);
+ while (isIdentifier(c)) {
+ vStringPut(id, c);
+ c = nextChar();
+ }
+ fileUngetc(c);
+ vStringTerminate(id);
+}
+
+static void skipToMatch(const char *const pair) {
+ const int begin = pair[0], end = pair[1];
+ const unsigned long inputLineNumber = getInputLineNumber();
+ int matchLevel = 1;
+ int c = '\0';
+
+ while (matchLevel > 0) {
+ c = nextChar();
+ if (c == begin)
+ ++matchLevel;
+ else if (c == end)
+ --matchLevel;
+ else if (c == '\n' || c == EOF)
+ break;
+ }
+ if (c == EOF)
+ verbose("%s: failed to find match for '%c' at line %lu\n",
+ getInputFileName(), begin, inputLineNumber);
+}
+
+static void findMakeTags(void) {
+ vString *name = vStringNew();
+ boolean newline = TRUE;
+ boolean in_define = FALSE;
+ boolean in_rule = FALSE;
+ boolean variable_possible = TRUE;
+ int c;
+
+ while ((c = nextChar()) != EOF) {
+ if (newline) {
+ if (in_rule) {
+ if (c == '\t') {
+ skipLine(); /* skip rule */
+ continue;
+ } else
+ in_rule = FALSE;
+ }
+ variable_possible = (boolean)(!in_rule);
+ newline = FALSE;
+ }
+ if (c == '\n')
+ newline = TRUE;
+ else if (isspace(c))
+ continue;
+ else if (c == '#')
+ skipLine();
+ else if (c == '(')
+ skipToMatch("()");
+ else if (c == '{')
+ skipToMatch("{}");
+ else if (c == ':') {
+ variable_possible = TRUE;
+ in_rule = TRUE;
+ } else if (variable_possible && isIdentifier(c)) {
+ readIdentifier(c, name);
+ if (strcmp(vStringValue(name), "endef") == 0)
+ in_define = FALSE;
+ else if (in_define)
+ skipLine();
+ else if (strcmp(vStringValue(name), "define") == 0 && isIdentifier(c)) {
+ in_define = TRUE;
+ c = skipToNonWhite();
+ readIdentifier(c, name);
+ makeSimpleTag(name, MakeKinds, K_MACRO);
+ skipLine();
+ } else {
+ if (strcmp(vStringValue(name), "export") == 0 && isIdentifier(c)) {
+ c = skipToNonWhite();
+ readIdentifier(c, name);
+ }
+ c = skipToNonWhite();
+ if (strchr(":?+", c) != NULL) {
+ boolean append = (boolean)(c == '+');
+ if (c == ':') in_rule = TRUE;
+ c = nextChar();
+ if (c != '=')
+ fileUngetc(c);
+ else if (append) {
+ skipLine();
+ continue;
+ }
+ }
+ if (c == '=') {
+ makeSimpleTag(name, MakeKinds, K_MACRO);
+ in_rule = FALSE;
+ skipLine();
+ }
+ }
+ } else
+ variable_possible = FALSE;
+ }
+ vStringDelete(name);
+}
+
+extern parserDefinition *MakefileParser(void) {
+ static const char *const patterns[] = {"[Mm]akefile", "GNUmakefile", NULL};
+ static const char *const extensions[] = {"mak", "mk", NULL};
+ parserDefinition *const def = parserNew("Make");
+ def->kinds = MakeKinds;
+ def->kindCount = KIND_COUNT(MakeKinds);
+ def->patterns = patterns;
+ def->extensions = extensions;
+ def->parser = findMakeTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/matlab.c b/third_party/ctags/matlab.c
new file mode 100644
index 000000000..fb1da715a
--- /dev/null
+++ b/third_party/ctags/matlab.c
@@ -0,0 +1,42 @@
+/*
+ * $Id$
+ *
+ * Copyright (c) 2008, David Fishburn
+ *
+ * This source code is released for free distribution under the terms
+ * of the GNU General Public License.
+ *
+ * This module contains functions for generating tags for MATLAB
+ * language files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parse.h"
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void installMatLabRegex(const langType language) {
+ /* function [x,y,z] = asdf */
+ addTagRegex(language, "^function[ \t]*\\[.*\\][ \t]*=[ \t]*([a-zA-Z0-9_]+)",
+ "\\1", "f,function", NULL);
+ /* function x = asdf */
+ addTagRegex(language,
+ "^function[ \t]*[a-zA-Z0-9_]+[ \t]*=[ \t]*([a-zA-Z0-9_]+)", "\\1",
+ "f,function", NULL);
+ /* function asdf */
+ addTagRegex(language, "^function[ \t]*([a-zA-Z0-9_]+)[^=]*$", "\\1",
+ "f,function", NULL);
+}
+
+extern parserDefinition* MatLabParser() {
+ static const char* const extensions[] = {"m", NULL};
+ parserDefinition* const def = parserNew("MatLab");
+ def->extensions = extensions;
+ def->initialize = installMatLabRegex;
+ def->regex = TRUE;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/objc.c b/third_party/ctags/objc.c
new file mode 100644
index 000000000..00dc9711f
--- /dev/null
+++ b/third_party/ctags/objc.c
@@ -0,0 +1,1027 @@
+/*
+ * Copyright (c) 2010, Vincent Berthoux
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for Objective C
+ * language files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/* To get rid of unused parameter warning in
+ * -Wextra */
+#ifdef UNUSED
+#elif defined(__GNUC__)
+#define UNUSED(x) UNUSED_##x __attribute__((unused))
+#elif defined(__LCLINT__)
+#define UNUSED(x) /*@unused@*/ x
+#else
+#define UNUSED(x) x
+#endif
+
+typedef enum {
+ K_INTERFACE,
+ K_IMPLEMENTATION,
+ K_PROTOCOL,
+ K_METHOD,
+ K_CLASSMETHOD,
+ K_VAR,
+ K_FIELD,
+ K_FUNCTION,
+ K_PROPERTY,
+ K_TYPEDEF,
+ K_STRUCT,
+ K_ENUM,
+ K_MACRO
+} objcKind;
+
+static kindOption ObjcKinds[] = {
+ {TRUE, 'i', "interface", "class interface"},
+ {TRUE, 'I', "implementation", "class implementation"},
+ {TRUE, 'p', "protocol", "Protocol"},
+ {TRUE, 'm', "method", "Object's method"},
+ {TRUE, 'c', "class", "Class' method"},
+ {TRUE, 'v', "var", "Global variable"},
+ {TRUE, 'F', "field", "Object field"},
+ {TRUE, 'f', "function", "A function"},
+ {TRUE, 'p', "property", "A property"},
+ {TRUE, 't', "typedef", "A type alias"},
+ {TRUE, 's', "struct", "A type structure"},
+ {TRUE, 'e', "enum", "An enumeration"},
+ {TRUE, 'M', "macro", "A preprocessor macro"},
+};
+
+typedef enum {
+ ObjcTYPEDEF,
+ ObjcSTRUCT,
+ ObjcENUM,
+ ObjcIMPLEMENTATION,
+ ObjcINTERFACE,
+ ObjcPROTOCOL,
+ ObjcENCODE,
+ ObjcSYNCHRONIZED,
+ ObjcSELECTOR,
+ ObjcPROPERTY,
+ ObjcEND,
+ ObjcDEFS,
+ ObjcCLASS,
+ ObjcPRIVATE,
+ ObjcPACKAGE,
+ ObjcPUBLIC,
+ ObjcPROTECTED,
+ ObjcSYNTHESIZE,
+ ObjcDYNAMIC,
+ ObjcOPTIONAL,
+ ObjcREQUIRED,
+ ObjcSTRING,
+ ObjcIDENTIFIER,
+
+ Tok_COMA, /* ',' */
+ Tok_PLUS, /* '+' */
+ Tok_MINUS, /* '-' */
+ Tok_PARL, /* '(' */
+ Tok_PARR, /* ')' */
+ Tok_CurlL, /* '{' */
+ Tok_CurlR, /* '}' */
+ Tok_SQUAREL, /* '[' */
+ Tok_SQUARER, /* ']' */
+ Tok_semi, /* ';' */
+ Tok_dpoint, /* ':' */
+ Tok_Sharp, /* '#' */
+ Tok_Backslash, /* '\\' */
+ Tok_EOL, /* '\r''\n' */
+ Tok_any,
+
+ Tok_EOF /* END of file */
+} objcKeyword;
+
+typedef objcKeyword objcToken;
+
+typedef struct sOBjcKeywordDesc {
+ const char *name;
+ objcKeyword id;
+} objcKeywordDesc;
+
+static const objcKeywordDesc objcKeywordTable[] = {
+ {"typedef", ObjcTYPEDEF},
+ {"struct", ObjcSTRUCT},
+ {"enum", ObjcENUM},
+ {"@implementation", ObjcIMPLEMENTATION},
+ {"@interface", ObjcINTERFACE},
+ {"@protocol", ObjcPROTOCOL},
+ {"@encode", ObjcENCODE},
+ {"@property", ObjcPROPERTY},
+ {"@synchronized", ObjcSYNCHRONIZED},
+ {"@selector", ObjcSELECTOR},
+ {"@end", ObjcEND},
+ {"@defs", ObjcDEFS},
+ {"@class", ObjcCLASS},
+ {"@private", ObjcPRIVATE},
+ {"@package", ObjcPACKAGE},
+ {"@public", ObjcPUBLIC},
+ {"@protected", ObjcPROTECTED},
+ {"@synthesize", ObjcSYNTHESIZE},
+ {"@dynamic", ObjcDYNAMIC},
+ {"@optional", ObjcOPTIONAL},
+ {"@required", ObjcREQUIRED},
+};
+
+static langType Lang_ObjectiveC;
+
+/*//////////////////////////////////////////////////////////////////
+//// lexingInit */
+typedef struct _lexingState {
+ vString *name; /* current parsed identifier/operator */
+ const unsigned char *cp; /* position in stream */
+} lexingState;
+
+static void initKeywordHash(void) {
+ const size_t count = sizeof(objcKeywordTable) / sizeof(objcKeywordDesc);
+ size_t i;
+
+ for (i = 0; i < count; ++i) {
+ addKeyword(objcKeywordTable[i].name, Lang_ObjectiveC,
+ (int)objcKeywordTable[i].id);
+ }
+}
+
+/*//////////////////////////////////////////////////////////////////////
+//// Lexing */
+static boolean isNum(char c) {
+ return c >= '0' && c <= '9';
+}
+
+static boolean isLowerAlpha(char c) {
+ return c >= 'a' && c <= 'z';
+}
+
+static boolean isUpperAlpha(char c) {
+ return c >= 'A' && c <= 'Z';
+}
+
+static boolean isAlpha(char c) {
+ return isLowerAlpha(c) || isUpperAlpha(c);
+}
+
+static boolean isIdent(char c) {
+ return isNum(c) || isAlpha(c) || c == '_';
+}
+
+static boolean isSpace(char c) {
+ return c == ' ' || c == '\t';
+}
+
+/* return true if it end with an end of line */
+static void eatWhiteSpace(lexingState *st) {
+ const unsigned char *cp = st->cp;
+ while (isSpace(*cp)) cp++;
+
+ st->cp = cp;
+}
+
+static void eatString(lexingState *st) {
+ boolean lastIsBackSlash = FALSE;
+ boolean unfinished = TRUE;
+ const unsigned char *c = st->cp + 1;
+
+ while (unfinished) {
+ /* end of line should never happen.
+ * we tolerate it */
+ if (c == NULL || c[0] == '\0')
+ break;
+ else if (*c == '"' && !lastIsBackSlash)
+ unfinished = FALSE;
+ else
+ lastIsBackSlash = *c == '\\';
+
+ c++;
+ }
+
+ st->cp = c;
+}
+
+static void eatComment(lexingState *st) {
+ boolean unfinished = TRUE;
+ boolean lastIsStar = FALSE;
+ const unsigned char *c = st->cp + 2;
+
+ while (unfinished) {
+ /* we've reached the end of the line..
+ * so we have to reload a line... */
+ if (c == NULL || *c == '\0') {
+ st->cp = fileReadLine();
+ /* WOOPS... no more input...
+ * we return, next lexing read
+ * will be null and ok */
+ if (st->cp == NULL) return;
+ c = st->cp;
+ }
+ /* we've reached the end of the comment */
+ else if (*c == '/' && lastIsStar)
+ unfinished = FALSE;
+ else {
+ lastIsStar = '*' == *c;
+ c++;
+ }
+ }
+
+ st->cp = c;
+}
+
+static void readIdentifier(lexingState *st) {
+ const unsigned char *p;
+ vStringClear(st->name);
+
+ /* first char is a simple letter */
+ if (isAlpha(*st->cp) || *st->cp == '_') vStringPut(st->name, (int)*st->cp);
+
+ /* Go till you get identifier chars */
+ for (p = st->cp + 1; isIdent(*p); p++) vStringPut(st->name, (int)*p);
+
+ st->cp = p;
+
+ vStringTerminate(st->name);
+}
+
+/* read the @something directives */
+static void readIdentifierObjcDirective(lexingState *st) {
+ const unsigned char *p;
+ vStringClear(st->name);
+
+ /* first char is a simple letter */
+ if (*st->cp == '@') vStringPut(st->name, (int)*st->cp);
+
+ /* Go till you get identifier chars */
+ for (p = st->cp + 1; isIdent(*p); p++) vStringPut(st->name, (int)*p);
+
+ st->cp = p;
+
+ vStringTerminate(st->name);
+}
+
+/* The lexer is in charge of reading the file.
+ * Some of sub-lexer (like eatComment) also read file.
+ * lexing is finished when the lexer return Tok_EOF */
+static objcKeyword lex(lexingState *st) {
+ int retType;
+
+ /* handling data input here */
+ while (st->cp == NULL || st->cp[0] == '\0') {
+ st->cp = fileReadLine();
+ if (st->cp == NULL) return Tok_EOF;
+
+ return Tok_EOL;
+ }
+
+ if (isAlpha(*st->cp)) {
+ readIdentifier(st);
+ retType = lookupKeyword(vStringValue(st->name), Lang_ObjectiveC);
+
+ if (retType == -1) /* If it's not a keyword */
+ {
+ return ObjcIDENTIFIER;
+ } else {
+ return retType;
+ }
+ } else if (*st->cp == '@') {
+ readIdentifierObjcDirective(st);
+ retType = lookupKeyword(vStringValue(st->name), Lang_ObjectiveC);
+
+ if (retType == -1) /* If it's not a keyword */
+ {
+ return Tok_any;
+ } else {
+ return retType;
+ }
+ } else if (isSpace(*st->cp)) {
+ eatWhiteSpace(st);
+ return lex(st);
+ } else
+ switch (*st->cp) {
+ case '(':
+ st->cp++;
+ return Tok_PARL;
+
+ case '\\':
+ st->cp++;
+ return Tok_Backslash;
+
+ case '#':
+ st->cp++;
+ return Tok_Sharp;
+
+ case '/':
+ if (st->cp[1] == '*') /* ergl, a comment */
+ {
+ eatComment(st);
+ return lex(st);
+ } else if (st->cp[1] == '/') {
+ st->cp = NULL;
+ return lex(st);
+ } else {
+ st->cp++;
+ return Tok_any;
+ }
+ break;
+
+ case ')':
+ st->cp++;
+ return Tok_PARR;
+ case '{':
+ st->cp++;
+ return Tok_CurlL;
+ case '}':
+ st->cp++;
+ return Tok_CurlR;
+ case '[':
+ st->cp++;
+ return Tok_SQUAREL;
+ case ']':
+ st->cp++;
+ return Tok_SQUARER;
+ case ',':
+ st->cp++;
+ return Tok_COMA;
+ case ';':
+ st->cp++;
+ return Tok_semi;
+ case ':':
+ st->cp++;
+ return Tok_dpoint;
+ case '"':
+ eatString(st);
+ return Tok_any;
+ case '+':
+ st->cp++;
+ return Tok_PLUS;
+ case '-':
+ st->cp++;
+ return Tok_MINUS;
+
+ default:
+ st->cp++;
+ break;
+ }
+
+ /* default return if nothing is recognized,
+ * shouldn't happen, but at least, it will
+ * be handled without destroying the parsing. */
+ return Tok_any;
+}
+
+/*//////////////////////////////////////////////////////////////////////
+//// Parsing */
+typedef void (*parseNext)(vString *const ident, objcToken what);
+
+/********** Helpers */
+/* This variable hold the 'parser' which is going to
+ * handle the next token */
+static parseNext toDoNext;
+
+/* Special variable used by parser eater to
+ * determine which action to put after their
+ * job is finished. */
+static parseNext comeAfter;
+
+/* Used by some parsers detecting certain token
+ * to revert to previous parser. */
+static parseNext fallback;
+
+/********** Grammar */
+static void globalScope(vString *const ident, objcToken what);
+static void parseMethods(vString *const ident, objcToken what);
+static void parseImplemMethods(vString *const ident, objcToken what);
+static vString *tempName = NULL;
+static vString *parentName = NULL;
+static objcKind parentType = K_INTERFACE;
+
+/* used to prepare tag for OCaml, just in case their is a need to
+ * add additional information to the tag. */
+static void prepareTag(tagEntryInfo *tag, vString const *name, objcKind kind) {
+ initTagEntry(tag, vStringValue(name));
+ tag->kindName = ObjcKinds[kind].name;
+ tag->kind = ObjcKinds[kind].letter;
+
+ if (parentName != NULL) {
+ tag->extensionFields.scope[0] = ObjcKinds[parentType].name;
+ tag->extensionFields.scope[1] = vStringValue(parentName);
+ }
+}
+
+void pushEnclosingContext(const vString *parent, objcKind type) {
+ vStringCopy(parentName, parent);
+ parentType = type;
+}
+
+void popEnclosingContext() {
+ vStringClear(parentName);
+}
+
+/* Used to centralise tag creation, and be able to add
+ * more information to it in the future */
+static void addTag(vString *const ident, int kind) {
+ tagEntryInfo toCreate;
+ prepareTag(&toCreate, ident, kind);
+ makeTagEntry(&toCreate);
+}
+
+static objcToken waitedToken, fallBackToken;
+
+/* Ignore everything till waitedToken and jump to comeAfter.
+ * If the "end" keyword is encountered break, doesn't remember
+ * why though. */
+static void tillToken(vString *const UNUSED(ident), objcToken what) {
+ if (what == waitedToken) toDoNext = comeAfter;
+}
+
+static void tillTokenOrFallBack(vString *const UNUSED(ident), objcToken what) {
+ if (what == waitedToken)
+ toDoNext = comeAfter;
+ else if (what == fallBackToken) {
+ toDoNext = fallback;
+ }
+}
+
+static void ignoreBalanced(vString *const UNUSED(ident), objcToken what) {
+ static int count = 0;
+
+ switch (what) {
+ case Tok_PARL:
+ case Tok_CurlL:
+ case Tok_SQUAREL:
+ count++;
+ break;
+
+ case Tok_PARR:
+ case Tok_CurlR:
+ case Tok_SQUARER:
+ count--;
+ break;
+
+ default:
+ /* don't care */
+ break;
+ }
+
+ if (count == 0) toDoNext = comeAfter;
+}
+
+static void parseFields(vString *const ident, objcToken what) {
+ switch (what) {
+ case Tok_CurlR:
+ toDoNext = &parseMethods;
+ break;
+
+ case Tok_SQUAREL:
+ case Tok_PARL:
+ toDoNext = &ignoreBalanced;
+ comeAfter = &parseFields;
+ break;
+
+ // we got an identifier, keep track
+ // of it
+ case ObjcIDENTIFIER:
+ vStringCopy(tempName, ident);
+ break;
+
+ // our last kept identifier must be our
+ // variable name =)
+ case Tok_semi:
+ addTag(tempName, K_FIELD);
+ vStringClear(tempName);
+ break;
+
+ default:
+ /* NOTHING */
+ break;
+ }
+}
+
+objcKind methodKind;
+
+static vString *fullMethodName;
+static vString *prevIdent;
+
+static void parseMethodsName(vString *const ident, objcToken what) {
+ switch (what) {
+ case Tok_PARL:
+ toDoNext = &tillToken;
+ comeAfter = &parseMethodsName;
+ waitedToken = Tok_PARR;
+ break;
+
+ case Tok_dpoint:
+ vStringCat(fullMethodName, prevIdent);
+ vStringCatS(fullMethodName, ":");
+ vStringClear(prevIdent);
+ break;
+
+ case ObjcIDENTIFIER:
+ vStringCopy(prevIdent, ident);
+ break;
+
+ case Tok_CurlL:
+ case Tok_semi:
+ // method name is not simple
+ if (vStringLength(fullMethodName) != '\0') {
+ addTag(fullMethodName, methodKind);
+ vStringClear(fullMethodName);
+ } else
+ addTag(prevIdent, methodKind);
+
+ toDoNext = &parseMethods;
+ parseImplemMethods(ident, what);
+ vStringClear(prevIdent);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void parseMethodsImplemName(vString *const ident, objcToken what) {
+ switch (what) {
+ case Tok_PARL:
+ toDoNext = &tillToken;
+ comeAfter = &parseMethodsImplemName;
+ waitedToken = Tok_PARR;
+ break;
+
+ case Tok_dpoint:
+ vStringCat(fullMethodName, prevIdent);
+ vStringCatS(fullMethodName, ":");
+ vStringClear(prevIdent);
+ break;
+
+ case ObjcIDENTIFIER:
+ vStringCopy(prevIdent, ident);
+ break;
+
+ case Tok_CurlL:
+ case Tok_semi:
+ // method name is not simple
+ if (vStringLength(fullMethodName) != '\0') {
+ addTag(fullMethodName, methodKind);
+ vStringClear(fullMethodName);
+ } else
+ addTag(prevIdent, methodKind);
+
+ toDoNext = &parseImplemMethods;
+ parseImplemMethods(ident, what);
+ vStringClear(prevIdent);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void parseImplemMethods(vString *const ident, objcToken what) {
+ switch (what) {
+ case Tok_PLUS: /* + */
+ toDoNext = &parseMethodsImplemName;
+ methodKind = K_CLASSMETHOD;
+ break;
+
+ case Tok_MINUS: /* - */
+ toDoNext = &parseMethodsImplemName;
+ methodKind = K_METHOD;
+ break;
+
+ case ObjcEND: /* @end */
+ popEnclosingContext();
+ toDoNext = &globalScope;
+ break;
+
+ case Tok_CurlL: /* { */
+ toDoNext = &ignoreBalanced;
+ ignoreBalanced(ident, what);
+ comeAfter = &parseImplemMethods;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void parseProperty(vString *const ident, objcToken what) {
+ switch (what) {
+ case Tok_PARL:
+ toDoNext = &tillToken;
+ comeAfter = &parseProperty;
+ waitedToken = Tok_PARR;
+ break;
+
+ // we got an identifier, keep track
+ // of it
+ case ObjcIDENTIFIER:
+ vStringCopy(tempName, ident);
+ break;
+
+ // our last kept identifier must be our
+ // variable name =)
+ case Tok_semi:
+ addTag(tempName, K_PROPERTY);
+ vStringClear(tempName);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void parseMethods(vString *const UNUSED(ident), objcToken what) {
+ switch (what) {
+ case Tok_PLUS: /* + */
+ toDoNext = &parseMethodsName;
+ methodKind = K_CLASSMETHOD;
+ break;
+
+ case Tok_MINUS: /* - */
+ toDoNext = &parseMethodsName;
+ methodKind = K_METHOD;
+ break;
+
+ case ObjcPROPERTY:
+ toDoNext = &parseProperty;
+ break;
+
+ case ObjcEND: /* @end */
+ popEnclosingContext();
+ toDoNext = &globalScope;
+ break;
+
+ case Tok_CurlL: /* { */
+ toDoNext = &parseFields;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void parseProtocol(vString *const ident, objcToken what) {
+ if (what == ObjcIDENTIFIER) {
+ pushEnclosingContext(ident, K_PROTOCOL);
+ addTag(ident, K_PROTOCOL);
+ }
+ toDoNext = &parseMethods;
+}
+
+static void parseImplementation(vString *const ident, objcToken what) {
+ if (what == ObjcIDENTIFIER) {
+ addTag(ident, K_IMPLEMENTATION);
+ pushEnclosingContext(ident, K_IMPLEMENTATION);
+ }
+ toDoNext = &parseImplemMethods;
+}
+
+static void parseInterface(vString *const ident, objcToken what) {
+ if (what == ObjcIDENTIFIER) {
+ addTag(ident, K_INTERFACE);
+ pushEnclosingContext(ident, K_INTERFACE);
+ }
+
+ toDoNext = &parseMethods;
+}
+
+static void parseStructMembers(vString *const ident, objcToken what) {
+ static parseNext prev = NULL;
+
+ if (prev != NULL) {
+ comeAfter = prev;
+ prev = NULL;
+ }
+
+ switch (what) {
+ case ObjcIDENTIFIER:
+ vStringCopy(tempName, ident);
+ break;
+
+ case Tok_semi: /* ';' */
+ addTag(tempName, K_FIELD);
+ vStringClear(tempName);
+ break;
+
+ // some types are complex, the only one
+ // we will loose is the function type.
+ case Tok_CurlL: /* '{' */
+ case Tok_PARL: /* '(' */
+ case Tok_SQUAREL: /* '[' */
+ toDoNext = &ignoreBalanced;
+ prev = comeAfter;
+ comeAfter = &parseStructMembers;
+ ignoreBalanced(ident, what);
+ break;
+
+ case Tok_CurlR:
+ toDoNext = comeAfter;
+ break;
+
+ default:
+ /* don't care */
+ break;
+ }
+}
+
+/* Called just after the struct keyword */
+static void parseStruct(vString *const ident, objcToken what) {
+ static boolean gotName = FALSE;
+
+ switch (what) {
+ case ObjcIDENTIFIER:
+ if (!gotName) {
+ addTag(ident, K_STRUCT);
+ pushEnclosingContext(ident, K_STRUCT);
+ gotName = TRUE;
+ } else {
+ gotName = FALSE;
+ popEnclosingContext();
+ toDoNext = comeAfter;
+ comeAfter(ident, what);
+ }
+ break;
+
+ case Tok_CurlL:
+ toDoNext = &parseStructMembers;
+ break;
+
+ /* maybe it was just a forward declaration
+ * in which case, we pop the context */
+ case Tok_semi:
+ if (gotName) popEnclosingContext();
+
+ toDoNext = comeAfter;
+ comeAfter(ident, what);
+ break;
+
+ default:
+ /* we don't care */
+ break;
+ }
+}
+
+/* Parse enumeration members, ignoring potential initialization */
+static void parseEnumFields(vString *const ident, objcToken what) {
+ static parseNext prev = NULL;
+
+ if (prev != NULL) {
+ comeAfter = prev;
+ prev = NULL;
+ }
+
+ switch (what) {
+ case ObjcIDENTIFIER:
+ addTag(ident, K_ENUM);
+ prev = comeAfter;
+ waitedToken = Tok_COMA;
+ /* last item might not have a coma */
+ fallBackToken = Tok_CurlR;
+ fallback = comeAfter;
+ comeAfter = parseEnumFields;
+ toDoNext = &tillTokenOrFallBack;
+ break;
+
+ case Tok_CurlR:
+ toDoNext = comeAfter;
+ popEnclosingContext();
+ break;
+
+ default:
+ /* don't care */
+ break;
+ }
+}
+
+/* parse enum ... { ... */
+static void parseEnum(vString *const ident, objcToken what) {
+ static boolean named = FALSE;
+
+ switch (what) {
+ case ObjcIDENTIFIER:
+ if (!named) {
+ addTag(ident, K_ENUM);
+ pushEnclosingContext(ident, K_ENUM);
+ named = TRUE;
+ } else {
+ named = FALSE;
+ popEnclosingContext();
+ toDoNext = comeAfter;
+ comeAfter(ident, what);
+ }
+ break;
+
+ case Tok_CurlL: /* '{' */
+ toDoNext = &parseEnumFields;
+ named = FALSE;
+ break;
+
+ case Tok_semi: /* ';' */
+ if (named) popEnclosingContext();
+ toDoNext = comeAfter;
+ comeAfter(ident, what);
+ break;
+
+ default:
+ /* don't care */
+ break;
+ }
+}
+
+/* Parse something like
+ * typedef .... ident ;
+ * ignoring the defined type but in the case of struct,
+ * in which case struct are parsed.
+ */
+static void parseTypedef(vString *const ident, objcToken what) {
+ switch (what) {
+ case ObjcSTRUCT:
+ toDoNext = &parseStruct;
+ comeAfter = &parseTypedef;
+ break;
+
+ case ObjcENUM:
+ toDoNext = &parseEnum;
+ comeAfter = &parseTypedef;
+ break;
+
+ case ObjcIDENTIFIER:
+ vStringCopy(tempName, ident);
+ break;
+
+ case Tok_semi: /* ';' */
+ addTag(tempName, K_TYPEDEF);
+ vStringClear(tempName);
+ toDoNext = &globalScope;
+ break;
+
+ default:
+ /* we don't care */
+ break;
+ }
+}
+
+static void ignorePreprocStuff(vString *const UNUSED(ident), objcToken what) {
+ static boolean escaped = FALSE;
+
+ switch (what) {
+ case Tok_Backslash:
+ escaped = TRUE;
+ break;
+
+ case Tok_EOL:
+ if (escaped) {
+ escaped = FALSE;
+ } else {
+ toDoNext = &globalScope;
+ }
+ break;
+
+ default:
+ escaped = FALSE;
+ break;
+ }
+}
+
+static void parseMacroName(vString *const ident, objcToken what) {
+ if (what == ObjcIDENTIFIER) addTag(ident, K_MACRO);
+
+ toDoNext = &ignorePreprocStuff;
+}
+
+static void parsePreproc(vString *const ident, objcToken what) {
+ switch (what) {
+ case ObjcIDENTIFIER:
+ if (strcmp(vStringValue(ident), "define") == 0)
+ toDoNext = &parseMacroName;
+ else
+ toDoNext = &ignorePreprocStuff;
+ break;
+
+ default:
+ toDoNext = &ignorePreprocStuff;
+ break;
+ }
+}
+
+/* Handle the "strong" top levels, all 'big' declarations
+ * happen here */
+static void globalScope(vString *const ident, objcToken what) {
+ switch (what) {
+ case Tok_Sharp:
+ toDoNext = &parsePreproc;
+ break;
+
+ case ObjcSTRUCT:
+ toDoNext = &parseStruct;
+ comeAfter = &globalScope;
+ break;
+
+ case ObjcIDENTIFIER:
+ /* we keep track of the identifier if we
+ * come across a function. */
+ vStringCopy(tempName, ident);
+ break;
+
+ case Tok_PARL:
+ /* if we find an opening parenthesis it means we
+ * found a function (or a macro...) */
+ addTag(tempName, K_FUNCTION);
+ vStringClear(tempName);
+ comeAfter = &globalScope;
+ toDoNext = &ignoreBalanced;
+ ignoreBalanced(ident, what);
+ break;
+
+ case ObjcINTERFACE:
+ toDoNext = &parseInterface;
+ break;
+
+ case ObjcIMPLEMENTATION:
+ toDoNext = &parseImplementation;
+ break;
+
+ case ObjcPROTOCOL:
+ toDoNext = &parseProtocol;
+ break;
+
+ case ObjcTYPEDEF:
+ toDoNext = parseTypedef;
+ comeAfter = &globalScope;
+ break;
+
+ case Tok_CurlL:
+ comeAfter = &globalScope;
+ toDoNext = &ignoreBalanced;
+ ignoreBalanced(ident, what);
+ break;
+
+ case ObjcEND:
+ case ObjcPUBLIC:
+ case ObjcPROTECTED:
+ case ObjcPRIVATE:
+
+ default:
+ /* we don't care */
+ break;
+ }
+}
+
+/*////////////////////////////////////////////////////////////////
+//// Deal with the system */
+
+static void findObjcTags(void) {
+ vString *name = vStringNew();
+ lexingState st;
+ objcToken tok;
+
+ parentName = vStringNew();
+ tempName = vStringNew();
+ fullMethodName = vStringNew();
+ prevIdent = vStringNew();
+
+ st.name = vStringNew();
+ st.cp = fileReadLine();
+ toDoNext = &globalScope;
+ tok = lex(&st);
+ while (tok != Tok_EOF) {
+ (*toDoNext)(st.name, tok);
+ tok = lex(&st);
+ }
+
+ vStringDelete(name);
+ vStringDelete(parentName);
+ vStringDelete(tempName);
+ vStringDelete(fullMethodName);
+ vStringDelete(prevIdent);
+ parentName = NULL;
+ tempName = NULL;
+ prevIdent = NULL;
+ fullMethodName = NULL;
+}
+
+static void objcInitialize(const langType language) {
+ Lang_ObjectiveC = language;
+
+ initKeywordHash();
+}
+
+extern parserDefinition *ObjcParser(void) {
+ static const char *const extensions[] = {"m", "h", NULL};
+ parserDefinition *def = parserNew("ObjectiveC");
+ def->kinds = ObjcKinds;
+ def->kindCount = KIND_COUNT(ObjcKinds);
+ def->extensions = extensions;
+ def->parser = findObjcTags;
+ def->initialize = objcInitialize;
+
+ return def;
+}
diff --git a/third_party/ctags/ocaml.c b/third_party/ctags/ocaml.c
new file mode 100644
index 000000000..d08d71062
--- /dev/null
+++ b/third_party/ctags/ocaml.c
@@ -0,0 +1,1713 @@
+/*
+ * Copyright (c) 2009, Vincent Berthoux
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for Objective Caml
+ * language files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/* To get rid of unused parameter warning in
+ * -Wextra */
+#ifdef UNUSED
+#elif defined(__GNUC__)
+#define UNUSED(x) UNUSED_##x __attribute__((unused))
+#elif defined(__LCLINT__)
+#define UNUSED(x) /*@unused@*/ x
+#else
+#define UNUSED(x) x
+#endif
+#define OCAML_MAX_STACK_SIZE 256
+
+typedef enum {
+ K_CLASS, /* Ocaml class, relatively rare */
+ K_METHOD, /* class method */
+ K_MODULE, /* Ocaml module OR functor */
+ K_VAR,
+ K_TYPE, /* name of an OCaml type */
+ K_FUNCTION,
+ K_CONSTRUCTOR, /* Constructor of a sum type */
+ K_RECORDFIELD,
+ K_EXCEPTION
+} ocamlKind;
+
+static kindOption OcamlKinds[] = {
+ {TRUE, 'c', "class", "classes"},
+ {TRUE, 'm', "method", "Object's method"},
+ {TRUE, 'M', "module", "Module or functor"},
+ {TRUE, 'v', "var", "Global variable"},
+ {TRUE, 't', "type", "Type name"},
+ {TRUE, 'f', "function", "A function"},
+ {TRUE, 'C', "Constructor", "A constructor"},
+ {TRUE, 'r', "Record field", "A 'structure' field"},
+ {TRUE, 'e', "Exception", "An exception"}};
+
+typedef enum {
+ OcaKEYWORD_and,
+ OcaKEYWORD_begin,
+ OcaKEYWORD_class,
+ OcaKEYWORD_do,
+ OcaKEYWORD_done,
+ OcaKEYWORD_else,
+ OcaKEYWORD_end,
+ OcaKEYWORD_exception,
+ OcaKEYWORD_for,
+ OcaKEYWORD_functor,
+ OcaKEYWORD_fun,
+ OcaKEYWORD_if,
+ OcaKEYWORD_in,
+ OcaKEYWORD_let,
+ OcaKEYWORD_value,
+ OcaKEYWORD_match,
+ OcaKEYWORD_method,
+ OcaKEYWORD_module,
+ OcaKEYWORD_mutable,
+ OcaKEYWORD_object,
+ OcaKEYWORD_of,
+ OcaKEYWORD_rec,
+ OcaKEYWORD_sig,
+ OcaKEYWORD_struct,
+ OcaKEYWORD_then,
+ OcaKEYWORD_try,
+ OcaKEYWORD_type,
+ OcaKEYWORD_val,
+ OcaKEYWORD_virtual,
+ OcaKEYWORD_while,
+ OcaKEYWORD_with,
+
+ OcaIDENTIFIER,
+ Tok_PARL, /* '(' */
+ Tok_PARR, /* ')' */
+ Tok_BRL, /* '[' */
+ Tok_BRR, /* ']' */
+ Tok_CurlL, /* '{' */
+ Tok_CurlR, /* '}' */
+ Tok_Prime, /* '\'' */
+ Tok_Pipe, /* '|' */
+ Tok_EQ, /* '=' */
+ Tok_Val, /* string/number/poo */
+ Tok_Op, /* any operator recognized by the language */
+ Tok_semi, /* ';' */
+ Tok_comma, /* ',' */
+ Tok_To, /* '->' */
+ Tok_Sharp, /* '#' */
+ Tok_Backslash, /* '\\' */
+
+ Tok_EOF /* END of file */
+} ocamlKeyword;
+
+typedef struct sOcaKeywordDesc {
+ const char *name;
+ ocamlKeyword id;
+} ocaKeywordDesc;
+
+typedef ocamlKeyword ocaToken;
+
+static const ocaKeywordDesc OcamlKeywordTable[] = {
+ {"and", OcaKEYWORD_and},
+ {"begin", OcaKEYWORD_begin},
+ {"class", OcaKEYWORD_class},
+ {"do", OcaKEYWORD_do},
+ {"done", OcaKEYWORD_done},
+ {"else", OcaKEYWORD_else},
+ {"end", OcaKEYWORD_end},
+ {"exception", OcaKEYWORD_exception},
+ {"for", OcaKEYWORD_for},
+ {"fun", OcaKEYWORD_fun},
+ {"function", OcaKEYWORD_fun},
+ {"functor", OcaKEYWORD_functor},
+ {"in", OcaKEYWORD_in},
+ {"let", OcaKEYWORD_let},
+ {"match", OcaKEYWORD_match},
+ {"method", OcaKEYWORD_method},
+ {"module", OcaKEYWORD_module},
+ {"mutable", OcaKEYWORD_mutable},
+ {"object", OcaKEYWORD_object},
+ {"of", OcaKEYWORD_of},
+ {"rec", OcaKEYWORD_rec},
+ {"sig", OcaKEYWORD_sig},
+ {"struct", OcaKEYWORD_struct},
+ {"then", OcaKEYWORD_then},
+ {"try", OcaKEYWORD_try},
+ {"type", OcaKEYWORD_type},
+ {"val", OcaKEYWORD_val},
+ {"value", OcaKEYWORD_value}, /* just to handle revised syntax */
+ {"virtual", OcaKEYWORD_virtual},
+ {"while", OcaKEYWORD_while},
+ {"with", OcaKEYWORD_with},
+
+ {"or", Tok_Op},
+ {"mod ", Tok_Op},
+ {"land ", Tok_Op},
+ {"lor ", Tok_Op},
+ {"lxor ", Tok_Op},
+ {"lsl ", Tok_Op},
+ {"lsr ", Tok_Op},
+ {"asr", Tok_Op},
+ {"->", Tok_To},
+ {"true", Tok_Val},
+ {"false", Tok_Val}};
+
+static langType Lang_Ocaml;
+
+boolean exportLocalInfo = FALSE;
+
+/*//////////////////////////////////////////////////////////////////
+//// lexingInit */
+typedef struct _lexingState {
+ vString *name; /* current parsed identifier/operator */
+ const unsigned char *cp; /* position in stream */
+} lexingState;
+
+/* array of the size of all possible value for a char */
+boolean isOperator[1 << (8 * sizeof(char))] = {FALSE};
+
+static void initKeywordHash(void) {
+ const size_t count = sizeof(OcamlKeywordTable) / sizeof(ocaKeywordDesc);
+ size_t i;
+
+ for (i = 0; i < count; ++i) {
+ addKeyword(OcamlKeywordTable[i].name, Lang_Ocaml,
+ (int)OcamlKeywordTable[i].id);
+ }
+}
+
+/* definition of all the operator in OCaml,
+ * /!\ certain operator get special treatment
+ * in regards of their role in OCaml grammar :
+ * '|' ':' '=' '~' and '?' */
+static void initOperatorTable(void) {
+ isOperator['!'] = TRUE;
+ isOperator['$'] = TRUE;
+ isOperator['%'] = TRUE;
+ isOperator['&'] = TRUE;
+ isOperator['*'] = TRUE;
+ isOperator['+'] = TRUE;
+ isOperator['-'] = TRUE;
+ isOperator['.'] = TRUE;
+ isOperator['/'] = TRUE;
+ isOperator[':'] = TRUE;
+ isOperator['<'] = TRUE;
+ isOperator['='] = TRUE;
+ isOperator['>'] = TRUE;
+ isOperator['?'] = TRUE;
+ isOperator['@'] = TRUE;
+ isOperator['^'] = TRUE;
+ isOperator['~'] = TRUE;
+ isOperator['|'] = TRUE;
+}
+
+/*//////////////////////////////////////////////////////////////////////
+//// Lexing */
+static boolean isNum(char c) {
+ return c >= '0' && c <= '9';
+}
+static boolean isLowerAlpha(char c) {
+ return c >= 'a' && c <= 'z';
+}
+
+static boolean isUpperAlpha(char c) {
+ return c >= 'A' && c <= 'Z';
+}
+
+static boolean isAlpha(char c) {
+ return isLowerAlpha(c) || isUpperAlpha(c);
+}
+
+static boolean isIdent(char c) {
+ return isNum(c) || isAlpha(c) || c == '_' || c == '\'';
+}
+
+static boolean isSpace(char c) {
+ return c == ' ' || c == '\t' || c == '\r' || c == '\n';
+}
+
+static void eatWhiteSpace(lexingState *st) {
+ const unsigned char *cp = st->cp;
+ while (isSpace(*cp)) cp++;
+
+ st->cp = cp;
+}
+
+static void eatString(lexingState *st) {
+ boolean lastIsBackSlash = FALSE;
+ boolean unfinished = TRUE;
+ const unsigned char *c = st->cp + 1;
+
+ while (unfinished) {
+ /* end of line should never happen.
+ * we tolerate it */
+ if (c == NULL || c[0] == '\0')
+ break;
+ else if (*c == '"' && !lastIsBackSlash)
+ unfinished = FALSE;
+ else
+ lastIsBackSlash = *c == '\\';
+
+ c++;
+ }
+
+ st->cp = c;
+}
+
+static void eatComment(lexingState *st) {
+ boolean unfinished = TRUE;
+ boolean lastIsStar = FALSE;
+ const unsigned char *c = st->cp + 2;
+
+ while (unfinished) {
+ /* we've reached the end of the line..
+ * so we have to reload a line... */
+ if (c == NULL || *c == '\0') {
+ st->cp = fileReadLine();
+ /* WOOPS... no more input...
+ * we return, next lexing read
+ * will be null and ok */
+ if (st->cp == NULL) return;
+ c = st->cp;
+ }
+ /* we've reached the end of the comment */
+ else if (*c == ')' && lastIsStar)
+ unfinished = FALSE;
+ /* here we deal with imbricated comment, which
+ * are allowed in OCaml */
+ else if (c[0] == '(' && c[1] == '*') {
+ st->cp = c;
+ eatComment(st);
+
+ c = st->cp;
+ if (c == NULL) return;
+
+ lastIsStar = FALSE;
+ c++;
+ }
+ /* OCaml has a rule which says :
+ *
+ * "Comments do not occur inside string or character literals.
+ * Nested comments are handled correctly."
+ *
+ * So if we encounter a string beginning, we must parse it to
+ * get a good comment nesting (bug ID: 3117537)
+ */
+ else if (*c == '"') {
+ st->cp = c;
+ eatString(st);
+ c = st->cp;
+ } else {
+ lastIsStar = '*' == *c;
+ c++;
+ }
+ }
+
+ st->cp = c;
+}
+
+static void readIdentifier(lexingState *st) {
+ const unsigned char *p;
+ vStringClear(st->name);
+
+ /* first char is a simple letter */
+ if (isAlpha(*st->cp) || *st->cp == '_') vStringPut(st->name, (int)*st->cp);
+
+ /* Go till you get identifier chars */
+ for (p = st->cp + 1; isIdent(*p); p++) vStringPut(st->name, (int)*p);
+
+ st->cp = p;
+
+ vStringTerminate(st->name);
+}
+
+static ocamlKeyword eatNumber(lexingState *st) {
+ while (isNum(*st->cp)) st->cp++;
+ return Tok_Val;
+}
+
+/* Operator can be defined in OCaml as a function
+ * so we must be ample enough to parse them normally */
+static ocamlKeyword eatOperator(lexingState *st) {
+ int count = 0;
+ const unsigned char *root = st->cp;
+
+ vStringClear(st->name);
+
+ while (isOperator[st->cp[count]]) {
+ vStringPut(st->name, st->cp[count]);
+ count++;
+ }
+
+ vStringTerminate(st->name);
+
+ st->cp += count;
+ if (count <= 1) {
+ switch (root[0]) {
+ case '|':
+ return Tok_Pipe;
+ case '=':
+ return Tok_EQ;
+ default:
+ return Tok_Op;
+ }
+ } else if (count == 2 && root[0] == '-' && root[1] == '>')
+ return Tok_To;
+ else
+ return Tok_Op;
+}
+
+/* The lexer is in charge of reading the file.
+ * Some of sub-lexer (like eatComment) also read file.
+ * lexing is finished when the lexer return Tok_EOF */
+static ocamlKeyword lex(lexingState *st) {
+ int retType;
+ /* handling data input here */
+ while (st->cp == NULL || st->cp[0] == '\0') {
+ st->cp = fileReadLine();
+ if (st->cp == NULL) return Tok_EOF;
+ }
+
+ if (isAlpha(*st->cp)) {
+ readIdentifier(st);
+ retType = lookupKeyword(vStringValue(st->name), Lang_Ocaml);
+
+ if (retType == -1) /* If it's not a keyword */
+ {
+ return OcaIDENTIFIER;
+ } else {
+ return retType;
+ }
+ } else if (isNum(*st->cp))
+ return eatNumber(st);
+ else if (isSpace(*st->cp)) {
+ eatWhiteSpace(st);
+ return lex(st);
+ }
+ /* OCaml permit the definition of our own operators
+ * so here we check all the consecuting chars which
+ * are operators to discard them. */
+ else if (isOperator[*st->cp])
+ return eatOperator(st);
+ else
+ switch (*st->cp) {
+ case '(':
+ if (st->cp[1] == '*') /* ergl, a comment */
+ {
+ eatComment(st);
+ return lex(st);
+ } else {
+ st->cp++;
+ return Tok_PARL;
+ }
+
+ case ')':
+ st->cp++;
+ return Tok_PARR;
+ case '[':
+ st->cp++;
+ return Tok_BRL;
+ case ']':
+ st->cp++;
+ return Tok_BRR;
+ case '{':
+ st->cp++;
+ return Tok_CurlL;
+ case '}':
+ st->cp++;
+ return Tok_CurlR;
+ case '\'':
+ st->cp++;
+ return Tok_Prime;
+ case ',':
+ st->cp++;
+ return Tok_comma;
+ case '=':
+ st->cp++;
+ return Tok_EQ;
+ case ';':
+ st->cp++;
+ return Tok_semi;
+ case '"':
+ eatString(st);
+ return Tok_Val;
+ case '_':
+ st->cp++;
+ return Tok_Val;
+ case '#':
+ st->cp++;
+ return Tok_Sharp;
+ case '\\':
+ st->cp++;
+ return Tok_Backslash;
+
+ default:
+ st->cp++;
+ break;
+ }
+
+ /* default return if nothing is recognized,
+ * shouldn't happen, but at least, it will
+ * be handled without destroying the parsing. */
+ return Tok_Val;
+}
+
+/*//////////////////////////////////////////////////////////////////////
+//// Parsing */
+typedef void (*parseNext)(vString *const ident, ocaToken what);
+
+/********** Helpers */
+/* This variable hold the 'parser' which is going to
+ * handle the next token */
+static parseNext toDoNext;
+
+/* Special variable used by parser eater to
+ * determine which action to put after their
+ * job is finished. */
+static parseNext comeAfter;
+
+/* If a token put an end to current delcaration/
+ * statement */
+static ocaToken terminatingToken;
+
+/* Token to be searched by the different
+ * parser eater. */
+static ocaToken waitedToken;
+
+/* name of the last class, used for
+ * context stacking. */
+vString *lastClass;
+
+vString *voidName;
+
+typedef enum _sContextKind { ContextStrong, ContextSoft } contextKind;
+
+typedef enum _sContextType {
+ ContextType,
+ ContextModule,
+ ContextClass,
+ ContextValue,
+ ContextFunction,
+ ContextMethod,
+ ContextBlock
+} contextType;
+
+typedef struct _sOcamlContext {
+ contextKind kind; /* well if the context is strong or not */
+ contextType type;
+ parseNext callback; /* what to do when a context is pop'd */
+ vString *contextName; /* name, if any, of the surrounding context */
+} ocamlContext;
+
+/* context stack, can be used to output scope information
+ * into the tag file. */
+ocamlContext stack[OCAML_MAX_STACK_SIZE];
+/* current position in the tag */
+int stackIndex;
+
+/* special function, often recalled, so putting it here */
+static void globalScope(vString *const ident, ocaToken what);
+
+/* Return : index of the last named context if one
+ * is found, -1 otherwise */
+static int getLastNamedIndex(void) {
+ int i;
+
+ for (i = stackIndex - 1; i >= 0; --i) {
+ if (vStringLength(stack[i].contextName) > 0) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static const char *contextDescription(contextType t) {
+ switch (t) {
+ case ContextFunction:
+ return "function";
+ case ContextMethod:
+ return "method";
+ case ContextValue:
+ return "value";
+ case ContextModule:
+ return "Module";
+ case ContextType:
+ return "type";
+ case ContextClass:
+ return "class";
+ case ContextBlock:
+ return "begin/end";
+ }
+
+ return NULL;
+}
+
+static char contextTypeSuffix(contextType t) {
+ switch (t) {
+ case ContextFunction:
+ case ContextMethod:
+ case ContextValue:
+ case ContextModule:
+ return '/';
+ case ContextType:
+ return '.';
+ case ContextClass:
+ return '#';
+ case ContextBlock:
+ return ' ';
+ }
+
+ return '$';
+}
+
+/* Push a new context, handle null string */
+static void pushContext(contextKind kind, contextType type, parseNext after,
+ vString const *contextName) {
+ int parentIndex;
+
+ if (stackIndex >= OCAML_MAX_STACK_SIZE) {
+ verbose("OCaml Maximum depth reached");
+ return;
+ }
+
+ stack[stackIndex].kind = kind;
+ stack[stackIndex].type = type;
+ stack[stackIndex].callback = after;
+
+ parentIndex = getLastNamedIndex();
+ if (contextName == NULL) {
+ vStringClear(stack[stackIndex++].contextName);
+ return;
+ }
+
+ if (parentIndex >= 0) {
+ vStringCopy(stack[stackIndex].contextName, stack[parentIndex].contextName);
+ vStringPut(stack[stackIndex].contextName,
+ contextTypeSuffix(stack[parentIndex].type));
+
+ vStringCat(stack[stackIndex].contextName, contextName);
+ } else
+ vStringCopy(stack[stackIndex].contextName, contextName);
+
+ stackIndex++;
+}
+
+static void pushStrongContext(vString *name, contextType type) {
+ pushContext(ContextStrong, type, &globalScope, name);
+}
+
+static void pushSoftContext(parseNext continuation, vString *name,
+ contextType type) {
+ pushContext(ContextSoft, type, continuation, name);
+}
+
+static void pushEmptyContext(parseNext continuation) {
+ pushContext(ContextSoft, ContextValue, continuation, NULL);
+}
+
+/* unroll the stack until the last named context.
+ * then discard it. Used to handle the :
+ * let f x y = ...
+ * in ...
+ * where the context is reseted after the in. Context may have
+ * been really nested before that. */
+static void popLastNamed(void) {
+ int i = getLastNamedIndex();
+
+ if (i >= 0) {
+ stackIndex = i;
+ toDoNext = stack[i].callback;
+ vStringClear(stack[i].contextName);
+ } else {
+ /* ok, no named context found...
+ * (should not happen). */
+ stackIndex = 0;
+ toDoNext = &globalScope;
+ }
+}
+
+/* pop a context without regarding it's content
+ * (beside handling empty stack case) */
+static void popSoftContext(void) {
+ if (stackIndex <= 0) {
+ toDoNext = &globalScope;
+ } else {
+ stackIndex--;
+ toDoNext = stack[stackIndex].callback;
+ vStringClear(stack[stackIndex].contextName);
+ }
+}
+
+/* Reset everything until the last global space.
+ * a strong context can be :
+ * - module
+ * - class definition
+ * - the initial global space
+ * - a _global_ delcaration (let at global scope or in a module).
+ * Created to exit quickly deeply nested context */
+static contextType popStrongContext(void) {
+ int i;
+
+ for (i = stackIndex - 1; i >= 0; --i) {
+ if (stack[i].kind == ContextStrong) {
+ stackIndex = i;
+ toDoNext = stack[i].callback;
+ vStringClear(stack[i].contextName);
+ return stack[i].type;
+ }
+ }
+ /* ok, no strong context found... */
+ stackIndex = 0;
+ toDoNext = &globalScope;
+ return -1;
+}
+
+/* Ignore everything till waitedToken and jump to comeAfter.
+ * If the "end" keyword is encountered break, doesn't remember
+ * why though. */
+static void tillToken(vString *const UNUSED(ident), ocaToken what) {
+ if (what == waitedToken)
+ toDoNext = comeAfter;
+ else if (what == OcaKEYWORD_end) {
+ popStrongContext();
+ toDoNext = &globalScope;
+ }
+}
+
+/* Ignore everything till a waitedToken is seen, but
+ * take care of balanced parentheses/bracket use */
+static void contextualTillToken(vString *const UNUSED(ident), ocaToken what) {
+ static int parentheses = 0;
+ static int bracket = 0;
+ static int curly = 0;
+
+ switch (what) {
+ case Tok_PARL:
+ parentheses--;
+ break;
+ case Tok_PARR:
+ parentheses++;
+ break;
+ case Tok_CurlL:
+ curly--;
+ break;
+ case Tok_CurlR:
+ curly++;
+ break;
+ case Tok_BRL:
+ bracket--;
+ break;
+ case Tok_BRR:
+ bracket++;
+ break;
+
+ default: /* other token are ignored */
+ break;
+ }
+
+ if (what == waitedToken && parentheses == 0 && bracket == 0 && curly == 0)
+ toDoNext = comeAfter;
+
+ else if (what == OcaKEYWORD_end) {
+ popStrongContext();
+ toDoNext = &globalScope;
+ }
+}
+
+/* Wait for waitedToken and jump to comeAfter or let
+ * the globalScope handle declarations */
+static void tillTokenOrFallback(vString *const ident, ocaToken what) {
+ if (what == waitedToken)
+ toDoNext = comeAfter;
+ else
+ globalScope(ident, what);
+}
+
+/* ignore token till waitedToken, or give up if find
+ * terminatingToken. Use globalScope to handle new
+ * declarations. */
+static void tillTokenOrTerminatingOrFallback(vString *const ident,
+ ocaToken what) {
+ if (what == waitedToken)
+ toDoNext = comeAfter;
+ else if (what == terminatingToken)
+ toDoNext = globalScope;
+ else
+ globalScope(ident, what);
+}
+
+/* ignore the next token in the stream and jump to the
+ * given comeAfter state */
+static void ignoreToken(vString *const UNUSED(ident), ocaToken UNUSED(what)) {
+ toDoNext = comeAfter;
+}
+
+/********** Grammar */
+/* the purpose of each function is detailled near their
+ * implementation */
+
+static void killCurrentState(void) {
+
+ /* Tracking the kind of previous strong
+ * context, if it doesn't match with a
+ * really strong entity, repop */
+ switch (popStrongContext()) {
+
+ case ContextValue:
+ popStrongContext();
+ break;
+ case ContextFunction:
+ popStrongContext();
+ break;
+ case ContextMethod:
+ popStrongContext();
+ break;
+
+ case ContextType:
+ popStrongContext();
+ break;
+ case ContextBlock:
+ break;
+ case ContextModule:
+ break;
+ case ContextClass:
+ break;
+ default:
+ /* nothing more */
+ break;
+ }
+}
+
+/* used to prepare tag for OCaml, just in case their is a need to
+ * add additional information to the tag. */
+static void prepareTag(tagEntryInfo *tag, vString const *name, ocamlKind kind) {
+ int parentIndex;
+
+ initTagEntry(tag, vStringValue(name));
+ tag->kindName = OcamlKinds[kind].name;
+ tag->kind = OcamlKinds[kind].letter;
+
+ if (kind == K_MODULE) {
+ tag->lineNumberEntry = TRUE;
+ tag->lineNumber = 1;
+ }
+ parentIndex = getLastNamedIndex();
+ if (parentIndex >= 0) {
+ tag->extensionFields.scope[0] = contextDescription(stack[parentIndex].type);
+ tag->extensionFields.scope[1] =
+ vStringValue(stack[parentIndex].contextName);
+ }
+}
+
+/* Used to centralise tag creation, and be able to add
+ * more information to it in the future */
+static void addTag(vString *const ident, int kind) {
+ if (OcamlKinds[kind].enabled && ident != NULL && vStringLength(ident) > 0) {
+ tagEntryInfo toCreate;
+ prepareTag(&toCreate, ident, kind);
+ makeTagEntry(&toCreate);
+ }
+}
+
+boolean needStrongPoping = FALSE;
+static void requestStrongPoping(void) {
+ needStrongPoping = TRUE;
+}
+
+static void cleanupPreviousParser(void) {
+ if (needStrongPoping) {
+ needStrongPoping = FALSE;
+ popStrongContext();
+ }
+}
+
+/* Due to some circular dependencies, the following functions
+ * must be forward-declared. */
+static void letParam(vString *const ident, ocaToken what);
+static void localScope(vString *const ident, ocaToken what);
+static void mayRedeclare(vString *const ident, ocaToken what);
+static void typeSpecification(vString *const ident, ocaToken what);
+
+/*
+ * Parse a record type
+ * type ident = // parsed previously
+ * {
+ * ident1: type1;
+ * ident2: type2;
+ * }
+ */
+static void typeRecord(vString *const ident, ocaToken what) {
+ switch (what) {
+ case OcaIDENTIFIER:
+ addTag(ident, K_RECORDFIELD);
+ terminatingToken = Tok_CurlR;
+ waitedToken = Tok_semi;
+ comeAfter = &typeRecord;
+ toDoNext = &tillTokenOrTerminatingOrFallback;
+ break;
+
+ case OcaKEYWORD_mutable:
+ /* ignore it */
+ break;
+
+ case Tok_CurlR:
+ popStrongContext();
+ toDoNext = &globalScope;
+ break;
+
+ default: /* don't care */
+ break;
+ }
+}
+
+/* handle :
+ * exception ExceptionName of ... */
+static void exceptionDecl(vString *const ident, ocaToken what) {
+ if (what == OcaIDENTIFIER) {
+ addTag(ident, K_EXCEPTION);
+ } else /* probably ill-formed, give back to global scope */
+ {
+ globalScope(ident, what);
+ }
+ toDoNext = &globalScope;
+}
+
+tagEntryInfo tempTag;
+vString *tempIdent;
+
+/* Ensure a constructor is not a type path beginning
+ * with a module */
+static void constructorValidation(vString *const ident, ocaToken what) {
+ switch (what) {
+ case Tok_Op: /* if we got a '.' which is an operator */
+ toDoNext = &globalScope;
+ popStrongContext();
+ needStrongPoping = FALSE;
+ break;
+
+ case OcaKEYWORD_of: /* OK, it must be a constructor :) */
+ makeTagEntry(&tempTag);
+ vStringClear(tempIdent);
+ toDoNext = &tillTokenOrFallback;
+ comeAfter = &typeSpecification;
+ waitedToken = Tok_Pipe;
+ break;
+
+ case Tok_Pipe: /* OK, it was a constructor :) */
+ makeTagEntry(&tempTag);
+ vStringClear(tempIdent);
+ toDoNext = &typeSpecification;
+ break;
+
+ default: /* and mean that we're not facing a module name */
+ makeTagEntry(&tempTag);
+ vStringClear(tempIdent);
+ toDoNext = &tillTokenOrFallback;
+ comeAfter = &typeSpecification;
+ waitedToken = Tok_Pipe;
+
+ /* nothing in the context, discard it */
+ popStrongContext();
+
+ /* to be sure we use this token */
+ globalScope(ident, what);
+ }
+}
+
+/* Parse beginning of type definition
+ * type 'avar ident =
+ * or
+ * type ('var1, 'var2) ident =
+ */
+static void typeDecl(vString *const ident, ocaToken what) {
+ switch (what) {
+ /* parameterized */
+ case Tok_Prime:
+ comeAfter = &typeDecl;
+ toDoNext = &ignoreToken;
+ break;
+ /* LOTS of parameters */
+ case Tok_PARL:
+ comeAfter = &typeDecl;
+ waitedToken = Tok_PARR;
+ toDoNext = &tillToken;
+ break;
+
+ case OcaIDENTIFIER:
+ addTag(ident, K_TYPE);
+ pushStrongContext(ident, ContextType);
+ requestStrongPoping();
+ waitedToken = Tok_EQ;
+ comeAfter = &typeSpecification;
+ toDoNext = &tillTokenOrFallback;
+ break;
+
+ default:
+ globalScope(ident, what);
+ }
+}
+
+/* Parse type of kind
+ * type bidule = Ctor1 of ...
+ * | Ctor2
+ * | Ctor3 of ...
+ * or
+ * type bidule = | Ctor1 of ... | Ctor2
+ *
+ * when type bidule = { ... } is detected,
+ * let typeRecord handle it. */
+static void typeSpecification(vString *const ident, ocaToken what) {
+ switch (what) {
+ case OcaIDENTIFIER:
+ if (isUpperAlpha(ident->buffer[0])) {
+ /* here we handle type aliases of type
+ * type foo = AnotherModule.bar
+ * AnotherModule can mistakenly be took
+ * for a constructor. */
+ vStringCopy(tempIdent, ident);
+ prepareTag(&tempTag, tempIdent, K_CONSTRUCTOR);
+ toDoNext = &constructorValidation;
+ } else {
+ toDoNext = &tillTokenOrFallback;
+ comeAfter = &typeSpecification;
+ waitedToken = Tok_Pipe;
+ }
+ break;
+
+ case OcaKEYWORD_and:
+ toDoNext = &typeDecl;
+ break;
+
+ case Tok_BRL: /* the '[' & ']' are ignored to accommodate */
+ case Tok_BRR: /* with the revised syntax */
+ case Tok_Pipe:
+ /* just ignore it */
+ break;
+
+ case Tok_CurlL:
+ toDoNext = &typeRecord;
+ break;
+
+ default: /* don't care */
+ break;
+ }
+}
+
+static boolean dirtySpecialParam = FALSE;
+
+/* parse the ~label and ~label:type parameter */
+static void parseLabel(vString *const ident, ocaToken what) {
+ static int parCount = 0;
+
+ switch (what) {
+ case OcaIDENTIFIER:
+ if (!dirtySpecialParam) {
+
+ if (exportLocalInfo) addTag(ident, K_VAR);
+
+ dirtySpecialParam = TRUE;
+ }
+ break;
+
+ case Tok_PARL:
+ parCount++;
+ break;
+
+ case Tok_PARR:
+ parCount--;
+ if (parCount == 0) toDoNext = &letParam;
+ break;
+
+ case Tok_Op:
+ if (ident->buffer[0] == ':') {
+ toDoNext = &ignoreToken;
+ comeAfter = &letParam;
+ } else if (parCount == 0 && dirtySpecialParam) {
+ toDoNext = &letParam;
+ letParam(ident, what);
+ }
+ break;
+
+ default:
+ if (parCount == 0 && dirtySpecialParam) {
+ toDoNext = &letParam;
+ letParam(ident, what);
+ }
+ break;
+ }
+}
+
+/* Optional argument with syntax like this :
+ * ?(foo = value) */
+static void parseOptionnal(vString *const ident, ocaToken what) {
+ static int parCount = 0;
+
+ switch (what) {
+ case OcaIDENTIFIER:
+ if (!dirtySpecialParam) {
+ if (exportLocalInfo) addTag(ident, K_VAR);
+
+ dirtySpecialParam = TRUE;
+
+ if (parCount == 0) toDoNext = &letParam;
+ }
+ break;
+
+ case Tok_PARL:
+ parCount++;
+ break;
+
+ case Tok_PARR:
+ parCount--;
+ if (parCount == 0) toDoNext = &letParam;
+ break;
+
+ default: /* don't care */
+ break;
+ }
+}
+
+/** handle let inside functions (so like it's name
+ * say : local let */
+static void localLet(vString *const ident, ocaToken what) {
+ switch (what) {
+ case Tok_PARL:
+ /* We ignore this token to be able to parse such
+ * declarations :
+ * let (ident : type) = ...
+ */
+ break;
+
+ case OcaKEYWORD_rec:
+ /* just ignore to be able to parse such declarations:
+ * let rec ident = ... */
+ break;
+
+ case Tok_Op:
+ /* we are defining a new operator, it's a
+ * function definition */
+ if (exportLocalInfo) addTag(ident, K_FUNCTION);
+
+ pushSoftContext(mayRedeclare, ident, ContextFunction);
+ toDoNext = &letParam;
+ break;
+
+ /* Can be a weiiird binding, or an '_' */
+ case Tok_Val:
+ if (exportLocalInfo) addTag(ident, K_VAR);
+ pushSoftContext(mayRedeclare, ident, ContextValue);
+ toDoNext = &letParam;
+ break;
+
+ case OcaIDENTIFIER:
+ if (exportLocalInfo) addTag(ident, K_VAR);
+ pushSoftContext(mayRedeclare, ident, ContextValue);
+ toDoNext = &letParam;
+ break;
+
+ case OcaKEYWORD_end:
+ popStrongContext();
+ break;
+
+ default:
+ toDoNext = &localScope;
+ break;
+ }
+}
+
+/* parse :
+ * | pattern pattern -> ...
+ * or
+ * pattern apttern apttern -> ...
+ * we ignore all identifiers declared in the pattern,
+ * because their scope is likely to be even more limited
+ * than the let definitions.
+ * Used after a match ... with, or a function ... or fun ...
+ * because their syntax is similar. */
+static void matchPattern(vString *const ident, ocaToken what) {
+ /* keep track of [], as it
+ * can be used in patterns and can
+ * mean the end of match expression in
+ * revised syntax */
+ static int braceCount = 0;
+
+ switch (what) {
+ case Tok_To:
+ pushEmptyContext(&matchPattern);
+ toDoNext = &mayRedeclare;
+ break;
+
+ case Tok_BRL:
+ braceCount++;
+ break;
+
+ case OcaKEYWORD_value:
+ popLastNamed();
+ globalScope(ident, what);
+ break;
+
+ case OcaKEYWORD_in:
+ popLastNamed();
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* Used at the beginning of a new scope (begin of a
+ * definition, parenthesis...) to catch inner let
+ * definition that may be in. */
+static void mayRedeclare(vString *const ident, ocaToken what) {
+ switch (what) {
+ case OcaKEYWORD_value:
+ // let globalScope handle it
+ globalScope(ident, what);
+ break;
+
+ case OcaKEYWORD_let:
+ case OcaKEYWORD_val:
+ toDoNext = localLet;
+ break;
+
+ case OcaKEYWORD_object:
+ vStringClear(lastClass);
+ pushContext(ContextStrong, ContextClass, &localScope, NULL /*voidName */);
+ needStrongPoping = FALSE;
+ toDoNext = &globalScope;
+ break;
+
+ case OcaKEYWORD_for:
+ case OcaKEYWORD_while:
+ toDoNext = &tillToken;
+ waitedToken = OcaKEYWORD_do;
+ comeAfter = &mayRedeclare;
+ break;
+
+ case OcaKEYWORD_try:
+ toDoNext = &mayRedeclare;
+ pushSoftContext(matchPattern, ident, ContextFunction);
+ break;
+
+ case OcaKEYWORD_fun:
+ toDoNext = &matchPattern;
+ break;
+
+ /* Handle the special ;; from the OCaml
+ * Top level */
+ case Tok_semi:
+ default:
+ toDoNext = &localScope;
+ localScope(ident, what);
+ }
+}
+
+/* parse :
+ * p1 p2 ... pn = ...
+ * or
+ * ?(p1=v) p2 ~p3 ~pn:ja ... = ... */
+static void letParam(vString *const ident, ocaToken what) {
+ switch (what) {
+ case Tok_EQ:
+ toDoNext = &mayRedeclare;
+ break;
+
+ case OcaIDENTIFIER:
+ if (exportLocalInfo) addTag(ident, K_VAR);
+ break;
+
+ case Tok_Op:
+ switch (ident->buffer[0]) {
+ case ':':
+ /*popSoftContext(); */
+ /* we got a type signature */
+ comeAfter = &mayRedeclare;
+ toDoNext = &tillTokenOrFallback;
+ waitedToken = Tok_EQ;
+ break;
+
+ /* parse something like
+ * ~varname:type
+ * or
+ * ~varname
+ * or
+ * ~(varname: long type) */
+ case '~':
+ toDoNext = &parseLabel;
+ dirtySpecialParam = FALSE;
+ break;
+
+ /* Optional argument with syntax like this :
+ * ?(bla = value)
+ * or
+ * ?bla */
+ case '?':
+ toDoNext = &parseOptionnal;
+ dirtySpecialParam = FALSE;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default: /* don't care */
+ break;
+ }
+}
+
+/* parse object ...
+ * used to be sure the class definition is not a type
+ * alias */
+static void classSpecif(vString *const UNUSED(ident), ocaToken what) {
+ switch (what) {
+ case OcaKEYWORD_object:
+ pushStrongContext(lastClass, ContextClass);
+ toDoNext = &globalScope;
+ break;
+
+ default:
+ vStringClear(lastClass);
+ toDoNext = &globalScope;
+ }
+}
+
+/* Handle a method ... class declaration.
+ * nearly a copy/paste of globalLet. */
+static void methodDecl(vString *const ident, ocaToken what) {
+
+ switch (what) {
+ case Tok_PARL:
+ /* We ignore this token to be able to parse such
+ * declarations :
+ * let (ident : type) = ... */
+ break;
+
+ case OcaKEYWORD_mutable:
+ case OcaKEYWORD_virtual:
+ case OcaKEYWORD_rec:
+ /* just ignore to be able to parse such declarations:
+ * let rec ident = ... */
+ break;
+
+ case OcaIDENTIFIER:
+ addTag(ident, K_METHOD);
+ /* Normal pushing to get good subs */
+ pushStrongContext(ident, ContextMethod);
+ /*pushSoftContext( globalScope, ident, ContextMethod ); */
+ toDoNext = &letParam;
+ break;
+
+ case OcaKEYWORD_end:
+ popStrongContext();
+ break;
+
+ default:
+ toDoNext = &globalScope;
+ break;
+ }
+}
+
+/* name of the last module, used for
+ * context stacking. */
+vString *lastModule;
+
+/* parse
+ * ... struct (* new global scope *) end
+ * or
+ * ... sig (* new global scope *) end
+ * or
+ * functor ... -> moduleSpecif
+ */
+static void moduleSpecif(vString *const ident, ocaToken what) {
+
+ switch (what) {
+ case OcaKEYWORD_functor:
+ toDoNext = &contextualTillToken;
+ waitedToken = Tok_To;
+ comeAfter = &moduleSpecif;
+ break;
+
+ case OcaKEYWORD_struct:
+ case OcaKEYWORD_sig:
+ pushStrongContext(lastModule, ContextModule);
+ toDoNext = &globalScope;
+ break;
+
+ case Tok_PARL: /* ( */
+ toDoNext = &contextualTillToken;
+ comeAfter = &globalScope;
+ waitedToken = Tok_PARR;
+ contextualTillToken(ident, what);
+ break;
+
+ default:
+ vStringClear(lastModule);
+ toDoNext = &globalScope;
+ }
+}
+
+/* parse :
+ * module name = ...
+ * then pass the token stream to moduleSpecif */
+static void moduleDecl(vString *const ident, ocaToken what) {
+ switch (what) {
+ case OcaKEYWORD_type:
+ /* just ignore it, name come after */
+ break;
+
+ case OcaIDENTIFIER:
+ addTag(ident, K_MODULE);
+ vStringCopy(lastModule, ident);
+ waitedToken = Tok_EQ;
+ comeAfter = &moduleSpecif;
+ toDoNext = &contextualTillToken;
+ break;
+
+ default: /* don't care */
+ break;
+ }
+}
+
+/* parse :
+ * class name = ...
+ * or
+ * class virtual ['a,'b] classname = ... */
+static void classDecl(vString *const ident, ocaToken what) {
+ switch (what) {
+ case OcaIDENTIFIER:
+ addTag(ident, K_CLASS);
+ vStringCopy(lastClass, ident);
+ toDoNext = &contextualTillToken;
+ waitedToken = Tok_EQ;
+ comeAfter = &classSpecif;
+ break;
+
+ case Tok_BRL:
+ toDoNext = &tillToken;
+ waitedToken = Tok_BRR;
+ comeAfter = &classDecl;
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* Handle a global
+ * let ident ...
+ * or
+ * let rec ident ... */
+static void globalLet(vString *const ident, ocaToken what) {
+ switch (what) {
+ case Tok_PARL:
+ /* We ignore this token to be able to parse such
+ * declarations :
+ * let (ident : type) = ...
+ */
+ break;
+
+ case OcaKEYWORD_mutable:
+ case OcaKEYWORD_virtual:
+ case OcaKEYWORD_rec:
+ /* just ignore to be able to parse such declarations:
+ * let rec ident = ... */
+ break;
+
+ case Tok_Op:
+ /* we are defining a new operator, it's a
+ * function definition */
+ addTag(ident, K_FUNCTION);
+ pushStrongContext(ident, ContextFunction);
+ toDoNext = &letParam;
+ break;
+
+ case OcaIDENTIFIER:
+ addTag(ident, K_VAR);
+ pushStrongContext(ident, ContextValue);
+ requestStrongPoping();
+ toDoNext = &letParam;
+ break;
+
+ case OcaKEYWORD_end:
+ popStrongContext();
+ break;
+
+ default:
+ toDoNext = &globalScope;
+ break;
+ }
+}
+
+/* Handle the "strong" top levels, all 'big' declarations
+ * happen here */
+static void globalScope(vString *const UNUSED(ident), ocaToken what) {
+ /* Do not touch, this is used only by the global scope
+ * to handle an 'and' */
+ static parseNext previousParser = &globalScope;
+
+ switch (what) {
+ case OcaKEYWORD_and:
+ cleanupPreviousParser();
+ toDoNext = previousParser;
+ break;
+
+ case OcaKEYWORD_type:
+ cleanupPreviousParser();
+ toDoNext = &typeDecl;
+ previousParser = &typeDecl;
+ break;
+
+ case OcaKEYWORD_class:
+ cleanupPreviousParser();
+ toDoNext = &classDecl;
+ previousParser = &classDecl;
+ break;
+
+ case OcaKEYWORD_module:
+ cleanupPreviousParser();
+ toDoNext = &moduleDecl;
+ previousParser = &moduleDecl;
+ break;
+
+ case OcaKEYWORD_end:
+ needStrongPoping = FALSE;
+ killCurrentState();
+ /*popStrongContext(); */
+ break;
+
+ case OcaKEYWORD_method:
+ cleanupPreviousParser();
+ toDoNext = &methodDecl;
+ /* and is not allowed in methods */
+ break;
+
+ /* val is mixed with let as global
+ * to be able to handle mli & new syntax */
+ case OcaKEYWORD_val:
+ case OcaKEYWORD_value:
+ case OcaKEYWORD_let:
+ cleanupPreviousParser();
+ toDoNext = &globalLet;
+ previousParser = &globalLet;
+ break;
+
+ case OcaKEYWORD_exception:
+ cleanupPreviousParser();
+ toDoNext = &exceptionDecl;
+ previousParser = &globalScope;
+ break;
+
+ /* must be a #line directive, discard the
+ * whole line. */
+ case Tok_Sharp:
+ /* ignore */
+ break;
+
+ default:
+ /* we don't care */
+ break;
+ }
+}
+
+/* Parse expression. Well ignore it is more the case,
+ * ignore all tokens except "shocking" keywords */
+static void localScope(vString *const ident, ocaToken what) {
+ switch (what) {
+ case Tok_Pipe:
+ case Tok_PARR:
+ case Tok_BRR:
+ case Tok_CurlR:
+ popSoftContext();
+ break;
+
+ /* Everything that `begin` has an `end`
+ * as end is overloaded and signal many end
+ * of things, we add an empty strong context to
+ * avoid problem with the end.
+ */
+ case OcaKEYWORD_begin:
+ pushContext(ContextStrong, ContextBlock, &mayRedeclare, NULL);
+ toDoNext = &mayRedeclare;
+ break;
+
+ case OcaKEYWORD_in:
+ popLastNamed();
+ break;
+
+ /* Ok, we got a '{', which is much likely to create
+ * a record. We cannot treat it like other [ && (,
+ * because it may contain the 'with' keyword and screw
+ * everything else. */
+ case Tok_CurlL:
+ toDoNext = &contextualTillToken;
+ waitedToken = Tok_CurlR;
+ comeAfter = &localScope;
+ contextualTillToken(ident, what);
+ break;
+
+ /* Yeah imperative feature of OCaml,
+ * a ';' like in C */
+ case Tok_semi:
+ toDoNext = &mayRedeclare;
+ break;
+
+ case Tok_PARL:
+ case Tok_BRL:
+ pushEmptyContext(&localScope);
+ toDoNext = &mayRedeclare;
+ break;
+
+ case OcaKEYWORD_and:
+ popLastNamed();
+ toDoNext = &localLet;
+ break;
+
+ case OcaKEYWORD_else:
+ case OcaKEYWORD_then:
+ popSoftContext();
+ pushEmptyContext(&localScope);
+ toDoNext = &mayRedeclare;
+ break;
+
+ case OcaKEYWORD_if:
+ pushEmptyContext(&localScope);
+ toDoNext = &mayRedeclare;
+ break;
+
+ case OcaKEYWORD_match:
+ pushEmptyContext(&localScope);
+ toDoNext = &mayRedeclare;
+ break;
+
+ case OcaKEYWORD_with:
+ popSoftContext();
+ toDoNext = &matchPattern;
+ pushEmptyContext(&matchPattern);
+ break;
+
+ case OcaKEYWORD_end:
+ killCurrentState();
+ break;
+
+ case OcaKEYWORD_fun:
+ comeAfter = &mayRedeclare;
+ toDoNext = &tillToken;
+ waitedToken = Tok_To;
+ break;
+
+ case OcaKEYWORD_done:
+ case OcaKEYWORD_val:
+ /* doesn't care */
+ break;
+
+ default:
+ requestStrongPoping();
+ globalScope(ident, what);
+ break;
+ }
+}
+
+/*////////////////////////////////////////////////////////////////
+//// Deal with the system */
+/* in OCaml the file name is the module name used in the language
+ * with it first letter put in upper case */
+static void computeModuleName(void) {
+ /* in Ocaml the file name define a module.
+ * so we define a module =)
+ */
+ const char *filename = getSourceFileName();
+ int beginIndex = 0;
+ int endIndex = strlen(filename) - 1;
+ vString *moduleName = vStringNew();
+
+ while (filename[endIndex] != '.' && endIndex > 0) endIndex--;
+
+ /* avoid problem with path in front of filename */
+ beginIndex = endIndex;
+ while (beginIndex > 0) {
+ if (filename[beginIndex] == '\\' || filename[beginIndex] == '/') {
+ beginIndex++;
+ break;
+ }
+
+ beginIndex--;
+ }
+
+ vStringNCopyS(moduleName, &filename[beginIndex], endIndex - beginIndex);
+ vStringTerminate(moduleName);
+
+ if (isLowerAlpha(moduleName->buffer[0])) moduleName->buffer[0] += ('A' - 'a');
+
+ addTag(moduleName, K_MODULE);
+ vStringDelete(moduleName);
+}
+
+/* Allocate all string of the context stack */
+static void initStack(void) {
+ int i;
+ for (i = 0; i < OCAML_MAX_STACK_SIZE; ++i)
+ stack[i].contextName = vStringNew();
+ stackIndex = 0;
+}
+
+static void clearStack(void) {
+ int i;
+ for (i = 0; i < OCAML_MAX_STACK_SIZE; ++i)
+ vStringDelete(stack[i].contextName);
+}
+
+static void findOcamlTags(void) {
+ vString *name = vStringNew();
+ lexingState st;
+ ocaToken tok;
+
+ initStack();
+ computeModuleName();
+ tempIdent = vStringNew();
+ lastModule = vStringNew();
+ lastClass = vStringNew();
+ voidName = vStringNew();
+ vStringCopyS(voidName, "_");
+
+ st.name = vStringNew();
+ st.cp = fileReadLine();
+ toDoNext = &globalScope;
+ tok = lex(&st);
+ while (tok != Tok_EOF) {
+ (*toDoNext)(st.name, tok);
+ tok = lex(&st);
+ }
+
+ vStringDelete(name);
+ vStringDelete(voidName);
+ vStringDelete(tempIdent);
+ vStringDelete(lastModule);
+ vStringDelete(lastClass);
+ clearStack();
+}
+
+static void ocamlInitialize(const langType language) {
+ Lang_Ocaml = language;
+
+ initOperatorTable();
+ initKeywordHash();
+}
+
+extern parserDefinition *OcamlParser(void) {
+ static const char *const extensions[] = {"ml", "mli", NULL};
+ parserDefinition *def = parserNew("OCaml");
+ def->kinds = OcamlKinds;
+ def->kindCount = KIND_COUNT(OcamlKinds);
+ def->extensions = extensions;
+ def->parser = findOcamlTags;
+ def->initialize = ocamlInitialize;
+
+ return def;
+}
diff --git a/third_party/ctags/options.c b/third_party/ctags/options.c
new file mode 100644
index 000000000..47c4e56ac
--- /dev/null
+++ b/third_party/ctags/options.c
@@ -0,0 +1,1669 @@
+/*
+ * $Id: options.c 576 2007-06-30 04:16:23Z elliotth $
+ *
+ * Copyright (c) 1996-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions to process command line options.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/ctags.h"
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/main.h"
+#define OPTION_WRITE
+#include "libc/fmt/fmt.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/routines.h"
+
+/*
+ * MACROS
+ */
+#define INVOCATION "Usage: %s [options] [file(s)]\n"
+
+#define CTAGS_ENVIRONMENT "CTAGS"
+#define ETAGS_ENVIRONMENT "ETAGS"
+
+#define CTAGS_FILE "tags"
+#define ETAGS_FILE "TAGS"
+
+#ifndef ETAGS
+#define ETAGS "etags" /* name which causes default use of to -e */
+#endif
+
+/* The following separators are permitted for list options.
+ */
+#define EXTENSION_SEPARATOR '.'
+#define PATTERN_START '('
+#define PATTERN_STOP ')'
+#define IGNORE_SEPARATORS ", \t\n"
+
+#ifndef DEFAULT_FILE_FORMAT
+#define DEFAULT_FILE_FORMAT 2
+#endif
+
+#if defined(HAVE_OPENDIR) || defined(HAVE_FINDFIRST) || \
+ defined(HAVE__FINDFIRST) || defined(AMIGA)
+#define RECURSE_SUPPORTED
+#endif
+
+#define isCompoundOption(c) (boolean)(strchr("fohiILpDb", (c)) != NULL)
+
+/*
+ * Data declarations
+ */
+
+enum eOptionLimits {
+ MaxHeaderExtensions = 100, /* maximum number of extensions in -h option */
+ MaxSupportedTagFormat = 2
+};
+
+typedef struct sOptionDescription {
+ int usedByEtags;
+ const char *description;
+} optionDescription;
+
+typedef void (*parametricOptionHandler)(const char *const option,
+ const char *const parameter);
+
+typedef const struct {
+ const char *name; /* name of option as specified by user */
+ parametricOptionHandler handler; /* routine to handle option */
+ boolean initOnly; /* option must be specified before any files */
+} parametricOption;
+
+typedef const struct {
+ const char *name; /* name of option as specified by user */
+ boolean *pValue; /* pointer to option value */
+ boolean initOnly; /* option must be specified before any files */
+} booleanOption;
+
+/*
+ * DATA DEFINITIONS
+ */
+
+static boolean NonOptionEncountered;
+static stringList *OptionFiles;
+static stringList *Excluded;
+static boolean FilesRequired = TRUE;
+static boolean SkipConfiguration;
+
+static const char *const HeaderExtensions[] = {
+ "h", "H", "hh", "hpp", "hxx", "h++", "inc", "def", NULL};
+
+optionValues Option = {
+ {
+ FALSE, /* --extra=f */
+ FALSE, /* --extra=q */
+ TRUE, /* --file-scope */
+ },
+ {
+ FALSE, /* -fields=a */
+ TRUE, /* -fields=f */
+ FALSE, /* -fields=m */
+ FALSE, /* -fields=i */
+ TRUE, /* -fields=k */
+ FALSE, /* -fields=z */
+ FALSE, /* -fields=K */
+ FALSE, /* -fields=l */
+ FALSE, /* -fields=n */
+ TRUE, /* -fields=s */
+ FALSE, /* -fields=S */
+ TRUE /* -fields=t */
+ },
+ NULL, /* -I */
+ FALSE, /* -a */
+ FALSE, /* -B */
+ FALSE, /* -e */
+#ifdef MACROS_USE_PATTERNS
+ EX_PATTERN, /* -n, --excmd */
+#else
+ EX_MIX, /* -n, --excmd */
+#endif
+ FALSE, /* -R */
+ SO_SORTED, /* -u, --sort */
+ FALSE, /* -V */
+ FALSE, /* -x */
+ NULL, /* -L */
+ NULL, /* -o */
+ NULL, /* -h */
+ NULL, /* --etags-include */
+ DEFAULT_FILE_FORMAT, /* --format */
+ FALSE, /* --if0 */
+ FALSE, /* --kind-long */
+ LANG_AUTO, /* --lang */
+ TRUE, /* --links */
+ FALSE, /* --filter */
+ NULL, /* --filter-terminator */
+ FALSE, /* --tag-relative */
+ FALSE, /* --totals */
+ FALSE, /* --line-directives */
+#ifdef DEBUG
+ 0,
+ 0 /* -D, -b */
+#endif
+};
+
+/*
+- Locally used only
+*/
+
+static optionDescription LongOptionDescription[] = {
+ {1, " -a Append the tags to an existing tag file."},
+#ifdef DEBUG
+ {1, " -b "},
+ {1, " Set break line."},
+#endif
+ {0, " -B Use backward searching patterns (?...?)."},
+#ifdef DEBUG
+ {1, " -D "},
+ {1, " Set debug level."},
+#endif
+ {0, " -e Output tag file for use with Emacs."},
+ {1, " -f "},
+ {1, " Write tags to specified file. Value of \"-\" writes tags to "
+ "stdout"},
+ {1, " [\"tags\"; or \"TAGS\" when -e supplied]."},
+ {0, " -F Use forward searching patterns (/.../) (default)."},
+ {1, " -h "},
+ {1,
+ " Specify list of file extensions to be treated as include files."},
+ {1, " [\".h.H.hh.hpp.hxx.h++\"]."},
+ {1, " -I "},
+ {1,
+ " A list of tokens to be specially handled is read from either the"},
+ {1, " command line or the specified file."},
+ {1, " -L "},
+ {1, " A list of source file names are read from the specified file."},
+ {1, " If specified as \"-\", then standard input is read."},
+ {0, " -n Equivalent to --excmd=number."},
+ {0, " -N Equivalent to --excmd=pattern."},
+ {1, " -o Alternative for -f."},
+#ifdef RECURSE_SUPPORTED
+ {1, " -R Equivalent to --recurse."},
+#else
+ {1, " -R Not supported on this platform."},
+#endif
+ {0, " -u Equivalent to --sort=no."},
+ {1, " -V Equivalent to --verbose."},
+ {1, " -x Print a tabular cross reference file to standard output."},
+ {1, " --append=[yes|no]"},
+ {1, " Should tags should be appended to existing tag file [no]?"},
+ {1, " --etags-include=file"},
+ {1, " Include reference to 'file' in Emacs-style tag file (requires "
+ "-e)."},
+ {1, " --exclude=pattern"},
+ {1, " Exclude files and directories matching 'pattern'."},
+ {0, " --excmd=number|pattern|mix"},
+#ifdef MACROS_USE_PATTERNS
+ {0,
+ " Uses the specified type of EX command to locate tags [pattern]."},
+#else
+ {0, " Uses the specified type of EX command to locate tags [mix]."},
+#endif
+ {1, " --extra=[+|-]flags"},
+ {1, " Include extra tag entries for selected information (flags: "
+ "\"fq\")."},
+ {1, " --fields=[+|-]flags"},
+ {1, " Include selected extension fields (flags: \"afmikKlnsStz\") "
+ "[fks]."},
+ {1, " --file-scope=[yes|no]"},
+ {1,
+ " Should tags scoped only for a single file (e.g. \"static\" tags"},
+ {1, " be included in the output [yes]?"},
+ {1, " --filter=[yes|no]"},
+ {1,
+ " Behave as a filter, reading file names from standard input and"},
+ {1, " writing tags to standard output [no]."},
+ {1, " --filter-terminator=string"},
+ {1, " Specify string to print to stdout following the tags for each "
+ "file"},
+ {1, " parsed when --filter is enabled."},
+ {0, " --format=level"},
+#if DEFAULT_FILE_FORMAT == 1
+ {0, " Force output of specified tag file format [1]."},
+#else
+ {0, " Force output of specified tag file format [2]."},
+#endif
+ {1, " --help"},
+ {1, " Print this option summary."},
+ {1, " --if0=[yes|no]"},
+ {1,
+ " Should C code within #if 0 conditional branches be parsed [no]?"},
+ {1, " ---kinds=[+|-]kinds"},
+ {1, " Enable/disable tag kinds for language ."},
+ {1, " --langdef=name"},
+ {1, " Define a new language to be parsed with regular expressions."},
+ {1, " --langmap=map(s)"},
+ {1,
+ " Override default mapping of language to source file extension."},
+ {1, " --language-force=language"},
+ {1, " Force all files to be interpreted using specified language."},
+ {1, " --languages=[+|-]list"},
+ {1, " Restrict files scanned for tags to those mapped to langauges"},
+ {1, " specified in the comma-separated 'list'. The list can contain "
+ "any"},
+ {1, " built-in or user-defined language [all]."},
+ {1, " --license"},
+ {1, " Print details of software license."},
+ {0, " --line-directives=[yes|no]"},
+ {0, " Should #line directives be processed [no]?"},
+ {1, " --links=[yes|no]"},
+ {1, " Indicate whether symbolic links should be followed [yes]."},
+ {1, " --list-kinds=[language|all]"},
+ {1, " Output a list of all tag kinds for specified language or all."},
+ {1, " --list-languages"},
+ {1, " Output list of supported languages."},
+ {1, " --list-maps=[language|all]"},
+ {1, " Output list of language mappings."},
+ {1, " --options=file"},
+ {1, " Specify file from which command line options should be read."},
+ {1, " --recurse=[yes|no]"},
+#ifdef RECURSE_SUPPORTED
+ {1, " Recurse into directories supplied on command line [no]."},
+#else
+ {1, " Not supported on this platform."},
+#endif
+#ifdef HAVE_REGEX
+ {1, " --regex-=/line_pattern/name_pattern/[flags]"},
+ {1, " Define regular expression for locating tags in specific "
+ "language."},
+#endif
+ {0, " --sort=[yes|no|foldcase]"},
+ {0, " Should tags be sorted (optionally ignoring case) [yes]?."},
+ {0, " --tag-relative=[yes|no]"},
+ {0, " Should paths be relative to location of tag file [no; yes when "
+ "-e]?"},
+ {1, " --totals=[yes|no]"},
+ {1, " Print statistics about source and tag files [no]."},
+ {1, " --verbose=[yes|no]"},
+ {1,
+ " Enable verbose messages describing actions on each source file."},
+ {1, " --version"},
+ {1, " Print version identifier to standard output."},
+ {1, NULL}};
+
+static const char *const License1 =
+ "This program is free software; you can redistribute it and/or\n"
+ "modify it under the terms of the GNU General Public License\n"
+ "as published by the Free Software Foundation; either version 2\n"
+ "of the License, or (at your option) any later version.\n"
+ "\n";
+static const char *const License2 =
+ "This program is distributed in the hope that it will be useful,\n"
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
+ "GNU General Public License for more details.\n"
+ "\n"
+ "You should have received a copy of the GNU General Public License\n"
+ "along with this program; if not, write to the Free Software\n"
+ "Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, "
+ "USA.\n";
+
+/* Contains a set of strings describing the set of "features" compiled into
+ * the code.
+ */
+static const char *const Features[] = {
+#ifdef WIN32
+ "win32",
+#endif
+#ifdef DJGPP
+ "msdos_32",
+#else
+#ifdef MSDOS
+ "msdos_16",
+#endif
+#endif
+#ifdef OS2
+ "os2",
+#endif
+#ifdef AMIGA
+ "amiga",
+#endif
+#ifdef VMS
+ "vms",
+#endif
+#ifdef HAVE_FNMATCH
+ "wildcards",
+#endif
+#ifdef HAVE_REGEX
+ "regex",
+#endif
+#ifndef EXTERNAL_SORT
+ "internal-sort",
+#endif
+#ifdef CUSTOM_CONFIGURATION_FILE
+ "custom-conf",
+#endif
+#if (defined(MSDOS) || defined(WIN32) || defined(OS2)) && \
+ defined(UNIX_PATH_SEPARATOR)
+ "unix-path-separator",
+#endif
+#ifdef DEBUG
+ "debug",
+#endif
+ NULL};
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+static boolean parseFileOptions(const char *const fileName);
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+extern void verbose(const char *const format, ...) {
+ if (Option.verbose) {
+ va_list ap;
+ va_start(ap, format);
+ vprintf(format, ap);
+ va_end(ap);
+ }
+}
+
+static char *stringCopy(const char *const string) {
+ char *result = NULL;
+ if (string != NULL) result = eStrdup(string);
+ return result;
+}
+
+static void freeString(char **const pString) {
+ if (*pString != NULL) {
+ eFree(*pString);
+ *pString = NULL;
+ }
+}
+
+extern void freeList(stringList **const pList) {
+ if (*pList != NULL) {
+ stringListDelete(*pList);
+ *pList = NULL;
+ }
+}
+
+extern void setDefaultTagFileName(void) {
+ if (Option.tagFileName != NULL)
+ ; /* accept given name */
+ else if (Option.etags)
+ Option.tagFileName = stringCopy(ETAGS_FILE);
+ else
+ Option.tagFileName = stringCopy(CTAGS_FILE);
+}
+
+extern boolean filesRequired(void) {
+ boolean result = FilesRequired;
+ if (Option.recurse) result = FALSE;
+ return result;
+}
+
+extern void checkOptions(void) {
+ const char *notice;
+ if (Option.xref) {
+ notice = "xref output";
+ if (Option.include.fileNames) {
+ error(WARNING, "%s disables file name tags", notice);
+ Option.include.fileNames = FALSE;
+ }
+ }
+ if (Option.append) {
+ notice = "append mode is not compatible with";
+ if (isDestinationStdout()) error(FATAL, "%s tags to stdout", notice);
+ }
+ if (Option.filter) {
+ notice = "filter mode";
+ if (Option.printTotals) {
+ error(WARNING, "%s disables totals", notice);
+ Option.printTotals = FALSE;
+ }
+ if (Option.tagFileName != NULL)
+ error(WARNING, "%s ignores output tag file name", notice);
+ }
+}
+
+static void setEtagsMode(void) {
+ Option.etags = TRUE;
+ Option.sorted = SO_UNSORTED;
+ Option.lineDirectives = FALSE;
+ Option.tagRelative = TRUE;
+}
+
+extern void testEtagsInvocation(void) {
+ char *const execName = eStrdup(getExecutableName());
+ char *const etags = eStrdup(ETAGS);
+#ifdef CASE_INSENSITIVE_FILENAMES
+ toLowerString(execName);
+ toLowerString(etags);
+#endif
+ if (strstr(execName, etags) != NULL) {
+ verbose("Running in etags mode\n");
+ setEtagsMode();
+ }
+ eFree(execName);
+ eFree(etags);
+}
+
+/*
+ * Cooked argument parsing
+ */
+
+static void parseShortOption(cookedArgs *const args) {
+ args->simple[0] = *args->shortOptions++;
+ args->simple[1] = '\0';
+ args->item = args->simple;
+ if (!isCompoundOption(*args->simple))
+ args->parameter = "";
+ else if (*args->shortOptions == '\0') {
+ argForth(args->args);
+ if (argOff(args->args))
+ args->parameter = NULL;
+ else
+ args->parameter = argItem(args->args);
+ args->shortOptions = NULL;
+ } else {
+ args->parameter = args->shortOptions;
+ args->shortOptions = NULL;
+ }
+}
+
+static void parseLongOption(cookedArgs *const args, const char *item) {
+ const char *const equal = strchr(item, '=');
+ if (equal == NULL) {
+ args->item = eStrdup(item); /* FIXME: memory leak. */
+ args->parameter = "";
+ } else {
+ const size_t length = equal - item;
+ args->item = xMalloc(length + 1, char); /* FIXME: memory leak. */
+ strncpy(args->item, item, length);
+ args->item[length] = '\0';
+ args->parameter = equal + 1;
+ }
+ Assert(args->item != NULL);
+ Assert(args->parameter != NULL);
+}
+
+static void cArgRead(cookedArgs *const current) {
+ char *item;
+
+ Assert(current != NULL);
+ if (!argOff(current->args)) {
+ item = argItem(current->args);
+ current->shortOptions = NULL;
+ Assert(item != NULL);
+ if (strncmp(item, "--", (size_t)2) == 0) {
+ current->isOption = TRUE;
+ current->longOption = TRUE;
+ parseLongOption(current, item + 2);
+ Assert(current->item != NULL);
+ Assert(current->parameter != NULL);
+ } else if (*item == '-') {
+ current->isOption = TRUE;
+ current->longOption = FALSE;
+ current->shortOptions = item + 1;
+ parseShortOption(current);
+ } else {
+ current->isOption = FALSE;
+ current->longOption = FALSE;
+ current->item = item;
+ current->parameter = NULL;
+ }
+ }
+}
+
+extern cookedArgs *cArgNewFromString(const char *string) {
+ cookedArgs *const result = xMalloc(1, cookedArgs);
+ memset(result, 0, sizeof(cookedArgs));
+ result->args = argNewFromString(string);
+ cArgRead(result);
+ return result;
+}
+
+extern cookedArgs *cArgNewFromArgv(char *const *const argv) {
+ cookedArgs *const result = xMalloc(1, cookedArgs);
+ memset(result, 0, sizeof(cookedArgs));
+ result->args = argNewFromArgv(argv);
+ cArgRead(result);
+ return result;
+}
+
+extern cookedArgs *cArgNewFromFile(FILE *const fp) {
+ cookedArgs *const result = xMalloc(1, cookedArgs);
+ memset(result, 0, sizeof(cookedArgs));
+ result->args = argNewFromFile(fp);
+ cArgRead(result);
+ return result;
+}
+
+extern cookedArgs *cArgNewFromLineFile(FILE *const fp) {
+ cookedArgs *const result = xMalloc(1, cookedArgs);
+ memset(result, 0, sizeof(cookedArgs));
+ result->args = argNewFromLineFile(fp);
+ cArgRead(result);
+ return result;
+}
+
+extern void cArgDelete(cookedArgs *const current) {
+ Assert(current != NULL);
+ argDelete(current->args);
+ memset(current, 0, sizeof(cookedArgs));
+ eFree(current);
+}
+
+static boolean cArgOptionPending(cookedArgs *const current) {
+ boolean result = FALSE;
+ if (current->shortOptions != NULL)
+ if (*current->shortOptions != '\0') result = TRUE;
+ return result;
+}
+
+extern boolean cArgOff(cookedArgs *const current) {
+ Assert(current != NULL);
+ return (boolean)(argOff(current->args) && !cArgOptionPending(current));
+}
+
+extern boolean cArgIsOption(cookedArgs *const current) {
+ Assert(current != NULL);
+ return current->isOption;
+}
+
+extern const char *cArgItem(cookedArgs *const current) {
+ Assert(current != NULL);
+ return current->item;
+}
+
+extern void cArgForth(cookedArgs *const current) {
+ Assert(current != NULL);
+ Assert(!cArgOff(current));
+ if (cArgOptionPending(current))
+ parseShortOption(current);
+ else {
+ Assert(!argOff(current->args));
+ argForth(current->args);
+ if (!argOff(current->args))
+ cArgRead(current);
+ else {
+ current->isOption = FALSE;
+ current->longOption = FALSE;
+ current->shortOptions = NULL;
+ current->item = NULL;
+ current->parameter = NULL;
+ }
+ }
+}
+
+/*
+ * File extension and language mapping
+ */
+
+static void addExtensionList(stringList *const slist, const char *const elist,
+ const boolean clear) {
+ char *const extensionList = eStrdup(elist);
+ const char *extension = NULL;
+ boolean first = TRUE;
+
+ if (clear) {
+ verbose(" clearing\n");
+ stringListClear(slist);
+ }
+ verbose(" adding: ");
+ if (elist != NULL && *elist != '\0') {
+ extension = extensionList;
+ if (elist[0] == EXTENSION_SEPARATOR) ++extension;
+ }
+ while (extension != NULL) {
+ char *separator = strchr(extension, EXTENSION_SEPARATOR);
+ if (separator != NULL) *separator = '\0';
+ verbose("%s%s", first ? "" : ", ",
+ *extension == '\0' ? "(NONE)" : extension);
+ stringListAdd(slist, vStringNewInit(extension));
+ first = FALSE;
+ if (separator == NULL)
+ extension = NULL;
+ else
+ extension = separator + 1;
+ }
+ if (Option.verbose) {
+ printf("\n now: ");
+ stringListPrint(slist);
+ putchar('\n');
+ }
+ eFree(extensionList);
+}
+
+static boolean isFalse(const char *parameter) {
+ return (boolean)(
+ strcasecmp(parameter, "0") == 0 || strcasecmp(parameter, "n") == 0 ||
+ strcasecmp(parameter, "no") == 0 || strcasecmp(parameter, "off") == 0);
+}
+
+static boolean isTrue(const char *parameter) {
+ return (boolean)(
+ strcasecmp(parameter, "1") == 0 || strcasecmp(parameter, "y") == 0 ||
+ strcasecmp(parameter, "yes") == 0 || strcasecmp(parameter, "on") == 0);
+}
+
+/* Determines whether the specified file name is considered to be a header
+ * file for the purposes of determining whether enclosed tags are global or
+ * static.
+ */
+extern boolean isIncludeFile(const char *const fileName) {
+ boolean result = FALSE;
+ const char *const extension = fileExtension(fileName);
+ if (Option.headerExt != NULL)
+ result = stringListExtensionMatched(Option.headerExt, extension);
+ return result;
+}
+
+/*
+ * Specific option processing
+ */
+
+static void processEtagsInclude(const char *const option,
+ const char *const parameter) {
+ if (!Option.etags)
+ error(FATAL, "Etags must be enabled to use \"%s\" option", option);
+ else {
+ vString *const file = vStringNewInit(parameter);
+ if (Option.etagsInclude == NULL) Option.etagsInclude = stringListNew();
+ stringListAdd(Option.etagsInclude, file);
+ FilesRequired = FALSE;
+ }
+}
+
+static void processExcludeOption(const char *const option __unused__,
+ const char *const parameter) {
+ const char *const fileName = parameter + 1;
+ if (parameter[0] == '\0')
+ freeList(&Excluded);
+ else if (parameter[0] == '@') {
+ stringList *const sl = stringListNewFromFile(fileName);
+ if (sl == NULL) error(FATAL | PERROR, "cannot open \"%s\"", fileName);
+ if (Excluded == NULL)
+ Excluded = sl;
+ else
+ stringListCombine(Excluded, sl);
+ verbose(" adding exclude patterns from %s\n", fileName);
+ } else {
+ vString *const item = vStringNewInit(parameter);
+ if (Excluded == NULL) Excluded = stringListNew();
+ stringListAdd(Excluded, item);
+ verbose(" adding exclude pattern: %s\n", parameter);
+ }
+}
+
+extern boolean isExcludedFile(const char *const name) {
+ const char *base = baseFilename(name);
+ boolean result = FALSE;
+ if (Excluded != NULL) {
+ result = stringListFileMatched(Excluded, base);
+ if (!result && name != base) result = stringListFileMatched(Excluded, name);
+ }
+#ifdef AMIGA
+ /* not a good solution, but the only one which works often */
+ if (!result) result = (boolean)(strcmp(name, TagFile.name) == 0);
+#endif
+ return result;
+}
+
+static void processExcmdOption(const char *const option,
+ const char *const parameter) {
+ switch (*parameter) {
+ case 'm':
+ Option.locate = EX_MIX;
+ break;
+ case 'n':
+ Option.locate = EX_LINENUM;
+ break;
+ case 'p':
+ Option.locate = EX_PATTERN;
+ break;
+ default:
+ error(FATAL, "Invalid value for \"%s\" option", option);
+ break;
+ }
+}
+
+static void processExtraTagsOption(const char *const option,
+ const char *const parameter) {
+ struct sInclude *const inc = &Option.include;
+ const char *p = parameter;
+ boolean mode = TRUE;
+ int c;
+
+ if (*p != '+' && *p != '-') {
+ inc->fileNames = FALSE;
+ inc->qualifiedTags = FALSE;
+#if 0
+ inc->fileScope = FALSE;
+#endif
+ }
+ while ((c = *p++) != '\0') switch (c) {
+ case '+':
+ mode = TRUE;
+ break;
+ case '-':
+ mode = FALSE;
+ break;
+
+ case 'f':
+ inc->fileNames = mode;
+ break;
+ case 'q':
+ inc->qualifiedTags = mode;
+ break;
+#if 0
+ case 'F': inc->fileScope = mode; break;
+#endif
+
+ default:
+ error(WARNING, "Unsupported parameter '%c' for \"%s\" option", c,
+ option);
+ break;
+ }
+}
+
+static void processFieldsOption(const char *const option,
+ const char *const parameter) {
+ struct sExtFields *field = &Option.extensionFields;
+ const char *p = parameter;
+ boolean mode = TRUE;
+ int c;
+
+ if (*p != '+' && *p != '-') {
+ field->access = FALSE;
+ field->fileScope = FALSE;
+ field->implementation = FALSE;
+ field->inheritance = FALSE;
+ field->kind = FALSE;
+ field->kindKey = FALSE;
+ field->kindLong = FALSE;
+ field->language = FALSE;
+ field->scope = FALSE;
+ field->typeRef = FALSE;
+ }
+ while ((c = *p++) != '\0') switch (c) {
+ case '+':
+ mode = TRUE;
+ break;
+ case '-':
+ mode = FALSE;
+ break;
+
+ case 'a':
+ field->access = mode;
+ break;
+ case 'f':
+ field->fileScope = mode;
+ break;
+ case 'm':
+ field->implementation = mode;
+ break;
+ case 'i':
+ field->inheritance = mode;
+ break;
+ case 'k':
+ field->kind = mode;
+ break;
+ case 'K':
+ field->kindLong = mode;
+ break;
+ case 'l':
+ field->language = mode;
+ break;
+ case 'n':
+ field->lineNumber = mode;
+ break;
+ case 's':
+ field->scope = mode;
+ break;
+ case 'S':
+ field->signature = mode;
+ break;
+ case 'z':
+ field->kindKey = mode;
+ break;
+ case 't':
+ field->typeRef = mode;
+ break;
+
+ default:
+ error(WARNING, "Unsupported parameter '%c' for \"%s\" option", c,
+ option);
+ break;
+ }
+}
+
+static void processFilterTerminatorOption(const char *const option __unused__,
+ const char *const parameter) {
+ freeString(&Option.filterTerminator);
+ Option.filterTerminator = stringCopy(parameter);
+}
+
+static void processFormatOption(const char *const option,
+ const char *const parameter) {
+ unsigned int format;
+
+ if (sscanf(parameter, "%u", &format) < 1)
+ error(FATAL, "Invalid value for \"%s\" option", option);
+ else if (format <= (unsigned int)MaxSupportedTagFormat)
+ Option.tagFileFormat = format;
+ else
+ error(FATAL, "Unsupported value for \"%s\" option", option);
+}
+
+static void printInvocationDescription(void) {
+ printf(INVOCATION, getExecutableName());
+}
+
+static void printOptionDescriptions(const optionDescription *const optDesc) {
+ int i;
+ for (i = 0; optDesc[i].description != NULL; ++i) {
+ if (!Option.etags || optDesc[i].usedByEtags) puts(optDesc[i].description);
+ }
+}
+
+static void printFeatureList(void) {
+ int i;
+
+ for (i = 0; Features[i] != NULL; ++i) {
+ if (i == 0) printf(" Optional compiled features: ");
+ printf("%s+%s", (i > 0 ? ", " : ""), Features[i]);
+#ifdef CUSTOM_CONFIGURATION_FILE
+ if (strcmp(Features[i], "custom-conf") == 0)
+ printf("=%s", CUSTOM_CONFIGURATION_FILE);
+#endif
+ }
+ if (i > 0) putchar('\n');
+}
+
+static void printProgramIdentification(void) {
+ printf("%s %s, %s %s\n", PROGRAM_NAME, PROGRAM_VERSION, PROGRAM_COPYRIGHT,
+ AUTHOR_NAME);
+ printf(" Addresses: <%s>, %s\n", AUTHOR_EMAIL, PROGRAM_URL);
+ printFeatureList();
+}
+
+static void processHelpOption(const char *const option __unused__,
+ const char *const parameter __unused__) {
+ printProgramIdentification();
+ putchar('\n');
+ printInvocationDescription();
+ putchar('\n');
+ printOptionDescriptions(LongOptionDescription);
+ exit(0);
+}
+
+static void processLanguageForceOption(const char *const option,
+ const char *const parameter) {
+ langType language;
+ if (strcasecmp(parameter, "auto") == 0)
+ language = LANG_AUTO;
+ else
+ language = getNamedLanguage(parameter);
+
+ if (strcmp(option, "lang") == 0 || strcmp(option, "language") == 0)
+ error(WARNING,
+ "\"--%s\" option is obsolete; use \"--language-force\" instead",
+ option);
+ if (language == LANG_IGNORE)
+ error(FATAL, "Unknown language \"%s\" in \"%s\" option", parameter, option);
+ else
+ Option.language = language;
+}
+static char *skipPastMap(char *p) {
+ while (*p != EXTENSION_SEPARATOR && *p != PATTERN_START && *p != ',' &&
+ *p != '\0')
+ ++p;
+ return p;
+}
+
+/* Parses the mapping beginning at `map', adds it to the language map, and
+ * returns first character past the map.
+ */
+static char *addLanguageMap(const langType language, char *map) {
+ char *p = NULL;
+ const char first = *map;
+ if (first == EXTENSION_SEPARATOR) /* extension map */
+ {
+ ++map;
+ p = skipPastMap(map);
+ if (*p == '\0') {
+ verbose(" .%s", map);
+ addLanguageExtensionMap(language, map);
+ p = map + strlen(map);
+ } else {
+ const char separator = *p;
+ *p = '\0';
+ verbose(" .%s", map);
+ addLanguageExtensionMap(language, map);
+ *p = separator;
+ }
+ } else if (first == PATTERN_START) /* pattern map */
+ {
+ ++map;
+ for (p = map; *p != PATTERN_STOP && *p != '\0'; ++p) {
+ if (*p == '\\' && *(p + 1) == PATTERN_STOP) ++p;
+ }
+ if (*p == '\0')
+ error(FATAL, "Unterminated file name pattern for %s language",
+ getLanguageName(language));
+ else {
+ *p++ = '\0';
+ verbose(" (%s)", map);
+ addLanguagePatternMap(language, map);
+ }
+ } else
+ error(FATAL, "Badly formed language map for %s language",
+ getLanguageName(language));
+ return p;
+}
+
+static char *processLanguageMap(char *map) {
+ char *const separator = strchr(map, ':');
+ char *result = NULL;
+ if (separator != NULL) {
+ langType language;
+ char *list = separator + 1;
+ boolean clear = FALSE;
+ *separator = '\0';
+ language = getNamedLanguage(map);
+ if (language != LANG_IGNORE) {
+ const char *const deflt = "default";
+ char *p;
+ if (*list == '+')
+ ++list;
+ else
+ clear = TRUE;
+ for (p = list; *p != ',' && *p != '\0'; ++p) /*no-op*/
+ ;
+ if ((size_t)(p - list) == strlen(deflt) &&
+ strncasecmp(list, deflt, p - list) == 0) {
+ verbose(" Restoring default %s language map: ",
+ getLanguageName(language));
+ installLanguageMapDefault(language);
+ list = p;
+ } else {
+ if (clear) {
+ verbose(" Setting %s language map:", getLanguageName(language));
+ clearLanguageMap(language);
+ } else
+ verbose(" Adding to %s language map:", getLanguageName(language));
+ while (list != NULL && *list != '\0' && *list != ',')
+ list = addLanguageMap(language, list);
+ verbose("\n");
+ }
+ if (list != NULL && *list == ',') ++list;
+ result = list;
+ }
+ }
+ return result;
+}
+
+static void processLanguageMapOption(const char *const option,
+ const char *const parameter) {
+ char *const maps = eStrdup(parameter);
+ char *map = maps;
+
+ if (strcmp(parameter, "default") == 0) {
+ verbose(" Restoring default language maps:\n");
+ installLanguageMapDefaults();
+ } else
+ while (map != NULL && *map != '\0') {
+ char *const next = processLanguageMap(map);
+ if (next == NULL)
+ error(WARNING, "Unknown language \"%s\" in \"%s\" option", parameter,
+ option);
+ map = next;
+ }
+ eFree(maps);
+}
+
+static void processLanguagesOption(const char *const option,
+ const char *const parameter) {
+ char *const langs = eStrdup(parameter);
+ enum { Add, Remove, Replace } mode = Replace;
+ boolean first = TRUE;
+ char *lang = langs;
+ const char *prefix = "";
+ verbose(" Enabled languages: ");
+ while (lang != NULL) {
+ char *const end = strchr(lang, ',');
+ if (lang[0] == '+') {
+ ++lang;
+ mode = Add;
+ prefix = "+ ";
+ } else if (lang[0] == '-') {
+ ++lang;
+ mode = Remove;
+ prefix = "- ";
+ }
+ if (mode == Replace) enableLanguages(FALSE);
+ if (end != NULL) *end = '\0';
+ if (lang[0] != '\0') {
+ if (strcmp(lang, "all") == 0)
+ enableLanguages((boolean)(mode != Remove));
+ else {
+ const langType language = getNamedLanguage(lang);
+ if (language == LANG_IGNORE)
+ error(WARNING, "Unknown language \"%s\" in \"%s\" option", lang,
+ option);
+ else
+ enableLanguage(language, (boolean)(mode != Remove));
+ }
+ verbose("%s%s%s", (first ? "" : ", "), prefix, lang);
+ prefix = "";
+ first = FALSE;
+ if (mode == Replace) mode = Add;
+ }
+ lang = (end != NULL ? end + 1 : NULL);
+ }
+ verbose("\n");
+ eFree(langs);
+}
+
+static void processLicenseOption(const char *const option __unused__,
+ const char *const parameter __unused__) {
+ printProgramIdentification();
+ puts("");
+ puts(License1);
+ puts(License2);
+ exit(0);
+}
+
+static void processListKindsOption(const char *const option,
+ const char *const parameter) {
+ if (parameter[0] == '\0' || strcasecmp(parameter, "all") == 0)
+ printLanguageKinds(LANG_AUTO);
+ else {
+ langType language = getNamedLanguage(parameter);
+ if (language == LANG_IGNORE)
+ error(FATAL, "Unknown language \"%s\" in \"%s\" option", parameter,
+ option);
+ else
+ printLanguageKinds(language);
+ }
+ exit(0);
+}
+
+static void processListMapsOption(const char *const __unused__ option,
+ const char *const __unused__ parameter) {
+ if (parameter[0] == '\0' || strcasecmp(parameter, "all") == 0)
+ printLanguageMaps(LANG_AUTO);
+ else {
+ langType language = getNamedLanguage(parameter);
+ if (language == LANG_IGNORE)
+ error(FATAL, "Unknown language \"%s\" in \"%s\" option", parameter,
+ option);
+ else
+ printLanguageMaps(language);
+ }
+ exit(0);
+}
+
+static void processListLanguagesOption(const char *const option __unused__,
+ const char *const parameter __unused__) {
+ printLanguageList();
+ exit(0);
+}
+
+static void processOptionFile(const char *const option,
+ const char *const parameter) {
+ if (parameter[0] == '\0')
+ error(WARNING, "no option file supplied for \"%s\"", option);
+ else if (!parseFileOptions(parameter))
+ error(FATAL | PERROR, "cannot open option file \"%s\"", parameter);
+}
+
+static void processSortOption(const char *const option,
+ const char *const parameter) {
+ if (isFalse(parameter))
+ Option.sorted = SO_UNSORTED;
+ else if (isTrue(parameter))
+ Option.sorted = SO_SORTED;
+ else if (strcasecmp(parameter, "f") == 0 ||
+ strcasecmp(parameter, "fold") == 0 ||
+ strcasecmp(parameter, "foldcase") == 0)
+ Option.sorted = SO_FOLDSORTED;
+ else
+ error(FATAL, "Invalid value for \"%s\" option", option);
+}
+
+static void installHeaderListDefaults(void) {
+ Option.headerExt = stringListNewFromArgv(HeaderExtensions);
+ if (Option.verbose) {
+ printf(" Setting default header extensions: ");
+ stringListPrint(Option.headerExt);
+ putchar('\n');
+ }
+}
+
+static void processHeaderListOption(const int option, const char *parameter) {
+ /* Check to make sure that the user did not enter "ctags -h *.c"
+ * by testing to see if the list is a filename that exists.
+ */
+ if (doesFileExist(parameter)) error(FATAL, "-%c: Invalid list", option);
+ if (strcmp(parameter, "default") == 0)
+ installHeaderListDefaults();
+ else {
+ boolean clear = TRUE;
+
+ if (parameter[0] == '+') {
+ ++parameter;
+ clear = FALSE;
+ }
+ if (Option.headerExt == NULL) Option.headerExt = stringListNew();
+ verbose(" Header Extensions:\n");
+ addExtensionList(Option.headerExt, parameter, clear);
+ }
+}
+
+/*
+ * Token ignore processing
+ */
+
+/* Determines whether or not "name" should be ignored, per the ignore list.
+ */
+extern boolean isIgnoreToken(const char *const name,
+ boolean *const pIgnoreParens,
+ const char **const replacement) {
+ boolean result = FALSE;
+
+ if (Option.ignore != NULL) {
+ const size_t nameLen = strlen(name);
+ unsigned int i;
+
+ if (pIgnoreParens != NULL) *pIgnoreParens = FALSE;
+
+ for (i = 0; i < stringListCount(Option.ignore); ++i) {
+ vString *token = stringListItem(Option.ignore, i);
+
+ if (strncmp(vStringValue(token), name, nameLen) == 0) {
+ const size_t tokenLen = vStringLength(token);
+
+ if (nameLen == tokenLen) {
+ result = TRUE;
+ break;
+ } else if (tokenLen == nameLen + 1 &&
+ vStringChar(token, tokenLen - 1) == '+') {
+ result = TRUE;
+ if (pIgnoreParens != NULL) *pIgnoreParens = TRUE;
+ break;
+ } else if (vStringChar(token, nameLen) == '=') {
+ if (replacement != NULL)
+ *replacement = vStringValue(token) + nameLen + 1;
+ break;
+ }
+ }
+ }
+ }
+ return result;
+}
+
+static void saveIgnoreToken(vString *const ignoreToken) {
+ if (Option.ignore == NULL) Option.ignore = stringListNew();
+ stringListAdd(Option.ignore, ignoreToken);
+ verbose(" ignore token: %s\n", vStringValue(ignoreToken));
+}
+
+static void readIgnoreList(const char *const list) {
+ char *newList = stringCopy(list);
+ const char *token = strtok(newList, IGNORE_SEPARATORS);
+
+ while (token != NULL) {
+ vString *const entry = vStringNewInit(token);
+
+ saveIgnoreToken(entry);
+ token = strtok(NULL, IGNORE_SEPARATORS);
+ }
+ eFree(newList);
+}
+
+static void addIgnoreListFromFile(const char *const fileName) {
+ stringList *tokens = stringListNewFromFile(fileName);
+ if (tokens == NULL) error(FATAL | PERROR, "cannot open \"%s\"", fileName);
+ if (Option.ignore == NULL)
+ Option.ignore = tokens;
+ else
+ stringListCombine(Option.ignore, tokens);
+}
+
+static void processIgnoreOption(const char *const list) {
+ if (strchr("@./\\", list[0]) != NULL) {
+ const char *fileName = (*list == '@') ? list + 1 : list;
+ addIgnoreListFromFile(fileName);
+ }
+#if defined(MSDOS) || defined(WIN32) || defined(OS2)
+ else if (isalpha(list[0]) && list[1] == ':')
+ addIgnoreListFromFile(list);
+#endif
+ else if (strcmp(list, "-") == 0) {
+ freeList(&Option.ignore);
+ verbose(" clearing list\n");
+ } else
+ readIgnoreList(list);
+}
+
+static void processVersionOption(const char *const option __unused__,
+ const char *const parameter __unused__) {
+ printProgramIdentification();
+ exit(0);
+}
+
+/*
+ * Option tables
+ */
+
+static parametricOption ParametricOptions[] = {
+ {"etags-include", processEtagsInclude, FALSE},
+ {"exclude", processExcludeOption, FALSE},
+ {"excmd", processExcmdOption, FALSE},
+ {"extra", processExtraTagsOption, FALSE},
+ {"fields", processFieldsOption, FALSE},
+ {"filter-terminator", processFilterTerminatorOption, TRUE},
+ {"format", processFormatOption, TRUE},
+ {"help", processHelpOption, TRUE},
+ {"lang", processLanguageForceOption, FALSE},
+ {"language", processLanguageForceOption, FALSE},
+ {"language-force", processLanguageForceOption, FALSE},
+ {"languages", processLanguagesOption, FALSE},
+ {"langdef", processLanguageDefineOption, FALSE},
+ {"langmap", processLanguageMapOption, FALSE},
+ {"license", processLicenseOption, TRUE},
+ {"list-kinds", processListKindsOption, TRUE},
+ {"list-maps", processListMapsOption, TRUE},
+ {"list-languages", processListLanguagesOption, TRUE},
+ {"options", processOptionFile, FALSE},
+ {"sort", processSortOption, TRUE},
+ {"version", processVersionOption, TRUE},
+};
+
+static booleanOption BooleanOptions[] = {
+ {"append", &Option.append, TRUE},
+ {"file-scope", &Option.include.fileScope, FALSE},
+ {"file-tags", &Option.include.fileNames, FALSE},
+ {"filter", &Option.filter, TRUE},
+ {"if0", &Option.if0, FALSE},
+ {"kind-long", &Option.kindLong, TRUE},
+ {"line-directives", &Option.lineDirectives, FALSE},
+ {"links", &Option.followLinks, FALSE},
+#ifdef RECURSE_SUPPORTED
+ {"recurse", &Option.recurse, FALSE},
+#endif
+ {"tag-relative", &Option.tagRelative, TRUE},
+ {"totals", &Option.printTotals, TRUE},
+ {"verbose", &Option.verbose, FALSE},
+};
+
+/*
+ * Generic option parsing
+ */
+
+static void checkOptionOrder(const char *const option) {
+ if (NonOptionEncountered)
+ error(FATAL, "-%s option may not follow a file name", option);
+}
+
+static boolean processParametricOption(const char *const option,
+ const char *const parameter) {
+ const int count = sizeof(ParametricOptions) / sizeof(parametricOption);
+ boolean found = FALSE;
+ int i;
+
+ for (i = 0; i < count && !found; ++i) {
+ parametricOption *const entry = &ParametricOptions[i];
+ if (strcmp(option, entry->name) == 0) {
+ found = TRUE;
+ if (entry->initOnly) checkOptionOrder(option);
+ (entry->handler)(option, parameter);
+ }
+ }
+ return found;
+}
+
+static boolean getBooleanOption(const char *const option,
+ const char *const parameter) {
+ boolean selection = TRUE;
+
+ if (parameter[0] == '\0')
+ selection = TRUE;
+ else if (isFalse(parameter))
+ selection = FALSE;
+ else if (isTrue(parameter))
+ selection = TRUE;
+ else
+ error(FATAL, "Invalid value for \"%s\" option", option);
+
+ return selection;
+}
+
+static boolean processBooleanOption(const char *const option,
+ const char *const parameter) {
+ const int count = sizeof(BooleanOptions) / sizeof(booleanOption);
+ boolean found = FALSE;
+ int i;
+
+ for (i = 0; i < count && !found; ++i) {
+ booleanOption *const entry = &BooleanOptions[i];
+ if (strcmp(option, entry->name) == 0) {
+ found = TRUE;
+ if (entry->initOnly) checkOptionOrder(option);
+ *entry->pValue = getBooleanOption(option, parameter);
+ }
+ }
+ return found;
+}
+
+static void processLongOption(const char *const option,
+ const char *const parameter) {
+ Assert(parameter != NULL);
+ if (parameter == NULL && parameter[0] == '\0')
+ verbose(" Option: --%s\n", option);
+ else
+ verbose(" Option: --%s=%s\n", option, parameter);
+
+ if (processBooleanOption(option, parameter))
+ ;
+ else if (processParametricOption(option, parameter))
+ ;
+ else if (processKindOption(option, parameter))
+ ;
+ else if (processRegexOption(option, parameter))
+ ;
+#ifndef RECURSE_SUPPORTED
+ else if (strcmp(option, "recurse") == 0)
+ error(WARNING, "%s option not supported on this host", option);
+#endif
+ else
+ error(FATAL, "Unknown option: --%s", option);
+}
+
+static void processShortOption(const char *const option,
+ const char *const parameter) {
+ if (parameter == NULL || parameter[0] == '\0')
+ verbose(" Option: -%s\n", option);
+ else
+ verbose(" Option: -%s %s\n", option, parameter);
+
+ if (isCompoundOption(*option) && (parameter == NULL || parameter[0] == '\0'))
+ error(FATAL, "Missing parameter for \"%s\" option", option);
+ else
+ switch (*option) {
+ case '?':
+ processHelpOption("?", NULL);
+ exit(0);
+ break;
+ case 'a':
+ checkOptionOrder(option);
+ Option.append = TRUE;
+ break;
+#ifdef DEBUG
+ case 'b':
+ if (atol(parameter) < 0)
+ error(FATAL, "-%s: Invalid line number", option);
+ Option.breakLine = atol(parameter);
+ break;
+ case 'D':
+ Option.debugLevel = strtol(parameter, NULL, 0);
+ if (debug(DEBUG_STATUS)) Option.verbose = TRUE;
+ break;
+#endif
+ case 'B':
+ Option.backward = TRUE;
+ break;
+ case 'e':
+ checkOptionOrder(option);
+ setEtagsMode();
+ break;
+ case 'f':
+ case 'o':
+ checkOptionOrder(option);
+ if (Option.tagFileName != NULL) {
+ error(WARNING, "-%s option specified more than once, last value used",
+ option);
+ freeString(&Option.tagFileName);
+ } else if (parameter[0] == '-' && parameter[1] != '\0')
+ error(FATAL, "output file name may not begin with a '-'");
+ Option.tagFileName = stringCopy(parameter);
+ break;
+ case 'F':
+ Option.backward = FALSE;
+ break;
+ case 'h':
+ processHeaderListOption(*option, parameter);
+ break;
+ case 'I':
+ processIgnoreOption(parameter);
+ break;
+ case 'L':
+ if (Option.fileList != NULL) {
+ error(WARNING, "-%s option specified more than once, last value used",
+ option);
+ freeString(&Option.fileList);
+ }
+ Option.fileList = stringCopy(parameter);
+ break;
+ case 'n':
+ Option.locate = EX_LINENUM;
+ break;
+ case 'N':
+ Option.locate = EX_PATTERN;
+ break;
+ case 'R':
+#ifdef RECURSE_SUPPORTED
+ Option.recurse = TRUE;
+#else
+ error(WARNING, "-%s option not supported on this host", option);
+#endif
+ break;
+ case 'u':
+ checkOptionOrder(option);
+ Option.sorted = SO_UNSORTED;
+ break;
+ case 'V':
+ Option.verbose = TRUE;
+ break;
+ case 'w':
+ /* silently ignored */
+ break;
+ case 'x':
+ checkOptionOrder(option);
+ Option.xref = TRUE;
+ break;
+ default:
+ error(FATAL, "Unknown option: -%s", option);
+ break;
+ }
+}
+
+extern void parseOption(cookedArgs *const args) {
+ Assert(!cArgOff(args));
+ if (args->isOption) {
+ if (args->longOption)
+ processLongOption(args->item, args->parameter);
+ else {
+ const char *parameter = args->parameter;
+ while (*parameter == ' ') ++parameter;
+ processShortOption(args->item, parameter);
+ }
+ cArgForth(args);
+ }
+}
+
+extern void parseOptions(cookedArgs *const args) {
+ NonOptionEncountered = FALSE;
+ while (!cArgOff(args) && cArgIsOption(args)) parseOption(args);
+ if (!cArgOff(args) && !cArgIsOption(args)) NonOptionEncountered = TRUE;
+}
+
+static const char *CheckFile;
+static boolean checkSameFile(const char *const fileName) {
+ return isSameFile(CheckFile, fileName);
+}
+
+static boolean parseFileOptions(const char *const fileName) {
+ boolean fileFound = FALSE;
+ const char *const format = "Considering option file %s: %s\n";
+ CheckFile = fileName;
+ if (stringListHasTest(OptionFiles, checkSameFile))
+ verbose(format, fileName, "already considered");
+ else {
+ FILE *const fp = fopen(fileName, "r");
+ if (fp == NULL)
+ verbose(format, fileName, "not found");
+ else {
+ cookedArgs *const args = cArgNewFromLineFile(fp);
+ vString *file = vStringNewInit(fileName);
+ stringListAdd(OptionFiles, file);
+ verbose(format, fileName, "reading...");
+ parseOptions(args);
+ if (NonOptionEncountered)
+ error(WARNING, "Ignoring non-option in %s\n", fileName);
+ cArgDelete(args);
+ fclose(fp);
+ fileFound = TRUE;
+ }
+ }
+ return fileFound;
+}
+
+/* Actions to be taken before reading any other options */
+extern void previewFirstOption(cookedArgs *const args) {
+ while (cArgIsOption(args)) {
+ if (strcmp(args->item, "V") == 0 || strcmp(args->item, "verbose") == 0)
+ parseOption(args);
+ else if (strcmp(args->item, "options") == 0 &&
+ strcmp(args->parameter, "NONE") == 0) {
+ fprintf(stderr, "No options will be read from files or environment\n");
+ SkipConfiguration = TRUE;
+ cArgForth(args);
+ } else
+ break;
+ }
+}
+
+static void parseConfigurationFileOptionsInDirectoryWithLeafname(
+ const char *directory, const char *leafname) {
+ vString *const pathname = combinePathAndFile(directory, leafname);
+ parseFileOptions(vStringValue(pathname));
+ vStringDelete(pathname);
+}
+
+static void parseConfigurationFileOptionsInDirectory(const char *directory) {
+ parseConfigurationFileOptionsInDirectoryWithLeafname(directory, ".ctags");
+#ifdef MSDOS_STYLE_PATH
+ parseConfigurationFileOptionsInDirectoryWithLeafname(directory, "ctags.cnf");
+#endif
+}
+
+static void parseConfigurationFileOptions(void) {
+ /* We parse .ctags on all systems, and additionally ctags.cnf on DOS. */
+ const char *const home = getenv("HOME");
+#ifdef CUSTOM_CONFIGURATION_FILE
+ parseFileOptions(CUSTOM_CONFIGURATION_FILE);
+#endif
+#ifdef MSDOS_STYLE_PATH
+ parseFileOptions("/ctags.cnf");
+#endif
+ parseFileOptions("/etc/ctags.conf");
+ parseFileOptions("/usr/local/etc/ctags.conf");
+ if (home != NULL) {
+ parseConfigurationFileOptionsInDirectory(home);
+ } else {
+#ifdef MSDOS_STYLE_PATH
+ /*
+ * Windows users don't usually set HOME.
+ * The OS sets HOMEDRIVE and HOMEPATH for them.
+ */
+ const char *homeDrive = getenv("HOMEDRIVE");
+ const char *homePath = getenv("HOMEPATH");
+ if (homeDrive != NULL && homePath != NULL) {
+ vString *const windowsHome = vStringNew();
+ vStringCatS(windowsHome, homeDrive);
+ vStringCatS(windowsHome, homePath);
+ parseConfigurationFileOptionsInDirectory(vStringValue(windowsHome));
+ vStringDelete(windowsHome);
+ }
+#endif
+ }
+ parseConfigurationFileOptionsInDirectory(".");
+}
+
+static void parseEnvironmentOptions(void) {
+ const char *envOptions = NULL;
+ const char *var = NULL;
+
+ if (Option.etags) {
+ var = ETAGS_ENVIRONMENT;
+ envOptions = getenv(var);
+ }
+ if (envOptions == NULL) {
+ var = CTAGS_ENVIRONMENT;
+ envOptions = getenv(var);
+ }
+ if (envOptions != NULL && envOptions[0] != '\0') {
+ cookedArgs *const args = cArgNewFromString(envOptions);
+ verbose("Reading options from $CTAGS\n");
+ parseOptions(args);
+ cArgDelete(args);
+ if (NonOptionEncountered)
+ error(WARNING, "Ignoring non-option in %s variable", var);
+ }
+}
+
+extern void readOptionConfiguration(void) {
+ if (!SkipConfiguration) {
+ parseConfigurationFileOptions();
+ parseEnvironmentOptions();
+ }
+}
+
+/*
+ * Option initialization
+ */
+
+extern void initOptions(void) {
+ OptionFiles = stringListNew();
+ verbose("Setting option defaults\n");
+ installHeaderListDefaults();
+ verbose(" Installing default language mappings:\n");
+ installLanguageMapDefaults();
+
+ /* always excluded by default */
+ verbose(" Installing default exclude patterns:\n");
+ processExcludeOption(NULL, "{arch}");
+ processExcludeOption(NULL, ".arch-ids");
+ processExcludeOption(NULL, ".arch-inventory");
+ processExcludeOption(NULL, "autom4te.cache");
+ processExcludeOption(NULL, "BitKeeper");
+ processExcludeOption(NULL, ".bzr");
+ processExcludeOption(NULL, ".bzrignore");
+ processExcludeOption(NULL, "CVS");
+ processExcludeOption(NULL, ".cvsignore");
+ processExcludeOption(NULL, "_darcs");
+ processExcludeOption(NULL, ".deps");
+ processExcludeOption(NULL, "EIFGEN");
+ processExcludeOption(NULL, ".git");
+ processExcludeOption(NULL, ".hg");
+ processExcludeOption(NULL, "PENDING");
+ processExcludeOption(NULL, "RCS");
+ processExcludeOption(NULL, "RESYNC");
+ processExcludeOption(NULL, "SCCS");
+ processExcludeOption(NULL, ".svn");
+}
+
+extern void freeOptionResources(void) {
+ freeString(&Option.tagFileName);
+ freeString(&Option.fileList);
+ freeString(&Option.filterTerminator);
+
+ freeList(&Excluded);
+ freeList(&Option.ignore);
+ freeList(&Option.headerExt);
+ freeList(&Option.etagsInclude);
+ freeList(&OptionFiles);
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/options.h b/third_party/ctags/options.h
new file mode 100644
index 000000000..9af483b8e
--- /dev/null
+++ b/third_party/ctags/options.h
@@ -0,0 +1,134 @@
+#ifndef _OPTIONS_H
+#define _OPTIONS_H
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/args.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/strlist.h"
+#include "third_party/ctags/vstring.h"
+
+#if defined(OPTION_WRITE) || defined(VAXC)
+#define CONST_OPTION
+#else
+#define CONST_OPTION const
+#endif
+
+/*
+ * DATA DECLARATIONS
+ */
+
+typedef enum { OPTION_NONE, OPTION_SHORT, OPTION_LONG } optionType;
+
+typedef struct sCookedArgs {
+ /* private */
+ Arguments* args;
+ char* shortOptions;
+ char simple[2];
+ boolean isOption;
+ boolean longOption;
+ const char* parameter;
+ /* public */
+ char* item;
+} cookedArgs;
+
+typedef enum eLocate {
+ EX_MIX, /* line numbers for defines, patterns otherwise */
+ EX_LINENUM, /* -n only line numbers in tag file */
+ EX_PATTERN /* -N only patterns in tag file */
+} exCmd;
+
+typedef enum sortType { SO_UNSORTED, SO_SORTED, SO_FOLDSORTED } sortType;
+
+struct sInclude {
+ boolean fileNames; /* include tags for source file names */
+ boolean qualifiedTags; /* include tags for qualified class members */
+ boolean fileScope; /* include tags of file scope only */
+};
+
+struct sExtFields { /* extension field content control */
+ boolean access;
+ boolean fileScope;
+ boolean implementation;
+ boolean inheritance;
+ boolean kind;
+ boolean kindKey;
+ boolean kindLong;
+ boolean language;
+ boolean lineNumber;
+ boolean scope;
+ boolean signature;
+ boolean typeRef;
+};
+
+/* This stores the command line options.
+ */
+typedef struct sOptionValues {
+ struct sInclude include; /* --extra extra tag inclusion */
+ struct sExtFields extensionFields; /* --fields extension field control */
+ stringList* ignore; /* -I name of file containing tokens to ignore */
+ boolean append; /* -a append to "tags" file */
+ boolean backward; /* -B regexp patterns search backwards */
+ boolean etags; /* -e output Emacs style tags file */
+ exCmd locate; /* --excmd EX command used to locate tag */
+ boolean recurse; /* -R recurse into directories */
+ sortType sorted; /* -u,--sort sort tags */
+ boolean verbose; /* -V verbose */
+ boolean xref; /* -x generate xref output instead */
+ char* fileList; /* -L name of file containing names of files */
+ char* tagFileName; /* -o name of tags file */
+ stringList* headerExt; /* -h header extensions */
+ stringList* etagsInclude; /* --etags-include list of TAGS files to include*/
+ unsigned int tagFileFormat; /* --format tag file format (level) */
+ boolean if0; /* --if0 examine code within "#if 0" branch */
+ boolean kindLong; /* --kind-long */
+ langType language; /* --lang specified language override */
+ boolean followLinks; /* --link follow symbolic links? */
+ boolean filter; /* --filter behave as filter: files in, tags out */
+ char* filterTerminator; /* --filter-terminator string to output */
+ boolean tagRelative; /* --tag-relative file paths relative to tag file */
+ boolean printTotals; /* --totals print cumulative statistics */
+ boolean lineDirectives; /* --linedirectives process #line directives */
+#ifdef DEBUG
+ long debugLevel; /* -D debugging output */
+ unsigned long breakLine; /* -b source line at which to call lineBreak() */
+#endif
+} optionValues;
+
+/*
+ * GLOBAL VARIABLES
+ */
+extern CONST_OPTION optionValues Option;
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+extern void verbose(const char* const format, ...) __printf__(1, 2);
+extern void freeList(stringList** const pString);
+extern void setDefaultTagFileName(void);
+extern void checkOptions(void);
+extern boolean filesRequired(void);
+extern void testEtagsInvocation(void);
+
+extern cookedArgs* cArgNewFromString(const char* string);
+extern cookedArgs* cArgNewFromArgv(char* const* const argv);
+extern cookedArgs* cArgNewFromFile(FILE* const fp);
+extern cookedArgs* cArgNewFromLineFile(FILE* const fp);
+extern void cArgDelete(cookedArgs* const current);
+extern boolean cArgOff(cookedArgs* const current);
+extern boolean cArgIsOption(cookedArgs* const current);
+extern const char* cArgItem(cookedArgs* const current);
+extern void cArgForth(cookedArgs* const current);
+
+extern boolean isExcludedFile(const char* const name);
+extern boolean isIncludeFile(const char* const fileName);
+extern boolean isIgnoreToken(const char* const name,
+ boolean* const pIgnoreParens,
+ const char** const replacement);
+extern void parseOption(cookedArgs* const cargs);
+extern void parseOptions(cookedArgs* const cargs);
+extern void previewFirstOption(cookedArgs* const cargs);
+extern void readOptionConfiguration(void);
+extern void initOptions(void);
+extern void freeOptionResources(void);
+
+#endif /* _OPTIONS_H */
diff --git a/third_party/ctags/parse.c b/third_party/ctags/parse.c
new file mode 100644
index 000000000..940b7a40c
--- /dev/null
+++ b/third_party/ctags/parse.c
@@ -0,0 +1,574 @@
+/*
+ * $Id: parse.c 597 2007-07-31 05:35:30Z dhiebert $
+ *
+ * Copyright (c) 1996-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for managing source languages and
+ * dispatching files to the appropriate language parser.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/main.h"
+#define OPTION_WRITE
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/parsers.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+static parserDefinitionFunc* BuiltInParsers[] = {PARSER_LIST};
+static parserDefinition** LanguageTable = NULL;
+static unsigned int LanguageCount = 0;
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+extern void makeSimpleTag(const vString* const name, kindOption* const kinds,
+ const int kind) {
+ if (kinds[kind].enabled && name != NULL && vStringLength(name) > 0) {
+ tagEntryInfo e;
+ initTagEntry(&e, vStringValue(name));
+
+ e.kindName = kinds[kind].name;
+ e.kind = kinds[kind].letter;
+
+ makeTagEntry(&e);
+ }
+}
+
+/*
+ * parserDescription mapping management
+ */
+
+extern parserDefinition* parserNew(const char* name) {
+ parserDefinition* result = xCalloc(1, parserDefinition);
+ result->name = eStrdup(name);
+ return result;
+}
+
+extern const char* getLanguageName(const langType language) {
+ const char* result;
+ if (language == LANG_IGNORE)
+ result = "unknown";
+ else {
+ Assert(0 <= language && language < (int)LanguageCount);
+ result = LanguageTable[language]->name;
+ }
+ return result;
+}
+
+extern langType getNamedLanguage(const char* const name) {
+ langType result = LANG_IGNORE;
+ unsigned int i;
+ Assert(name != NULL);
+ for (i = 0; i < LanguageCount && result == LANG_IGNORE; ++i) {
+ const parserDefinition* const lang = LanguageTable[i];
+ if (lang->name != NULL)
+ if (strcasecmp(name, lang->name) == 0) result = i;
+ }
+ return result;
+}
+
+static langType getExtensionLanguage(const char* const extension) {
+ langType result = LANG_IGNORE;
+ unsigned int i;
+ for (i = 0; i < LanguageCount && result == LANG_IGNORE; ++i) {
+ stringList* const exts = LanguageTable[i]->currentExtensions;
+ if (exts != NULL && stringListExtensionMatched(exts, extension)) result = i;
+ }
+ return result;
+}
+
+static langType getPatternLanguage(const char* const fileName) {
+ langType result = LANG_IGNORE;
+ const char* base = baseFilename(fileName);
+ unsigned int i;
+ for (i = 0; i < LanguageCount && result == LANG_IGNORE; ++i) {
+ stringList* const ptrns = LanguageTable[i]->currentPatterns;
+ if (ptrns != NULL && stringListFileMatched(ptrns, base)) result = i;
+ }
+ return result;
+}
+
+#ifdef SYS_INTERPRETER
+
+/* The name of the language interpreter, either directly or as the argument
+ * to "env".
+ */
+static vString* determineInterpreter(const char* const cmd) {
+ vString* const interpreter = vStringNew();
+ const char* p = cmd;
+ do {
+ vStringClear(interpreter);
+ for (; isspace((int)*p); ++p)
+ ; /* no-op */
+ for (; *p != '\0' && !isspace((int)*p); ++p)
+ vStringPut(interpreter, (int)*p);
+ vStringTerminate(interpreter);
+ } while (strcmp(vStringValue(interpreter), "env") == 0);
+ return interpreter;
+}
+
+static langType getInterpreterLanguage(const char* const fileName) {
+ langType result = LANG_IGNORE;
+ FILE* const fp = fopen(fileName, "r");
+ if (fp != NULL) {
+ vString* const vLine = vStringNew();
+ const char* const line = readLine(vLine, fp);
+ if (line != NULL && line[0] == '#' && line[1] == '!') {
+ const char* const lastSlash = strrchr(line, '/');
+ const char* const cmd = lastSlash != NULL ? lastSlash + 1 : line + 2;
+ vString* const interpreter = determineInterpreter(cmd);
+ result = getExtensionLanguage(vStringValue(interpreter));
+ if (result == LANG_IGNORE)
+ result = getNamedLanguage(vStringValue(interpreter));
+ vStringDelete(interpreter);
+ }
+ vStringDelete(vLine);
+ fclose(fp);
+ }
+ return result;
+}
+
+#endif
+
+extern langType getFileLanguage(const char* const fileName) {
+ langType language = Option.language;
+ if (language == LANG_AUTO) {
+ language = getExtensionLanguage(fileExtension(fileName));
+ if (language == LANG_IGNORE) language = getPatternLanguage(fileName);
+#ifdef SYS_INTERPRETER
+ if (language == LANG_IGNORE) {
+ fileStatus* status = eStat(fileName);
+ if (status->isExecutable) language = getInterpreterLanguage(fileName);
+ }
+#endif
+ }
+ return language;
+}
+
+extern void printLanguageMap(const langType language) {
+ boolean first = TRUE;
+ unsigned int i;
+ stringList* map = LanguageTable[language]->currentPatterns;
+ Assert(0 <= language && language < (int)LanguageCount);
+ for (i = 0; map != NULL && i < stringListCount(map); ++i) {
+ printf("%s(%s)", (first ? "" : " "), vStringValue(stringListItem(map, i)));
+ first = FALSE;
+ }
+ map = LanguageTable[language]->currentExtensions;
+ for (i = 0; map != NULL && i < stringListCount(map); ++i) {
+ printf("%s.%s", (first ? "" : " "), vStringValue(stringListItem(map, i)));
+ first = FALSE;
+ }
+}
+
+extern void installLanguageMapDefault(const langType language) {
+ parserDefinition* lang;
+ Assert(0 <= language && language < (int)LanguageCount);
+ lang = LanguageTable[language];
+ if (lang->currentPatterns != NULL) stringListDelete(lang->currentPatterns);
+ if (lang->currentExtensions != NULL)
+ stringListDelete(lang->currentExtensions);
+
+ if (lang->patterns == NULL)
+ lang->currentPatterns = stringListNew();
+ else {
+ lang->currentPatterns = stringListNewFromArgv(lang->patterns);
+ }
+ if (lang->extensions == NULL)
+ lang->currentExtensions = stringListNew();
+ else {
+ lang->currentExtensions = stringListNewFromArgv(lang->extensions);
+ }
+ if (Option.verbose) printLanguageMap(language);
+ verbose("\n");
+}
+
+extern void installLanguageMapDefaults(void) {
+ unsigned int i;
+ for (i = 0; i < LanguageCount; ++i) {
+ verbose(" %s: ", getLanguageName(i));
+ installLanguageMapDefault(i);
+ }
+}
+
+extern void clearLanguageMap(const langType language) {
+ Assert(0 <= language && language < (int)LanguageCount);
+ stringListClear(LanguageTable[language]->currentPatterns);
+ stringListClear(LanguageTable[language]->currentExtensions);
+}
+
+extern void addLanguagePatternMap(const langType language, const char* ptrn) {
+ vString* const str = vStringNewInit(ptrn);
+ parserDefinition* lang;
+ Assert(0 <= language && language < (int)LanguageCount);
+ lang = LanguageTable[language];
+ if (lang->currentPatterns == NULL) lang->currentPatterns = stringListNew();
+ stringListAdd(lang->currentPatterns, str);
+}
+
+extern boolean removeLanguageExtensionMap(const char* const extension) {
+ boolean result = FALSE;
+ unsigned int i;
+ for (i = 0; i < LanguageCount && !result; ++i) {
+ stringList* const exts = LanguageTable[i]->currentExtensions;
+ if (exts != NULL && stringListRemoveExtension(exts, extension)) {
+ verbose(" (removed from %s)", getLanguageName(i));
+ result = TRUE;
+ }
+ }
+ return result;
+}
+
+extern void addLanguageExtensionMap(const langType language,
+ const char* extension) {
+ vString* const str = vStringNewInit(extension);
+ Assert(0 <= language && language < (int)LanguageCount);
+ removeLanguageExtensionMap(extension);
+ stringListAdd(LanguageTable[language]->currentExtensions, str);
+}
+
+extern void enableLanguage(const langType language, const boolean state) {
+ Assert(0 <= language && language < (int)LanguageCount);
+ LanguageTable[language]->enabled = state;
+}
+
+extern void enableLanguages(const boolean state) {
+ unsigned int i;
+ for (i = 0; i < LanguageCount; ++i) enableLanguage(i, state);
+}
+
+static void initializeParsers(void) {
+ unsigned int i;
+ for (i = 0; i < LanguageCount; ++i)
+ if (LanguageTable[i]->initialize != NULL)
+ (LanguageTable[i]->initialize)((langType)i);
+}
+
+extern void initializeParsing(void) {
+ unsigned int builtInCount;
+ unsigned int i;
+
+ builtInCount = sizeof(BuiltInParsers) / sizeof(BuiltInParsers[0]);
+ LanguageTable = xMalloc(builtInCount, parserDefinition*);
+
+ verbose("Installing parsers: ");
+ for (i = 0; i < builtInCount; ++i) {
+ parserDefinition* const def = (*BuiltInParsers[i])();
+ if (def != NULL) {
+ boolean accepted = FALSE;
+ if (def->name == NULL || def->name[0] == '\0')
+ error(FATAL, "parser definition must contain name\n");
+ else if (def->regex) {
+#ifdef HAVE_REGEX
+ def->parser = findRegexTags;
+ accepted = TRUE;
+#endif
+ } else if ((def->parser == NULL) == (def->parser2 == NULL))
+ error(FATAL,
+ "%s parser definition must define one and only one parsing "
+ "routine\n",
+ def->name);
+ else
+ accepted = TRUE;
+ if (accepted) {
+ verbose("%s%s", i > 0 ? ", " : "", def->name);
+ def->id = LanguageCount++;
+ LanguageTable[def->id] = def;
+ }
+ }
+ }
+ verbose("\n");
+ enableLanguages(TRUE);
+ initializeParsers();
+}
+
+extern void freeParserResources(void) {
+ unsigned int i;
+ for (i = 0; i < LanguageCount; ++i) {
+ parserDefinition* const lang = LanguageTable[i];
+ freeList(&lang->currentPatterns);
+ freeList(&lang->currentExtensions);
+ eFree(lang->name);
+ lang->name = NULL;
+ eFree(lang);
+ }
+ if (LanguageTable != NULL) eFree(LanguageTable);
+ LanguageTable = NULL;
+ LanguageCount = 0;
+}
+
+/*
+ * Option parsing
+ */
+
+extern void processLanguageDefineOption(
+ const char* const option, const char* const parameter __unused__) {
+#ifdef HAVE_REGEX
+ if (parameter[0] == '\0')
+ error(WARNING, "No language specified for \"%s\" option", option);
+ else if (getNamedLanguage(parameter) != LANG_IGNORE)
+ error(WARNING, "Language \"%s\" already defined", parameter);
+ else {
+ unsigned int i = LanguageCount++;
+ parserDefinition* const def = parserNew(parameter);
+ def->parser = findRegexTags;
+ def->currentPatterns = stringListNew();
+ def->currentExtensions = stringListNew();
+ def->regex = TRUE;
+ def->enabled = TRUE;
+ def->id = i;
+ LanguageTable = xRealloc(LanguageTable, i + 1, parserDefinition*);
+ LanguageTable[i] = def;
+ }
+#else
+ error(WARNING, "regex support not available; required for --%s option",
+ option);
+#endif
+}
+
+static kindOption* langKindOption(const langType language, const int flag) {
+ unsigned int i;
+ kindOption* result = NULL;
+ const parserDefinition* lang;
+ Assert(0 <= language && language < (int)LanguageCount);
+ lang = LanguageTable[language];
+ for (i = 0; i < lang->kindCount && result == NULL; ++i)
+ if (lang->kinds[i].letter == flag) result = &lang->kinds[i];
+ return result;
+}
+
+static void disableLanguageKinds(const langType language) {
+ const parserDefinition* lang;
+ Assert(0 <= language && language < (int)LanguageCount);
+ lang = LanguageTable[language];
+ if (lang->regex)
+ disableRegexKinds(language);
+ else {
+ unsigned int i;
+ for (i = 0; i < lang->kindCount; ++i) lang->kinds[i].enabled = FALSE;
+ }
+}
+
+static boolean enableLanguageKind(const langType language, const int kind,
+ const boolean mode) {
+ boolean result = FALSE;
+ if (LanguageTable[language]->regex)
+ result = enableRegexKind(language, kind, mode);
+ else {
+ kindOption* const opt = langKindOption(language, kind);
+ if (opt != NULL) {
+ opt->enabled = mode;
+ result = TRUE;
+ }
+ }
+ return result;
+}
+
+static void processLangKindOption(const langType language,
+ const char* const option,
+ const char* const parameter) {
+ const char* p = parameter;
+ boolean mode = TRUE;
+ int c;
+
+ Assert(0 <= language && language < (int)LanguageCount);
+ if (*p != '+' && *p != '-') disableLanguageKinds(language);
+ while ((c = *p++) != '\0') switch (c) {
+ case '+':
+ mode = TRUE;
+ break;
+ case '-':
+ mode = FALSE;
+ break;
+ default:
+ if (!enableLanguageKind(language, c, mode))
+ error(WARNING, "Unsupported parameter '%c' for --%s option", c,
+ option);
+ break;
+ }
+}
+
+extern boolean processKindOption(const char* const option,
+ const char* const parameter) {
+ boolean handled = FALSE;
+ const char* const dash = strchr(option, '-');
+ if (dash != NULL &&
+ (strcmp(dash + 1, "kinds") == 0 || strcmp(dash + 1, "types") == 0)) {
+ langType language;
+ vString* langName = vStringNew();
+ vStringNCopyS(langName, option, dash - option);
+ language = getNamedLanguage(vStringValue(langName));
+ if (language == LANG_IGNORE)
+ error(WARNING, "Unknown language \"%s\" in \"%s\" option",
+ vStringValue(langName), option);
+ else
+ processLangKindOption(language, option, parameter);
+ vStringDelete(langName);
+ handled = TRUE;
+ }
+ return handled;
+}
+
+static void printLanguageKind(const kindOption* const kind, boolean indent) {
+ const char* const indentation = indent ? " " : "";
+ printf("%s%c %s%s\n", indentation, kind->letter,
+ kind->description != NULL ? kind->description
+ : (kind->name != NULL ? kind->name : ""),
+ kind->enabled ? "" : " [off]");
+}
+
+static void printKinds(langType language, boolean indent) {
+ const parserDefinition* lang;
+ Assert(0 <= language && language < (int)LanguageCount);
+ lang = LanguageTable[language];
+ if (lang->kinds != NULL || lang->regex) {
+ unsigned int i;
+ for (i = 0; i < lang->kindCount; ++i)
+ printLanguageKind(lang->kinds + i, indent);
+ printRegexKinds(language, indent);
+ }
+}
+
+extern void printLanguageKinds(const langType language) {
+ if (language == LANG_AUTO) {
+ unsigned int i;
+ for (i = 0; i < LanguageCount; ++i) {
+ const parserDefinition* const lang = LanguageTable[i];
+ printf("%s%s\n", lang->name, lang->enabled ? "" : " [disabled]");
+ printKinds(i, TRUE);
+ }
+ } else
+ printKinds(language, FALSE);
+}
+
+static void printMaps(const langType language) {
+ const parserDefinition* lang;
+ unsigned int i;
+ Assert(0 <= language && language < (int)LanguageCount);
+ lang = LanguageTable[language];
+ printf("%-8s", lang->name);
+ if (lang->currentExtensions != NULL)
+ for (i = 0; i < stringListCount(lang->currentExtensions); ++i)
+ printf(" *.%s", vStringValue(stringListItem(lang->currentExtensions, i)));
+ if (lang->currentPatterns != NULL)
+ for (i = 0; i < stringListCount(lang->currentPatterns); ++i)
+ printf(" %s", vStringValue(stringListItem(lang->currentPatterns, i)));
+ putchar('\n');
+}
+
+extern void printLanguageMaps(const langType language) {
+ if (language == LANG_AUTO) {
+ unsigned int i;
+ for (i = 0; i < LanguageCount; ++i) printMaps(i);
+ } else
+ printMaps(language);
+}
+
+static void printLanguage(const langType language) {
+ const parserDefinition* lang;
+ Assert(0 <= language && language < (int)LanguageCount);
+ lang = LanguageTable[language];
+ if (lang->kinds != NULL || lang->regex)
+ printf("%s%s\n", lang->name, lang->enabled ? "" : " [disabled]");
+}
+
+extern void printLanguageList(void) {
+ unsigned int i;
+ for (i = 0; i < LanguageCount; ++i) printLanguage(i);
+}
+
+/*
+ * File parsing
+ */
+
+static void makeFileTag(const char* const fileName) {
+ if (Option.include.fileNames) {
+ tagEntryInfo tag;
+ initTagEntry(&tag, baseFilename(fileName));
+
+ tag.isFileEntry = TRUE;
+ tag.lineNumberEntry = TRUE;
+ tag.lineNumber = 1;
+ tag.kindName = "file";
+ tag.kind = 'F';
+
+ makeTagEntry(&tag);
+ }
+}
+
+static boolean createTagsForFile(const char* const fileName,
+ const langType language,
+ const unsigned int passCount) {
+ boolean retried = FALSE;
+ Assert(0 <= language && language < (int)LanguageCount);
+ if (fileOpen(fileName, language)) {
+ const parserDefinition* const lang = LanguageTable[language];
+ if (Option.etags) beginEtagsFile();
+
+ makeFileTag(fileName);
+
+ if (lang->parser != NULL)
+ lang->parser();
+ else if (lang->parser2 != NULL)
+ retried = lang->parser2(passCount);
+
+ if (Option.etags) endEtagsFile(getSourceFileTagPath());
+
+ fileClose();
+ }
+
+ return retried;
+}
+
+static boolean createTagsWithFallback(const char* const fileName,
+ const langType language) {
+ const unsigned long numTags = TagFile.numTags.added;
+ fpos_t tagFilePosition;
+ unsigned int passCount = 0;
+ boolean tagFileResized = FALSE;
+
+ fgetpos(TagFile.fp, &tagFilePosition);
+ while (createTagsForFile(fileName, language, ++passCount)) {
+ /* Restore prior state of tag file.
+ */
+ fsetpos(TagFile.fp, &tagFilePosition);
+ TagFile.numTags.added = numTags;
+ tagFileResized = TRUE;
+ }
+ return tagFileResized;
+}
+
+extern boolean parseFile(const char* const fileName) {
+ boolean tagFileResized = FALSE;
+ langType language = Option.language;
+ if (Option.language == LANG_AUTO) language = getFileLanguage(fileName);
+ Assert(language != LANG_AUTO);
+ if (language == LANG_IGNORE)
+ verbose("ignoring %s (unknown language)\n", fileName);
+ else if (!LanguageTable[language]->enabled)
+ verbose("ignoring %s (language disabled)\n", fileName);
+ else {
+ if (Option.filter) openTagFile();
+
+ tagFileResized = createTagsWithFallback(fileName, language);
+
+ if (Option.filter) closeTagFile(tagFileResized);
+ addTotals(1, 0L, 0L);
+
+ return tagFileResized;
+ }
+ return tagFileResized;
+}
+
+/* vi:set tabstop=4 shiftwidth=4 nowrap: */
diff --git a/third_party/ctags/parse.h b/third_party/ctags/parse.h
new file mode 100644
index 000000000..4f8b13746
--- /dev/null
+++ b/third_party/ctags/parse.h
@@ -0,0 +1,125 @@
+#ifndef _PARSE_H
+#define _PARSE_H
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parsers.h"
+#include "third_party/ctags/strlist.h"
+
+/*
+ * MACROS
+ */
+#define KIND_COUNT(kindTable) (sizeof(kindTable) / sizeof(kindOption))
+
+#define LANG_AUTO (-1)
+#define LANG_IGNORE (-2)
+
+/*
+ * DATA DECLARATIONS
+ */
+typedef int langType;
+
+typedef void (*createRegexTag)(const vString* const name);
+typedef void (*simpleParser)(void);
+typedef boolean (*rescanParser)(const unsigned int passCount);
+typedef void (*parserInitialize)(langType language);
+
+typedef struct sKindOption {
+ boolean enabled; /* are tags for kind enabled? */
+ int letter; /* kind letter */
+ const char* name; /* kind name */
+ const char* description; /* displayed in --help output */
+} kindOption;
+
+typedef struct {
+ /* defined by parser */
+ char* name; /* name of language */
+ kindOption* kinds; /* tag kinds handled by parser */
+ unsigned int kindCount; /* size of `kinds' list */
+ const char* const* extensions; /* list of default extensions */
+ const char* const* patterns; /* list of default file name patterns */
+ parserInitialize initialize; /* initialization routine, if needed */
+ simpleParser parser; /* simple parser (common case) */
+ rescanParser parser2; /* rescanning parser (unusual case) */
+ boolean regex; /* is this a regex parser? */
+
+ /* used internally */
+ unsigned int id; /* id assigned to language */
+ boolean enabled; /* currently enabled? */
+ stringList* currentPatterns; /* current list of file name patterns */
+ stringList* currentExtensions; /* current list of extensions */
+} parserDefinition;
+
+typedef parserDefinition*(parserDefinitionFunc)(void);
+
+typedef struct {
+ size_t start; /* character index in line where match starts */
+ size_t length; /* length of match */
+} regexMatch;
+
+typedef void (*regexCallback)(const char* line, const regexMatch* matches,
+ unsigned int count);
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+
+/* Each parsers' definition function is called. The routine is expected to
+ * return a structure allocated using parserNew(). This structure must,
+ * at minimum, set the `parser' field.
+ */
+extern parserDefinitionFunc PARSER_LIST;
+
+/* Legacy interface */
+extern boolean includingDefineTags(void);
+
+/* Language processing and parsing */
+extern void makeSimpleTag(const vString* const name, kindOption* const kinds,
+ const int kind);
+extern parserDefinition* parserNew(const char* name);
+extern const char* getLanguageName(const langType language);
+extern langType getNamedLanguage(const char* const name);
+extern langType getFileLanguage(const char* const fileName);
+extern void installLanguageMapDefault(const langType language);
+extern void installLanguageMapDefaults(void);
+extern void clearLanguageMap(const langType language);
+extern boolean removeLanguageExtensionMap(const char* const extension);
+extern void addLanguageExtensionMap(const langType language,
+ const char* extension);
+extern void addLanguagePatternMap(const langType language, const char* ptrn);
+extern void printLanguageMap(const langType language);
+extern void printLanguageMaps(const langType language);
+extern void enableLanguages(const boolean state);
+extern void enableLanguage(const langType language, const boolean state);
+extern void initializeParsing(void);
+extern void freeParserResources(void);
+extern void processLanguageDefineOption(const char* const option,
+ const char* const parameter);
+extern boolean processKindOption(const char* const option,
+ const char* const parameter);
+extern void printKindOptions(void);
+extern void printLanguageKinds(const langType language);
+extern void printLanguageList(void);
+extern boolean parseFile(const char* const fileName);
+
+/* Regex interface */
+#ifdef HAVE_REGEX
+extern void findRegexTags(void);
+extern boolean matchRegex(const vString* const line, const langType language);
+#endif
+extern boolean processRegexOption(const char* const option,
+ const char* const parameter);
+extern void addLanguageRegex(const langType language, const char* const regex);
+extern void addTagRegex(const langType language, const char* const regex,
+ const char* const name, const char* const kinds,
+ const char* const flags);
+extern void addCallbackRegex(const langType language, const char* const regex,
+ const char* const flags,
+ const regexCallback callback);
+extern void disableRegexKinds(const langType language);
+extern boolean enableRegexKind(const langType language, const int kind,
+ const boolean mode);
+extern void printRegexKinds(const langType language, boolean indent);
+extern void freeRegexResources(void);
+extern void checkRegex(void);
+
+#endif /* _PARSE_H */
diff --git a/third_party/ctags/parsers.h b/third_party/ctags/parsers.h
new file mode 100644
index 000000000..bfc10f8bf
--- /dev/null
+++ b/third_party/ctags/parsers.h
@@ -0,0 +1,15 @@
+#ifndef _PARSERS_H
+#define _PARSERS_H
+
+/* Add the name of any new parser definition function here */
+#define PARSER_LIST \
+ AntParser, AsmParser, AspParser, AwkParser, BasicParser, BetaParser, \
+ CParser, CppParser, CsharpParser, CobolParser, DosBatchParser, \
+ EiffelParser, ErlangParser, FlexParser, FortranParser, GoParser, \
+ HtmlParser, JavaParser, JavaScriptParser, LispParser, LuaParser, \
+ MakefileParser, MatLabParser, ObjcParser, OcamlParser, PascalParser, \
+ PerlParser, PhpParser, PythonParser, RexxParser, RubyParser, \
+ SchemeParser, ShParser, SlangParser, SmlParser, SqlParser, TclParser, \
+ TexParser, VeraParser, VerilogParser, VhdlParser, VimParser, YaccParser
+
+#endif /* _PARSERS_H */
diff --git a/third_party/ctags/pascal.c b/third_party/ctags/pascal.c
new file mode 100644
index 000000000..3511fbe5d
--- /dev/null
+++ b/third_party/ctags/pascal.c
@@ -0,0 +1,222 @@
+/*
+ * $Id: pascal.c 536 2007-06-02 06:09:00Z elliotth $
+ *
+ * Copyright (c) 2001-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for the Pascal language,
+ * including some extensions for Object Pascal.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum { K_FUNCTION, K_PROCEDURE } pascalKind;
+
+static kindOption PascalKinds[] = {{TRUE, 'f', "function", "functions"},
+ {TRUE, 'p', "procedure", "procedures"}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void createPascalTag(tagEntryInfo* const tag, const vString* const name,
+ const int kind) {
+ if (PascalKinds[kind].enabled && name != NULL && vStringLength(name) > 0) {
+ initTagEntry(tag, vStringValue(name));
+ tag->kindName = PascalKinds[kind].name;
+ tag->kind = PascalKinds[kind].letter;
+ } else
+ initTagEntry(tag, NULL);
+}
+
+static void makePascalTag(const tagEntryInfo* const tag) {
+ if (tag->name != NULL) makeTagEntry(tag);
+}
+
+static const unsigned char* dbp;
+
+#define starttoken(c) (isalpha((int)c) || (int)c == '_')
+#define intoken(c) (isalnum((int)c) || (int)c == '_' || (int)c == '.')
+#define endtoken(c) (!intoken(c) && !isdigit((int)c))
+
+static boolean tail(const char* cp) {
+ boolean result = FALSE;
+ register int len = 0;
+
+ while (*cp != '\0' && tolower((int)*cp) == tolower((int)dbp[len]))
+ cp++, len++;
+ if (*cp == '\0' && !intoken(dbp[len])) {
+ dbp += len;
+ result = TRUE;
+ }
+ return result;
+}
+
+/* Algorithm adapted from from GNU etags.
+ * Locates tags for procedures & functions. Doesn't do any type- or
+ * var-definitions. It does look for the keyword "extern" or "forward"
+ * immediately following the procedure statement; if found, the tag is
+ * skipped.
+ */
+static void findPascalTags(void) {
+ vString* name = vStringNew();
+ tagEntryInfo tag;
+ pascalKind kind = K_FUNCTION;
+ /* each of these flags is TRUE iff: */
+ boolean incomment = FALSE; /* point is inside a comment */
+ int comment_char = '\0'; /* type of current comment */
+ boolean inquote = FALSE; /* point is inside '..' string */
+ boolean get_tagname = FALSE; /* point is after PROCEDURE/FUNCTION
+ keyword, so next item = potential tag */
+ boolean found_tag = FALSE; /* point is after a potential tag */
+ boolean inparms = FALSE; /* point is within parameter-list */
+ boolean verify_tag = FALSE;
+ /* point has passed the parm-list, so the next token will determine
+ * whether this is a FORWARD/EXTERN to be ignored, or whether it is a
+ * real tag
+ */
+
+ dbp = fileReadLine();
+ while (dbp != NULL) {
+ int c = *dbp++;
+
+ if (c == '\0') /* if end of line */
+ {
+ dbp = fileReadLine();
+ if (dbp == NULL || *dbp == '\0') continue;
+ if (!((found_tag && verify_tag) || get_tagname)) c = *dbp++;
+ /* only if don't need *dbp pointing to the beginning of
+ * the name of the procedure or function
+ */
+ }
+ if (incomment) {
+ if (comment_char == '{' && c == '}')
+ incomment = FALSE;
+ else if (comment_char == '(' && c == '*' && *dbp == ')') {
+ dbp++;
+ incomment = FALSE;
+ }
+ continue;
+ } else if (inquote) {
+ if (c == '\'') inquote = FALSE;
+ continue;
+ } else
+ switch (c) {
+ case '\'':
+ inquote = TRUE; /* found first quote */
+ continue;
+ case '{': /* found open { comment */
+ incomment = TRUE;
+ comment_char = c;
+ continue;
+ case '(':
+ if (*dbp == '*') /* found open (* comment */
+ {
+ incomment = TRUE;
+ comment_char = c;
+ dbp++;
+ } else if (found_tag) /* found '(' after tag, i.e., parm-list */
+ inparms = TRUE;
+ continue;
+ case ')': /* end of parms list */
+ if (inparms) inparms = FALSE;
+ continue;
+ case ';':
+ if (found_tag && !inparms) /* end of proc or fn stmt */
+ {
+ verify_tag = TRUE;
+ break;
+ }
+ continue;
+ }
+ if (found_tag && verify_tag && *dbp != ' ') {
+ /* check if this is an "extern" declaration */
+ if (*dbp == '\0') continue;
+ if (tolower((int)*dbp == 'e')) {
+ if (tail("extern")) /* superfluous, really! */
+ {
+ found_tag = FALSE;
+ verify_tag = FALSE;
+ }
+ } else if (tolower((int)*dbp) == 'f') {
+ if (tail("forward")) /* check for forward reference */
+ {
+ found_tag = FALSE;
+ verify_tag = FALSE;
+ }
+ }
+ if (found_tag && verify_tag) /* not external proc, so make tag */
+ {
+ found_tag = FALSE;
+ verify_tag = FALSE;
+ makePascalTag(&tag);
+ continue;
+ }
+ }
+ if (get_tagname) /* grab name of proc or fn */
+ {
+ const unsigned char* cp;
+
+ if (*dbp == '\0') continue;
+
+ /* grab block name */
+ while (isspace((int)*dbp)) ++dbp;
+ for (cp = dbp; *cp != '\0' && !endtoken(*cp); cp++) continue;
+ vStringNCopyS(name, (const char*)dbp, cp - dbp);
+ createPascalTag(&tag, name, kind);
+ dbp = cp; /* set dbp to e-o-token */
+ get_tagname = FALSE;
+ found_tag = TRUE;
+ /* and proceed to check for "extern" */
+ } else if (!incomment && !inquote && !found_tag) {
+ switch (tolower((int)c)) {
+ case 'c':
+ if (tail("onstructor")) {
+ get_tagname = TRUE;
+ kind = K_PROCEDURE;
+ }
+ break;
+ case 'd':
+ if (tail("estructor")) {
+ get_tagname = TRUE;
+ kind = K_PROCEDURE;
+ }
+ break;
+ case 'p':
+ if (tail("rocedure")) {
+ get_tagname = TRUE;
+ kind = K_PROCEDURE;
+ }
+ break;
+ case 'f':
+ if (tail("unction")) {
+ get_tagname = TRUE;
+ kind = K_FUNCTION;
+ }
+ break;
+ }
+ } /* while not eof */
+ }
+ vStringDelete(name);
+}
+
+extern parserDefinition* PascalParser(void) {
+ static const char* const extensions[] = {"p", "pas", NULL};
+ parserDefinition* def = parserNew("Pascal");
+ def->extensions = extensions;
+ def->kinds = PascalKinds;
+ def->kindCount = KIND_COUNT(PascalKinds);
+ def->parser = findPascalTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/perl.c b/third_party/ctags/perl.c
new file mode 100644
index 000000000..a9fb7ecf6
--- /dev/null
+++ b/third_party/ctags/perl.c
@@ -0,0 +1,327 @@
+/*
+ * $Id: perl.c 601 2007-08-02 04:45:16Z perlguy0 $
+ *
+ * Copyright (c) 2000-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for PERL language
+ * files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+#define TRACE_PERL_C 0
+#define TRACE \
+ if (TRACE_PERL_C) printf("perl.c:%d: ", __LINE__), printf
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum {
+ K_NONE = -1,
+ K_CONSTANT,
+ K_FORMAT,
+ K_LABEL,
+ K_PACKAGE,
+ K_SUBROUTINE,
+ K_SUBROUTINE_DECLARATION
+} perlKind;
+
+static kindOption PerlKinds[] = {
+ {TRUE, 'c', "constant", "constants"},
+ {TRUE, 'f', "format", "formats"},
+ {TRUE, 'l', "label", "labels"},
+ {TRUE, 'p', "package", "packages"},
+ {TRUE, 's', "subroutine", "subroutines"},
+ {FALSE, 'd', "subroutine declaration", "subroutine declarations"},
+};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static boolean isIdentifier1(int c) {
+ return (boolean)(isalpha(c) || c == '_');
+}
+
+static boolean isIdentifier(int c) {
+ return (boolean)(isalnum(c) || c == '_');
+}
+
+static boolean isPodWord(const char *word) {
+ boolean result = FALSE;
+ if (isalpha(*word)) {
+ const char *const pods[] = {"head1", "head2", "head3", "head4",
+ "over", "item", "back", "pod",
+ "begin", "end", "for"};
+ const size_t count = sizeof(pods) / sizeof(pods[0]);
+ const char *white = strpbrk(word, " \t");
+ const size_t len = (white != NULL) ? (size_t)(white - word) : strlen(word);
+ char *const id = (char *)eMalloc(len + 1);
+ size_t i;
+ strncpy(id, word, len);
+ id[len] = '\0';
+ for (i = 0; i < count && !result; ++i) {
+ if (strcmp(id, pods[i]) == 0) result = TRUE;
+ }
+ eFree(id);
+ }
+ return result;
+}
+
+/*
+ * Perl subroutine declaration may look like one of the following:
+ *
+ * sub abc;
+ * sub abc :attr;
+ * sub abc (proto);
+ * sub abc (proto) :attr;
+ *
+ * Note that there may be more than one attribute. Attributes may
+ * have things in parentheses (they look like arguments). Anything
+ * inside of those parentheses goes. Prototypes may contain semi-colons.
+ * The matching end when we encounter (outside of any parentheses) either
+ * a semi-colon (that'd be a declaration) or an left curly brace
+ * (definition).
+ *
+ * This is pretty complicated parsing (plus we all know that only perl can
+ * parse Perl), so we are only promising best effort here.
+ *
+ * If we can't determine what this is (due to a file ending, for example),
+ * we will return FALSE.
+ */
+static boolean isSubroutineDeclaration(const unsigned char *cp) {
+ boolean attr = FALSE;
+ int nparens = 0;
+
+ do {
+ for (; *cp; ++cp) {
+ SUB_DECL_SWITCH:
+ switch (*cp) {
+ case ':':
+ if (nparens)
+ break;
+ else if (TRUE == attr)
+ return FALSE; /* Invalid attribute name */
+ else
+ attr = TRUE;
+ break;
+ case '(':
+ ++nparens;
+ break;
+ case ')':
+ --nparens;
+ break;
+ case ' ':
+ case '\t':
+ break;
+ case ';':
+ if (!nparens) return TRUE;
+ case '{':
+ if (!nparens) return FALSE;
+ default:
+ if (attr) {
+ if (isIdentifier1(*cp)) {
+ cp++;
+ while (isIdentifier(*cp)) cp++;
+ attr = FALSE;
+ goto SUB_DECL_SWITCH; /* Instead of --cp; */
+ } else {
+ return FALSE;
+ }
+ } else if (nparens) {
+ break;
+ } else {
+ return FALSE;
+ }
+ }
+ }
+ } while (NULL != (cp = fileReadLine()));
+
+ return FALSE;
+}
+
+/* Algorithm adapted from from GNU etags.
+ * Perl support by Bart Robinson
+ * Perl sub names: look for /^ [ \t\n]sub [ \t\n]+ [^ \t\n{ (]+/
+ */
+static void findPerlTags(void) {
+ vString *name = vStringNew();
+ vString *package = NULL;
+ boolean skipPodDoc = FALSE;
+ const unsigned char *line;
+
+ while ((line = fileReadLine()) != NULL) {
+ boolean spaceRequired = FALSE;
+ boolean qualified = FALSE;
+ const unsigned char *cp = line;
+ perlKind kind = K_NONE;
+ tagEntryInfo e;
+
+ if (skipPodDoc) {
+ if (strncmp((const char *)line, "=cut", (size_t)4) == 0)
+ skipPodDoc = FALSE;
+ continue;
+ } else if (line[0] == '=') {
+ skipPodDoc = isPodWord((const char *)line + 1);
+ continue;
+ } else if (strcmp((const char *)line, "__DATA__") == 0)
+ break;
+ else if (strcmp((const char *)line, "__END__") == 0)
+ break;
+ else if (line[0] == '#')
+ continue;
+
+ while (isspace(*cp)) cp++;
+
+ if (strncmp((const char *)cp, "sub", (size_t)3) == 0) {
+ TRACE("this looks like a sub\n");
+ cp += 3;
+ kind = K_SUBROUTINE;
+ spaceRequired = TRUE;
+ qualified = TRUE;
+ } else if (strncmp((const char *)cp, "use", (size_t)3) == 0) {
+ cp += 3;
+ if (!isspace(*cp)) continue;
+ while (*cp && isspace(*cp)) ++cp;
+ if (strncmp((const char *)cp, "constant", (size_t)8) != 0) continue;
+ cp += 8;
+ kind = K_CONSTANT;
+ spaceRequired = TRUE;
+ qualified = TRUE;
+ } else if (strncmp((const char *)cp, "package", (size_t)7) == 0) {
+ /* This will point to space after 'package' so that a tag
+ can be made */
+ const unsigned char *space = cp += 7;
+
+ if (package == NULL)
+ package = vStringNew();
+ else
+ vStringClear(package);
+ while (isspace(*cp)) cp++;
+ while ((int)*cp != ';' && !isspace((int)*cp)) {
+ vStringPut(package, (int)*cp);
+ cp++;
+ }
+ vStringCatS(package, "::");
+
+ cp = space; /* Rewind */
+ kind = K_PACKAGE;
+ spaceRequired = TRUE;
+ qualified = TRUE;
+ } else if (strncmp((const char *)cp, "format", (size_t)6) == 0) {
+ cp += 6;
+ kind = K_FORMAT;
+ spaceRequired = TRUE;
+ qualified = TRUE;
+ } else {
+ if (isIdentifier1(*cp)) {
+ const unsigned char *p = cp;
+ while (isIdentifier(*p)) ++p;
+ while (isspace(*p)) ++p;
+ if ((int)*p == ':' && (int)*(p + 1) != ':') kind = K_LABEL;
+ }
+ }
+ if (kind != K_NONE) {
+ TRACE("cp0: %s\n", (const char *)cp);
+ if (spaceRequired && *cp && !isspace(*cp)) continue;
+
+ TRACE("cp1: %s\n", (const char *)cp);
+ while (isspace(*cp)) cp++;
+
+ while (!*cp || '#' == *cp) { /* Gobble up empty lines
+ and comments */
+ cp = fileReadLine();
+ if (!cp) goto END_MAIN_WHILE;
+ while (isspace(*cp)) cp++;
+ }
+
+ while (isIdentifier(*cp) || (K_PACKAGE == kind && ':' == *cp)) {
+ vStringPut(name, (int)*cp);
+ cp++;
+ }
+
+ if (K_FORMAT == kind &&
+ vStringLength(name) == 0 && /* cp did not advance */
+ '=' == *cp) {
+ /* format's name is optional. If it's omitted, 'STDOUT'
+ is assumed. */
+ vStringCatS(name, "STDOUT");
+ }
+
+ vStringTerminate(name);
+ TRACE("name: %s\n", name->buffer);
+
+ if (0 == vStringLength(name)) {
+ vStringClear(name);
+ continue;
+ }
+
+ if (K_SUBROUTINE == kind) {
+ /*
+ * isSubroutineDeclaration() may consume several lines. So
+ * we record line positions.
+ */
+ initTagEntry(&e, vStringValue(name));
+
+ if (TRUE == isSubroutineDeclaration(cp)) {
+ if (TRUE == PerlKinds[K_SUBROUTINE_DECLARATION].enabled) {
+ kind = K_SUBROUTINE_DECLARATION;
+ } else {
+ vStringClear(name);
+ continue;
+ }
+ }
+
+ e.kind = PerlKinds[kind].letter;
+ e.kindName = PerlKinds[kind].name;
+
+ makeTagEntry(&e);
+
+ if (Option.include.qualifiedTags && qualified && package != NULL &&
+ vStringLength(package) > 0) {
+ vString *const qualifiedName = vStringNew();
+ vStringCopy(qualifiedName, package);
+ vStringCat(qualifiedName, name);
+ e.name = vStringValue(qualifiedName);
+ makeTagEntry(&e);
+ vStringDelete(qualifiedName);
+ }
+ } else if (vStringLength(name) > 0) {
+ makeSimpleTag(name, PerlKinds, kind);
+ if (Option.include.qualifiedTags && qualified && K_PACKAGE != kind &&
+ package != NULL && vStringLength(package) > 0) {
+ vString *const qualifiedName = vStringNew();
+ vStringCopy(qualifiedName, package);
+ vStringCat(qualifiedName, name);
+ makeSimpleTag(qualifiedName, PerlKinds, kind);
+ vStringDelete(qualifiedName);
+ }
+ }
+ vStringClear(name);
+ }
+ }
+
+END_MAIN_WHILE:
+ vStringDelete(name);
+ if (package != NULL) vStringDelete(package);
+}
+
+extern parserDefinition *PerlParser(void) {
+ static const char *const extensions[] = {"pl", "pm", "plx", "perl", NULL};
+ parserDefinition *def = parserNew("Perl");
+ def->kinds = PerlKinds;
+ def->kindCount = KIND_COUNT(PerlKinds);
+ def->extensions = extensions;
+ def->parser = findPerlTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */
diff --git a/third_party/ctags/php.c b/third_party/ctags/php.c
new file mode 100644
index 000000000..06ab4094d
--- /dev/null
+++ b/third_party/ctags/php.c
@@ -0,0 +1,240 @@
+/*
+ * $Id: php.c 734 2009-08-20 23:33:54Z jafl $
+ *
+ * Copyright (c) 2000, Jesus Castagnetto
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for the PHP web page
+ * scripting language. Only recognizes functions and classes, not methods or
+ * variables.
+ *
+ * Parsing PHP defines by Pavel Hlousek , Apr 2003.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum { K_CLASS, K_DEFINE, K_FUNCTION, K_VARIABLE } phpKind;
+
+#if 0
+static kindOption PhpKinds [] = {
+ { TRUE, 'c', "class", "classes" },
+ { TRUE, 'd', "define", "constant definitions" },
+ { TRUE, 'f', "function", "functions" },
+ { TRUE, 'v', "variable", "variables" }
+};
+#endif
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+/* JavaScript patterns are duplicated in jscript.c */
+
+/*
+ * Cygwin doesn't support non-ASCII characters in character classes.
+ * This isn't a good solution to the underlying problem, because we're still
+ * making assumptions about the character encoding.
+ * Really, these regular expressions need to concentrate on what marks the
+ * end of an identifier, and we need something like iconv to take into
+ * account the user's locale (or an override on the command-line.)
+ */
+#ifdef __CYGWIN__
+#define ALPHA "[:alpha:]"
+#define ALNUM "[:alnum:]"
+#else
+#define ALPHA "A-Za-z\x7f-\uffff"
+#define ALNUM "0-9A-Za-z\x7f-\uffff"
+#endif
+
+static void installPHPRegex(const langType language) {
+ addTagRegex(language,
+ "^[ \t]*((final|abstract)[ \t]+)*class[ \t]+([" ALPHA "_][" ALNUM
+ "_]*)",
+ "\\3", "c,class,classes", NULL);
+ addTagRegex(language, "^[ \t]*interface[ \t]+([" ALPHA "_][" ALNUM "_]*)",
+ "\\1", "i,interface,interfaces", NULL);
+ addTagRegex(language,
+ "^[ \t]*define[ \t]*\\([ \t]*['\"]?([" ALPHA "_][" ALNUM "_]*)",
+ "\\1", "d,define,constant definitions", NULL);
+ addTagRegex(language,
+ "^[ \t]*((static|public|protected|private)[ \t]+)*function[ "
+ "\t]+&?[ \t]*([" ALPHA "_][" ALNUM "_]*)",
+ "\\3", "f,function,functions", NULL);
+ addTagRegex(language,
+ "^[ \t]*(\\$|::\\$|\\$this->)([" ALPHA "_][" ALNUM "_]*)[ \t]*=",
+ "\\2", "v,variable,variables", NULL);
+ addTagRegex(language,
+ "^[ \t]*((var|public|protected|private|static)[ \t]+)+\\$([" ALPHA
+ "_][" ALNUM "_]*)[ \t]*[=;]",
+ "\\3", "v,variable,variables", NULL);
+
+ /* function regex is covered by PHP regex */
+ addTagRegex(language,
+ "(^|[ \t])([A-Za-z0-9_]+)[ \t]*[=:][ \t]*function[ \t]*\\(",
+ "\\2", "j,jsfunction,javascript functions", NULL);
+ addTagRegex(language,
+ "(^|[ \t])([A-Za-z0-9_.]+)\\.([A-Za-z0-9_]+)[ \t]*=[ "
+ "\t]*function[ \t]*\\(",
+ "\\2.\\3", "j,jsfunction,javascript functions", NULL);
+ addTagRegex(language,
+ "(^|[ \t])([A-Za-z0-9_.]+)\\.([A-Za-z0-9_]+)[ \t]*=[ "
+ "\t]*function[ \t]*\\(",
+ "\\3", "j,jsfunction,javascript functions", NULL);
+}
+
+/* Create parser definition structure */
+extern parserDefinition* PhpParser(void) {
+ static const char* const extensions[] = {"php", "php3", "phtml", NULL};
+ parserDefinition* def = parserNew("PHP");
+ def->extensions = extensions;
+ def->initialize = installPHPRegex;
+ def->regex = TRUE;
+ return def;
+}
+
+#if 0
+
+static boolean isLetter(const int c)
+{
+ return (boolean)(isalpha(c) || (c >= 127 && c <= 255));
+}
+
+static boolean isVarChar1(const int c)
+{
+ return (boolean)(isLetter (c) || c == '_');
+}
+
+static boolean isVarChar(const int c)
+{
+ return (boolean)(isVarChar1 (c) || isdigit (c));
+}
+
+static void findPhpTags (void)
+{
+ vString *name = vStringNew ();
+ const unsigned char *line;
+
+ while ((line = fileReadLine ()) != NULL)
+ {
+ const unsigned char *cp = line;
+ const char* f;
+
+ while (isspace (*cp))
+ cp++;
+
+ if (*(const char*)cp == '$' && isVarChar1 (*(const char*)(cp+1)))
+ {
+ cp += 1;
+ vStringClear (name);
+ while (isVarChar ((int) *cp))
+ {
+ vStringPut (name, (int) *cp);
+ ++cp;
+ }
+ while (isspace ((int) *cp))
+ ++cp;
+ if (*(const char*) cp == '=')
+ {
+ vStringTerminate (name);
+ makeSimpleTag (name, PhpKinds, K_VARIABLE);
+ vStringClear (name);
+ }
+ }
+ else if ((f = strstr ((const char*) cp, "function")) != NULL &&
+ (f == (const char*) cp || isspace ((int) f [-1])) &&
+ isspace ((int) f [8]))
+ {
+ cp = ((const unsigned char *) f) + 8;
+
+ while (isspace ((int) *cp))
+ ++cp;
+
+ if (*cp == '&') /* skip reference character and following whitespace */
+ {
+ cp++;
+
+ while (isspace ((int) *cp))
+ ++cp;
+ }
+
+ vStringClear (name);
+ while (isalnum ((int) *cp) || *cp == '_')
+ {
+ vStringPut (name, (int) *cp);
+ ++cp;
+ }
+ vStringTerminate (name);
+ makeSimpleTag (name, PhpKinds, K_FUNCTION);
+ vStringClear (name);
+ }
+ else if (strncmp ((const char*) cp, "class", (size_t) 5) == 0 &&
+ isspace ((int) cp [5]))
+ {
+ cp += 5;
+
+ while (isspace ((int) *cp))
+ ++cp;
+ vStringClear (name);
+ while (isalnum ((int) *cp) || *cp == '_')
+ {
+ vStringPut (name, (int) *cp);
+ ++cp;
+ }
+ vStringTerminate (name);
+ makeSimpleTag (name, PhpKinds, K_CLASS);
+ vStringClear (name);
+ }
+ else if (strncmp ((const char*) cp, "define", (size_t) 6) == 0 &&
+ ! isalnum ((int) cp [6]))
+ {
+ cp += 6;
+
+ while (isspace ((int) *cp))
+ ++cp;
+ if (*cp != '(')
+ continue;
+ ++cp;
+
+ while (isspace ((int) *cp))
+ ++cp;
+ if ((*cp == '\'') || (*cp == '"'))
+ ++cp;
+ else if (! ((*cp == '_') || isalnum ((int) *cp)))
+ continue;
+
+ vStringClear (name);
+ while (isalnum ((int) *cp) || *cp == '_')
+ {
+ vStringPut (name, (int) *cp);
+ ++cp;
+ }
+ vStringTerminate (name);
+ makeSimpleTag (name, PhpKinds, K_DEFINE);
+ vStringClear (name);
+ }
+ }
+ vStringDelete (name);
+}
+
+extern parserDefinition* PhpParser (void)
+{
+ static const char *const extensions [] = { "php", "php3", "phtml", NULL };
+ parserDefinition* def = parserNew ("PHP");
+ def->kinds = PhpKinds;
+ def->kindCount = KIND_COUNT (PhpKinds);
+ def->extensions = extensions;
+ def->parser = findPhpTags;
+ return def;
+}
+
+#endif
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/python.c b/third_party/ctags/python.c
new file mode 100644
index 000000000..ac070ada1
--- /dev/null
+++ b/third_party/ctags/python.c
@@ -0,0 +1,668 @@
+/*
+ * $Id: python.c 752 2010-02-27 17:52:46Z elliotth $
+ *
+ * Copyright (c) 2000-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for Python language
+ * files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/mem/mem.h"
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/main.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DECLARATIONS
+ */
+typedef struct NestingLevel NestingLevel;
+typedef struct NestingLevels NestingLevels;
+
+struct NestingLevel {
+ int indentation;
+ vString *name;
+ int type;
+};
+
+struct NestingLevels {
+ NestingLevel *levels;
+ int n; /* number of levels in use */
+ int allocated;
+};
+
+typedef enum { K_CLASS, K_FUNCTION, K_MEMBER, K_VARIABLE, K_IMPORT } pythonKind;
+
+/*
+ * DATA DEFINITIONS
+ */
+static kindOption PythonKinds[] = {{TRUE, 'c', "class", "classes"},
+ {TRUE, 'f', "function", "functions"},
+ {TRUE, 'm', "member", "class members"},
+ {TRUE, 'v', "variable", "variables"},
+ {FALSE, 'i', "namespace", "imports"}};
+
+static char const *const singletriple = "'''";
+static char const *const doubletriple = "\"\"\"";
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static NestingLevels *nestingLevelsNew(void) {
+ NestingLevels *nls = xCalloc(1, NestingLevels);
+ return nls;
+}
+
+static void nestingLevelsFree(NestingLevels *nls) {
+ int i;
+ for (i = 0; i < nls->allocated; i++) vStringDelete(nls->levels[i].name);
+ if (nls->levels) eFree(nls->levels);
+ eFree(nls);
+}
+
+static void nestingLevelsPush(NestingLevels *nls, const vString *name,
+ int type) {
+ NestingLevel *nl = NULL;
+
+ if (nls->n >= nls->allocated) {
+ nls->allocated++;
+ nls->levels = xRealloc(nls->levels, nls->allocated, NestingLevel);
+ nls->levels[nls->n].name = vStringNew();
+ }
+ nl = &nls->levels[nls->n];
+ nls->n++;
+
+ vStringCopy(nl->name, name);
+ nl->type = type;
+}
+
+#if 0
+static NestingLevel *nestingLevelsGetCurrent (NestingLevels *nls)
+{
+ Assert (nls != NULL);
+
+ if (nls->n < 1)
+ return NULL;
+
+ return &nls->levels[nls->n - 1];
+}
+
+static void nestingLevelsPop (NestingLevels *nls)
+{
+ const NestingLevel *nl = nestingLevelsGetCurrent(nls);
+
+ Assert (nl != NULL);
+ vStringClear(nl->name);
+ nls->n--;
+}
+#endif
+
+static boolean isIdentifierFirstCharacter(int c) {
+ return (boolean)(isalpha(c) || c == '_');
+}
+
+static boolean isIdentifierCharacter(int c) {
+ return (boolean)(isalnum(c) || c == '_');
+}
+
+/* Given a string with the contents of a line directly after the "def" keyword,
+ * extract all relevant information and create a tag.
+ */
+static void makeFunctionTag(vString *const function, vString *const parent,
+ int is_class_parent,
+ const char *arglist __unused__) {
+ tagEntryInfo tag;
+ initTagEntry(&tag, vStringValue(function));
+
+ tag.kindName = "function";
+ tag.kind = 'f';
+ /* tag.extensionFields.arglist = arglist; */
+
+ if (vStringLength(parent) > 0) {
+ if (is_class_parent) {
+ tag.kindName = "member";
+ tag.kind = 'm';
+ tag.extensionFields.scope[0] = "class";
+ tag.extensionFields.scope[1] = vStringValue(parent);
+ } else {
+ tag.extensionFields.scope[0] = "function";
+ tag.extensionFields.scope[1] = vStringValue(parent);
+ }
+ }
+
+ /* If a function starts with __, we mark it as file scope.
+ * FIXME: What is the proper way to signal such attributes?
+ * TODO: What does functions/classes starting with _ and __ mean in python?
+ */
+ if (strncmp(vStringValue(function), "__", 2) == 0 &&
+ strcmp(vStringValue(function), "__init__") != 0) {
+ tag.extensionFields.access = "private";
+ tag.isFileScope = TRUE;
+ } else {
+ tag.extensionFields.access = "public";
+ }
+ makeTagEntry(&tag);
+}
+
+/* Given a string with the contents of the line directly after the "class"
+ * keyword, extract all necessary information and create a tag.
+ */
+static void makeClassTag(vString *const class, vString *const inheritance,
+ vString *const parent, int is_class_parent) {
+ tagEntryInfo tag;
+ initTagEntry(&tag, vStringValue(class));
+ tag.kindName = "class";
+ tag.kind = 'c';
+ if (vStringLength(parent) > 0) {
+ if (is_class_parent) {
+ tag.extensionFields.scope[0] = "class";
+ tag.extensionFields.scope[1] = vStringValue(parent);
+ } else {
+ tag.extensionFields.scope[0] = "function";
+ tag.extensionFields.scope[1] = vStringValue(parent);
+ }
+ }
+ tag.extensionFields.inheritance = vStringValue(inheritance);
+ makeTagEntry(&tag);
+}
+
+static void makeVariableTag(vString *const var, vString *const parent) {
+ tagEntryInfo tag;
+ initTagEntry(&tag, vStringValue(var));
+ tag.kindName = "variable";
+ tag.kind = 'v';
+ if (vStringLength(parent) > 0) {
+ tag.extensionFields.scope[0] = "class";
+ tag.extensionFields.scope[1] = vStringValue(parent);
+ }
+ makeTagEntry(&tag);
+}
+
+/* Skip a single or double quoted string. */
+static const char *skipString(const char *cp) {
+ const char *start = cp;
+ int escaped = 0;
+ for (cp++; *cp; cp++) {
+ if (escaped)
+ escaped--;
+ else if (*cp == '\\')
+ escaped++;
+ else if (*cp == *start)
+ return cp + 1;
+ }
+ return cp;
+}
+
+/* Skip everything up to an identifier start. */
+static const char *skipEverything(const char *cp) {
+ for (; *cp; cp++) {
+ if (*cp == '"' || *cp == '\'' || *cp == '#') {
+ cp = skipString(cp);
+ if (!*cp) break;
+ }
+ if (isIdentifierFirstCharacter((int)*cp)) return cp;
+ }
+ return cp;
+}
+
+/* Skip an identifier. */
+static const char *skipIdentifier(const char *cp) {
+ while (isIdentifierCharacter((int)*cp)) cp++;
+ return cp;
+}
+
+static const char *findDefinitionOrClass(const char *cp) {
+ while (*cp) {
+ cp = skipEverything(cp);
+ if (!strncmp(cp, "def", 3) || !strncmp(cp, "class", 5) ||
+ !strncmp(cp, "cdef", 4) || !strncmp(cp, "cpdef", 5)) {
+ return cp;
+ }
+ cp = skipIdentifier(cp);
+ }
+ return NULL;
+}
+
+static const char *skipSpace(const char *cp) {
+ while (isspace((int)*cp)) ++cp;
+ return cp;
+}
+
+/* Starting at ''cp'', parse an identifier into ''identifier''. */
+static const char *parseIdentifier(const char *cp, vString *const identifier) {
+ vStringClear(identifier);
+ while (isIdentifierCharacter((int)*cp)) {
+ vStringPut(identifier, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(identifier);
+ return cp;
+}
+
+static void parseClass(const char *cp, vString *const class,
+ vString *const parent, int is_class_parent) {
+ vString *const inheritance = vStringNew();
+ vStringClear(inheritance);
+ cp = parseIdentifier(cp, class);
+ cp = skipSpace(cp);
+ if (*cp == '(') {
+ ++cp;
+ while (*cp != ')') {
+ if (*cp == '\0') {
+ /* Closing parenthesis can be in follow up line. */
+ cp = (const char *)fileReadLine();
+ if (!cp) break;
+ vStringPut(inheritance, ' ');
+ continue;
+ }
+ vStringPut(inheritance, *cp);
+ ++cp;
+ }
+ vStringTerminate(inheritance);
+ }
+ makeClassTag(class, inheritance, parent, is_class_parent);
+ vStringDelete(inheritance);
+}
+
+static void parseImports(const char *cp) {
+ const char *pos;
+ vString *name, *name_next;
+
+ cp = skipEverything(cp);
+
+ if ((pos = strstr(cp, "import")) == NULL) return;
+
+ cp = pos + 6;
+
+ /* continue only if there is some space between the keyword and the identifier
+ */
+ if (!isspace(*cp)) return;
+
+ cp++;
+ cp = skipSpace(cp);
+
+ name = vStringNew();
+ name_next = vStringNew();
+
+ cp = skipEverything(cp);
+ while (*cp) {
+ cp = parseIdentifier(cp, name);
+
+ cp = skipEverything(cp);
+ /* we parse the next possible import statement as well to be able to ignore
+ * 'foo' in 'import foo as bar' */
+ parseIdentifier(cp, name_next);
+
+ /* take the current tag only if the next one is not "as" */
+ if (strcmp(vStringValue(name_next), "as") != 0 &&
+ strcmp(vStringValue(name), "as") != 0) {
+ makeSimpleTag(name, PythonKinds, K_IMPORT);
+ }
+ }
+ vStringDelete(name);
+ vStringDelete(name_next);
+}
+
+/* modified from get.c getArglistFromStr().
+ * warning: terminates rest of string past arglist!
+ * note: does not ignore brackets inside strings! */
+static char *parseArglist(const char *buf) {
+ char *start, *end;
+ int level;
+ if (NULL == buf) return NULL;
+ if (NULL == (start = strchr(buf, '('))) return NULL;
+ for (level = 1, end = start + 1; level > 0; ++end) {
+ if ('\0' == *end)
+ break;
+ else if ('(' == *end)
+ ++level;
+ else if (')' == *end)
+ --level;
+ }
+ *end = '\0';
+ return strdup(start);
+}
+
+static void parseFunction(const char *cp, vString *const def,
+ vString *const parent, int is_class_parent) {
+ char *arglist;
+
+ cp = parseIdentifier(cp, def);
+ arglist = parseArglist(cp);
+ makeFunctionTag(def, parent, is_class_parent, arglist);
+ if (arglist != NULL) {
+ eFree(arglist);
+ }
+}
+
+/* Get the combined name of a nested symbol. Classes are separated with ".",
+ * functions with "/". For example this code:
+ * class MyClass:
+ * def myFunction:
+ * def SubFunction:
+ * class SubClass:
+ * def Method:
+ * pass
+ * Would produce this string:
+ * MyClass.MyFunction/SubFunction/SubClass.Method
+ */
+static boolean constructParentString(NestingLevels *nls, int indent,
+ vString *result) {
+ int i;
+ NestingLevel *prev = NULL;
+ int is_class = FALSE;
+ vStringClear(result);
+ for (i = 0; i < nls->n; i++) {
+ NestingLevel *nl = nls->levels + i;
+ if (indent <= nl->indentation) break;
+ if (prev) {
+ vStringCatS(result,
+ "."); /* make Geany symbol list grouping work properly */
+ /*
+ if (prev->type == K_CLASS)
+ vStringCatS(result, ".");
+ else
+ vStringCatS(result, "/");
+ */
+ }
+ vStringCat(result, nl->name);
+ is_class = (nl->type == K_CLASS);
+ prev = nl;
+ }
+ return is_class;
+}
+
+/* Check whether parent's indentation level is higher than the current level and
+ * if so, remove it.
+ */
+static void checkParent(NestingLevels *nls, int indent, vString *parent) {
+ int i;
+ NestingLevel *n;
+
+ for (i = 0; i < nls->n; i++) {
+ n = nls->levels + i;
+ /* is there a better way to compare two vStrings? */
+ if (strcmp(vStringValue(parent), vStringValue(n->name)) == 0) {
+ if (n && indent <= n->indentation) {
+ /* remove this level by clearing its name */
+ vStringClear(n->name);
+ }
+ break;
+ }
+ }
+}
+
+static void addNestingLevel(NestingLevels *nls, int indentation,
+ const vString *name, boolean is_class) {
+ int i;
+ NestingLevel *nl = NULL;
+
+ for (i = 0; i < nls->n; i++) {
+ nl = nls->levels + i;
+ if (indentation <= nl->indentation) break;
+ }
+ if (i == nls->n) {
+ nestingLevelsPush(nls, name, 0);
+ nl = nls->levels + i;
+ } else { /* reuse existing slot */
+ nls->n = i + 1;
+ vStringCopy(nl->name, name);
+ }
+ nl->indentation = indentation;
+ nl->type = is_class ? K_CLASS : !K_CLASS;
+}
+
+/* Return a pointer to the start of the next triple string, or NULL. Store
+ * the kind of triple string in "which" if the return is not NULL.
+ */
+static char const *find_triple_start(char const *string, char const **which) {
+ char const *cp = string;
+
+ for (; *cp; cp++) {
+ if (*cp == '"' || *cp == '\'') {
+ if (strncmp(cp, doubletriple, 3) == 0) {
+ *which = doubletriple;
+ return cp;
+ }
+ if (strncmp(cp, singletriple, 3) == 0) {
+ *which = singletriple;
+ return cp;
+ }
+ cp = skipString(cp);
+ if (!*cp) break;
+ }
+ }
+ return NULL;
+}
+
+/* Find the end of a triple string as pointed to by "which", and update "which"
+ * with any other triple strings following in the given string.
+ */
+static void find_triple_end(char const *string, char const **which) {
+ char const *s = string;
+ while (1) {
+ /* Check if the string ends in the same line. */
+ s = strstr(s, *which);
+ if (!s) break;
+ s += 3;
+ *which = NULL;
+ /* If yes, check if another one starts in the same line. */
+ s = find_triple_start(s, which);
+ if (!s) break;
+ s += 3;
+ }
+}
+
+static const char *findVariable(const char *line) {
+ /* Parse global and class variable names (C.x) from assignment statements.
+ * Object attributes (obj.x) are ignored.
+ * Assignment to a tuple 'x, y = 2, 3' not supported.
+ * TODO: ignore duplicate tags from reassignment statements. */
+ const char *cp, *sp, *eq, *start;
+
+ cp = strstr(line, "=");
+ if (!cp) return NULL;
+ eq = cp + 1;
+ while (*eq) {
+ if (*eq == '=')
+ return NULL; /* ignore '==' operator and 'x=5,y=6)' function lines */
+ if (*eq == '(' || *eq == '#')
+ break; /* allow 'x = func(b=2,y=2,' lines and comments at the end of line
+ */
+ eq++;
+ }
+
+ /* go backwards to the start of the line, checking we have valid chars */
+ start = cp - 1;
+ while (start >= line && isspace((int)*start)) --start;
+ while (start >= line && isIdentifierCharacter((int)*start)) --start;
+ if (!isIdentifierFirstCharacter(*(start + 1))) return NULL;
+ sp = start;
+ while (sp >= line && isspace((int)*sp)) --sp;
+ if ((sp + 1) != line) /* the line isn't a simple variable assignment */
+ return NULL;
+ /* the line is valid, parse the variable name */
+ ++start;
+ return start;
+}
+
+/* Skip type declaration that optionally follows a cdef/cpdef */
+static const char *skipTypeDecl(const char *cp, boolean *is_class) {
+ const char *lastStart = cp, *ptr = cp;
+ int loopCount = 0;
+ ptr = skipSpace(cp);
+ if (!strncmp("extern", ptr, 6)) {
+ ptr += 6;
+ ptr = skipSpace(ptr);
+ if (!strncmp("from", ptr, 4)) {
+ return NULL;
+ }
+ }
+ if (!strncmp("class", ptr, 5)) {
+ ptr += 5;
+ *is_class = TRUE;
+ ptr = skipSpace(ptr);
+ return ptr;
+ }
+ /* limit so that we don't pick off "int item=obj()" */
+ while (*ptr && loopCount++ < 2) {
+ while (*ptr && *ptr != '=' && *ptr != '(' && !isspace(*ptr)) ptr++;
+ if (!*ptr || *ptr == '=') return NULL;
+ if (*ptr == '(') {
+ return lastStart; /* if we stopped on a '(' we are done */
+ }
+ ptr = skipSpace(ptr);
+ lastStart = ptr;
+ while (*lastStart == '*') lastStart++; /* cdef int *identifier */
+ }
+ return NULL;
+}
+
+static void findPythonTags(void) {
+ vString *const continuation = vStringNew();
+ vString *const name = vStringNew();
+ vString *const parent = vStringNew();
+
+ NestingLevels *const nesting_levels = nestingLevelsNew();
+
+ const char *line;
+ int line_skip = 0;
+ char const *longStringLiteral = NULL;
+
+ while ((line = (const char *)fileReadLine()) != NULL) {
+ const char *cp = line, *candidate;
+ char const *longstring;
+ char const *keyword, *variable;
+ int indent;
+
+ cp = skipSpace(cp);
+
+ if (*cp == '\0') /* skip blank line */
+ continue;
+
+ /* Skip comment if we are not inside a multi-line string. */
+ if (*cp == '#' && !longStringLiteral) continue;
+
+ /* Deal with line continuation. */
+ if (!line_skip) vStringClear(continuation);
+ vStringCatS(continuation, line);
+ vStringStripTrailing(continuation);
+ if (vStringLast(continuation) == '\\') {
+ vStringChop(continuation);
+ vStringCatS(continuation, " ");
+ line_skip = 1;
+ continue;
+ }
+ cp = line = vStringValue(continuation);
+ cp = skipSpace(cp);
+ indent = cp - line;
+ line_skip = 0;
+
+ checkParent(nesting_levels, indent, parent);
+
+ /* Deal with multiline string ending. */
+ if (longStringLiteral) {
+ find_triple_end(cp, &longStringLiteral);
+ continue;
+ }
+
+ /* Deal with multiline string start. */
+ longstring = find_triple_start(cp, &longStringLiteral);
+ if (longstring) {
+ longstring += 3;
+ find_triple_end(longstring, &longStringLiteral);
+ /* We don't parse for any tags in the rest of the line. */
+ continue;
+ }
+
+ /* Deal with def and class keywords. */
+ keyword = findDefinitionOrClass(cp);
+ if (keyword) {
+ boolean found = FALSE;
+ boolean is_class = FALSE;
+ if (!strncmp(keyword, "def ", 4)) {
+ cp = skipSpace(keyword + 3);
+ found = TRUE;
+ } else if (!strncmp(keyword, "class ", 6)) {
+ cp = skipSpace(keyword + 5);
+ found = TRUE;
+ is_class = TRUE;
+ } else if (!strncmp(keyword, "cdef ", 5)) {
+ cp = skipSpace(keyword + 4);
+ candidate = skipTypeDecl(cp, &is_class);
+ if (candidate) {
+ found = TRUE;
+ cp = candidate;
+ }
+
+ } else if (!strncmp(keyword, "cpdef ", 6)) {
+ cp = skipSpace(keyword + 5);
+ candidate = skipTypeDecl(cp, &is_class);
+ if (candidate) {
+ found = TRUE;
+ cp = candidate;
+ }
+ }
+
+ if (found) {
+ boolean is_parent_class;
+
+ is_parent_class = constructParentString(nesting_levels, indent, parent);
+
+ if (is_class)
+ parseClass(cp, name, parent, is_parent_class);
+ else
+ parseFunction(cp, name, parent, is_parent_class);
+
+ addNestingLevel(nesting_levels, indent, name, is_class);
+ }
+ }
+ /* Find global and class variables */
+ variable = findVariable(line);
+ if (variable) {
+ const char *start = variable;
+ boolean parent_is_class;
+
+ vStringClear(name);
+ while (isIdentifierCharacter((int)*start)) {
+ vStringPut(name, (int)*start);
+ ++start;
+ }
+ vStringTerminate(name);
+
+ parent_is_class = constructParentString(nesting_levels, indent, parent);
+ /* skip variables in methods */
+ if (!parent_is_class && vStringLength(parent) > 0) continue;
+
+ makeVariableTag(name, parent);
+ }
+ /* Find and parse imports */
+ parseImports(line);
+ }
+ /* Clean up all memory we allocated. */
+ vStringDelete(parent);
+ vStringDelete(name);
+ vStringDelete(continuation);
+ nestingLevelsFree(nesting_levels);
+}
+
+extern parserDefinition *PythonParser(void) {
+ static const char *const extensions[] = {"py", "pyx", "pxd",
+ "pxi", "scons", NULL};
+ parserDefinition *def = parserNew("Python");
+ def->kinds = PythonKinds;
+ def->kindCount = KIND_COUNT(PythonKinds);
+ def->extensions = extensions;
+ def->parser = findPythonTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/read.c b/third_party/ctags/read.c
new file mode 100644
index 000000000..9fb5549a2
--- /dev/null
+++ b/third_party/ctags/read.c
@@ -0,0 +1,473 @@
+/*
+ * $Id: read.c 769 2010-09-11 21:00:16Z dhiebert $
+ *
+ * Copyright (c) 1996-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains low level source and tag file read functions (newline
+ * conversion for source files are performed at this level).
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#define FILE_WRITE
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/main.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+inputFile File; /* globally read through macros */
+static fpos_t StartOfLine; /* holds deferred position of start of line */
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+extern void freeSourceFileResources(void) {
+ if (File.name != NULL) vStringDelete(File.name);
+ if (File.path != NULL) vStringDelete(File.path);
+ if (File.source.name != NULL) vStringDelete(File.source.name);
+ if (File.source.tagPath != NULL) eFree(File.source.tagPath);
+ if (File.line != NULL) vStringDelete(File.line);
+}
+
+/*
+ * Source file access functions
+ */
+
+static void setInputFileName(const char *const fileName) {
+ const char *const head = fileName;
+ const char *const tail = baseFilename(head);
+
+ if (File.name != NULL) vStringDelete(File.name);
+ File.name = vStringNewInit(fileName);
+
+ if (File.path != NULL) vStringDelete(File.path);
+ if (tail == head)
+ File.path = NULL;
+ else {
+ const size_t length = tail - head - 1;
+ File.path = vStringNew();
+ vStringNCopyS(File.path, fileName, length);
+ }
+}
+
+static void setSourceFileParameters(vString *const fileName) {
+ if (File.source.name != NULL) vStringDelete(File.source.name);
+ File.source.name = fileName;
+
+ if (File.source.tagPath != NULL) eFree(File.source.tagPath);
+ if (!Option.tagRelative || isAbsolutePath(vStringValue(fileName)))
+ File.source.tagPath = eStrdup(vStringValue(fileName));
+ else
+ File.source.tagPath =
+ relativeFilename(vStringValue(fileName), TagFile.directory);
+
+ if (vStringLength(fileName) > TagFile.max.file)
+ TagFile.max.file = vStringLength(fileName);
+
+ File.source.isHeader = isIncludeFile(vStringValue(fileName));
+ File.source.language = getFileLanguage(vStringValue(fileName));
+}
+
+static boolean setSourceFileName(vString *const fileName) {
+ boolean result = FALSE;
+ if (getFileLanguage(vStringValue(fileName)) != LANG_IGNORE) {
+ vString *pathName;
+ if (isAbsolutePath(vStringValue(fileName)) || File.path == NULL)
+ pathName = vStringNewCopy(fileName);
+ else
+ pathName =
+ combinePathAndFile(vStringValue(File.path), vStringValue(fileName));
+ setSourceFileParameters(pathName);
+ result = TRUE;
+ }
+ return result;
+}
+
+/*
+ * Line directive parsing
+ */
+
+static int skipWhite(void) {
+ int c;
+ do
+ c = getc(File.fp);
+ while (c == ' ' || c == '\t');
+ return c;
+}
+
+static unsigned long readLineNumber(void) {
+ unsigned long lNum = 0;
+ int c = skipWhite();
+ while (c != EOF && isdigit(c)) {
+ lNum = (lNum * 10) + (c - '0');
+ c = getc(File.fp);
+ }
+ ungetc(c, File.fp);
+ if (c != ' ' && c != '\t') lNum = 0;
+
+ return lNum;
+}
+
+/* While ANSI only permits lines of the form:
+ * # line n "filename"
+ * Earlier compilers generated lines of the form
+ * # n filename
+ * GNU C will output lines of the form:
+ * # n "filename"
+ * So we need to be fairly flexible in what we accept.
+ */
+static vString *readFileName(void) {
+ vString *const fileName = vStringNew();
+ boolean quoteDelimited = FALSE;
+ int c = skipWhite();
+
+ if (c == '"') {
+ c = getc(File.fp); /* skip double-quote */
+ quoteDelimited = TRUE;
+ }
+ while (c != EOF && c != '\n' &&
+ (quoteDelimited ? (c != '"') : (c != ' ' && c != '\t'))) {
+ vStringPut(fileName, c);
+ c = getc(File.fp);
+ }
+ if (c == '\n') ungetc(c, File.fp);
+ vStringPut(fileName, '\0');
+
+ return fileName;
+}
+
+static boolean parseLineDirective(void) {
+ boolean result = FALSE;
+ int c = skipWhite();
+ DebugStatement(const char *lineStr = "";)
+
+ if (isdigit(c)) {
+ ungetc(c, File.fp);
+ result = TRUE;
+ }
+ else if (c == 'l' && getc(File.fp) == 'i' && getc(File.fp) == 'n' &&
+ getc(File.fp) == 'e') {
+ c = getc(File.fp);
+ if (c == ' ' || c == '\t') {
+ DebugStatement(lineStr = "line";) result = TRUE;
+ }
+ }
+ if (result) {
+ const unsigned long lNum = readLineNumber();
+ if (lNum == 0)
+ result = FALSE;
+ else {
+ vString *const fileName = readFileName();
+ if (vStringLength(fileName) == 0) {
+ File.source.lineNumber = lNum - 1; /* applies to NEXT line */
+ DebugStatement(debugPrintf(DEBUG_RAW, "#%s %ld", lineStr, lNum);)
+ } else if (setSourceFileName(fileName)) {
+ File.source.lineNumber = lNum - 1; /* applies to NEXT line */
+ DebugStatement(debugPrintf(DEBUG_RAW, "#%s %ld \"%s\"", lineStr, lNum,
+ vStringValue(fileName));)
+ }
+
+ if (Option.include.fileNames && vStringLength(fileName) > 0 &&
+ lNum == 1) {
+ tagEntryInfo tag;
+ initTagEntry(&tag, baseFilename(vStringValue(fileName)));
+
+ tag.isFileEntry = TRUE;
+ tag.lineNumberEntry = TRUE;
+ tag.lineNumber = 1;
+ tag.kindName = "file";
+ tag.kind = 'F';
+
+ makeTagEntry(&tag);
+ }
+ vStringDelete(fileName);
+ result = TRUE;
+ }
+ }
+ return result;
+}
+
+/*
+ * Source file I/O operations
+ */
+
+/* This function opens a source file, and resets the line counter. If it
+ * fails, it will display an error message and leave the File.fp set to NULL.
+ */
+extern boolean fileOpen(const char *const fileName, const langType language) {
+#ifdef VMS
+ const char *const openMode = "r";
+#else
+ const char *const openMode = "rb";
+#endif
+ boolean opened = FALSE;
+
+ /* If another file was already open, then close it.
+ */
+ if (File.fp != NULL) {
+ fclose(File.fp); /* close any open source file */
+ File.fp = NULL;
+ }
+
+ File.fp = fopen(fileName, openMode);
+ if (File.fp == NULL)
+ error(WARNING | PERROR, "cannot open \"%s\"", fileName);
+ else {
+ opened = TRUE;
+
+ setInputFileName(fileName);
+ fgetpos(File.fp, &StartOfLine);
+ fgetpos(File.fp, &File.filePosition);
+ File.currentLine = NULL;
+ File.lineNumber = 0L;
+ File.eof = FALSE;
+ File.newLine = TRUE;
+
+ if (File.line != NULL) vStringClear(File.line);
+
+ setSourceFileParameters(vStringNewInit(fileName));
+ File.source.lineNumber = 0L;
+
+ verbose("OPENING %s as %s language %sfile\n", fileName,
+ getLanguageName(language), File.source.isHeader ? "include " : "");
+ }
+ return opened;
+}
+
+extern void fileClose(void) {
+ if (File.fp != NULL) {
+ /* The line count of the file is 1 too big, since it is one-based
+ * and is incremented upon each newline.
+ */
+ if (Option.printTotals) {
+ fileStatus *status = eStat(vStringValue(File.name));
+ addTotals(0, File.lineNumber - 1L, status->size);
+ }
+ fclose(File.fp);
+ File.fp = NULL;
+ }
+}
+
+extern boolean fileEOF(void) {
+ return File.eof;
+}
+
+/* Action to take for each encountered source newline.
+ */
+static void fileNewline(void) {
+ File.filePosition = StartOfLine;
+ File.newLine = FALSE;
+ File.lineNumber++;
+ File.source.lineNumber++;
+ DebugStatement(if (Option.breakLine == File.lineNumber) lineBreak();)
+ DebugStatement(debugPrintf(DEBUG_RAW, "%6ld: ", File.lineNumber);)
+}
+
+/* This function reads a single character from the stream, performing newline
+ * canonicalization.
+ */
+static int iFileGetc(void) {
+ int c;
+readnext:
+ c = getc(File.fp);
+
+ /* If previous character was a newline, then we're starting a line.
+ */
+ if (File.newLine && c != EOF) {
+ fileNewline();
+ if (c == '#' && Option.lineDirectives) {
+ if (parseLineDirective())
+ goto readnext;
+ else {
+ fsetpos(File.fp, &StartOfLine);
+ c = getc(File.fp);
+ }
+ }
+ }
+
+ if (c == EOF)
+ File.eof = TRUE;
+ else if (c == NEWLINE) {
+ File.newLine = TRUE;
+ fgetpos(File.fp, &StartOfLine);
+ } else if (c == CRETURN) {
+ /* Turn line breaks into a canonical form. The three commonly
+ * used forms if line breaks: LF (UNIX/Mac OS X), CR (Mac OS 9),
+ * and CR-LF (MS-DOS) are converted into a generic newline.
+ */
+#ifndef macintosh
+ const int next = getc(File.fp); /* is CR followed by LF? */
+ if (next != NEWLINE)
+ ungetc(next, File.fp);
+ else
+#endif
+ {
+ c = NEWLINE; /* convert CR into newline */
+ File.newLine = TRUE;
+ fgetpos(File.fp, &StartOfLine);
+ }
+ }
+ DebugStatement(debugPutc(DEBUG_RAW, c);) return c;
+}
+
+extern void fileUngetc(int c) {
+ File.ungetch = c;
+}
+
+static vString *iFileGetLine(void) {
+ vString *result = NULL;
+ int c;
+ if (File.line == NULL) File.line = vStringNew();
+ vStringClear(File.line);
+ do {
+ c = iFileGetc();
+ if (c != EOF) vStringPut(File.line, c);
+ if (c == '\n' || (c == EOF && vStringLength(File.line) > 0)) {
+ vStringTerminate(File.line);
+#ifdef HAVE_REGEX
+ if (vStringLength(File.line) > 0)
+ matchRegex(File.line, File.source.language);
+#endif
+ result = File.line;
+ break;
+ }
+ } while (c != EOF);
+ Assert(result != NULL || File.eof);
+ return result;
+}
+
+/* Do not mix use of fileReadLine () and fileGetc () for the same file.
+ */
+extern int fileGetc(void) {
+ int c;
+
+ /* If there is an ungotten character, then return it. Don't do any
+ * other processing on it, though, because we already did that the
+ * first time it was read through fileGetc ().
+ */
+ if (File.ungetch != '\0') {
+ c = File.ungetch;
+ File.ungetch = '\0';
+ return c; /* return here to avoid re-calling debugPutc () */
+ }
+ do {
+ if (File.currentLine != NULL) {
+ c = *File.currentLine++;
+ if (c == '\0') File.currentLine = NULL;
+ } else {
+ vString *const line = iFileGetLine();
+ if (line != NULL) File.currentLine = (unsigned char *)vStringValue(line);
+ if (File.currentLine == NULL)
+ c = EOF;
+ else
+ c = '\0';
+ }
+ } while (c == '\0');
+ DebugStatement(debugPutc(DEBUG_READ, c);) return c;
+}
+
+extern int fileSkipToCharacter(int c) {
+ int d;
+ do {
+ d = fileGetc();
+ } while (d != EOF && d != c);
+ return d;
+}
+
+/* An alternative interface to fileGetc (). Do not mix use of fileReadLine()
+ * and fileGetc() for the same file. The returned string does not contain
+ * the terminating newline. A NULL return value means that all lines in the
+ * file have been read and we are at the end of file.
+ */
+extern const unsigned char *fileReadLine(void) {
+ vString *const line = iFileGetLine();
+ const unsigned char *result = NULL;
+ if (line != NULL) {
+ result = (const unsigned char *)vStringValue(line);
+ vStringStripNewline(line);
+ DebugStatement(debugPrintf(DEBUG_READ, "%s\n", result);)
+ }
+ return result;
+}
+
+/*
+ * Source file line reading with automatic buffer sizing
+ */
+extern char *readLine(vString *const vLine, FILE *const fp) {
+ char *result = NULL;
+
+ vStringClear(vLine);
+ if (fp == NULL) /* to free memory allocated to buffer */
+ error(FATAL, "NULL file pointer");
+ else {
+ boolean reReadLine;
+
+ /* If reading the line places any character other than a null or a
+ * newline at the last character position in the buffer (one less
+ * than the buffer size), then we must resize the buffer and
+ * reattempt to read the line.
+ */
+ do {
+ char *const pLastChar = vStringValue(vLine) + vStringSize(vLine) - 2;
+ fpos_t startOfLine;
+
+ fgetpos(fp, &startOfLine);
+ reReadLine = FALSE;
+ *pLastChar = '\0';
+ result = fgets(vStringValue(vLine), (int)vStringSize(vLine), fp);
+ if (result == NULL) {
+ if (!feof(fp)) error(FATAL | PERROR, "Failure on attempt to read file");
+ } else if (*pLastChar != '\0' && *pLastChar != '\n' &&
+ *pLastChar != '\r') {
+ /* buffer overflow */
+ reReadLine = vStringAutoResize(vLine);
+ if (reReadLine)
+ fsetpos(fp, &startOfLine);
+ else
+ error(FATAL | PERROR, "input line too big; out of memory");
+ } else {
+ char *eol;
+ vStringSetLength(vLine);
+ /* canonicalize new line */
+ eol = vStringValue(vLine) + vStringLength(vLine) - 1;
+ if (*eol == '\r')
+ *eol = '\n';
+ else if (*(eol - 1) == '\r' && *eol == '\n') {
+ *(eol - 1) = '\n';
+ *eol = '\0';
+ --vLine->length;
+ }
+ }
+ } while (reReadLine);
+ }
+ return result;
+}
+
+/* Places into the line buffer the contents of the line referenced by
+ * "location".
+ */
+extern char *readSourceLine(vString *const vLine, fpos_t location,
+ long *const pSeekValue) {
+ fpos_t orignalPosition;
+ char *result;
+
+ fgetpos(File.fp, &orignalPosition);
+ fsetpos(File.fp, &location);
+ if (pSeekValue != NULL) *pSeekValue = ftell(File.fp);
+ result = readLine(vLine, File.fp);
+ if (result == NULL)
+ error(FATAL, "Unexpected end of file: %s", vStringValue(File.name));
+ fsetpos(File.fp, &orignalPosition);
+
+ return result;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/read.h b/third_party/ctags/read.h
new file mode 100644
index 000000000..9046a7bc2
--- /dev/null
+++ b/third_party/ctags/read.h
@@ -0,0 +1,98 @@
+#ifndef _READ_H
+#define _READ_H
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/stdio/stdio.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/vstring.h"
+
+#if defined(FILE_WRITE) || defined(VAXC)
+#define CONST_FILE
+#else
+#define CONST_FILE const
+#endif
+
+/*
+ * MACROS
+ */
+#define getInputLineNumber() File.lineNumber
+#define getInputFileName() vStringValue(File.source.name)
+#define getInputFilePosition() File.filePosition
+#define getSourceFileName() vStringValue(File.source.name)
+#define getSourceFileTagPath() File.source.tagPath
+#define getSourceLanguage() File.source.language
+#define getSourceLanguageName() getLanguageName(File.source.language)
+#define getSourceLineNumber() File.source.lineNumber
+#define isLanguage(lang) (boolean)((lang) == File.source.language)
+#define isHeaderFile() File.source.isHeader
+
+/*
+ * DATA DECLARATIONS
+ */
+
+enum eCharacters {
+ /* white space characters */
+ SPACE = ' ',
+ NEWLINE = '\n',
+ CRETURN = '\r',
+ FORMFEED = '\f',
+ TAB = '\t',
+ VTAB = '\v',
+
+ /* some hard to read characters */
+ DOUBLE_QUOTE = '"',
+ SINGLE_QUOTE = '\'',
+ BACKSLASH = '\\',
+
+ STRING_SYMBOL = ('S' + 0x80),
+ CHAR_SYMBOL = ('C' + 0x80)
+};
+
+/* Maintains the state of the current source file.
+ */
+typedef struct sInputFile {
+ vString *name; /* name of input file */
+ vString *path; /* path of input file (if any) */
+ vString *line; /* last line read from file */
+ const unsigned char *currentLine; /* current line being worked on */
+ FILE *fp; /* stream used for reading the file */
+ unsigned long lineNumber; /* line number in the input file */
+ fpos_t filePosition; /* file position of current line */
+ int ungetch; /* a single character that was ungotten */
+ boolean eof; /* have we reached the end of file? */
+ boolean newLine; /* will the next character begin a new line? */
+
+ /* Contains data pertaining to the original source file in which the tag
+ * was defined. This may be different from the input file when #line
+ * directives are processed (i.e. the input file is preprocessor output).
+ */
+ struct sSource {
+ vString *name; /* name to report for source file */
+ char *tagPath; /* path of source file relative to tag file */
+ unsigned long lineNumber; /* line number in the source file */
+ boolean isHeader; /* is source file a header file? */
+ langType language; /* language of source file */
+ } source;
+} inputFile;
+
+/*
+ * GLOBAL VARIABLES
+ */
+extern CONST_FILE inputFile File;
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+extern void freeSourceFileResources(void);
+extern boolean fileOpen(const char *const fileName, const langType language);
+extern boolean fileEOF(void);
+extern void fileClose(void);
+extern int fileGetc(void);
+extern int fileSkipToCharacter(int c);
+extern void fileUngetc(int c);
+extern const unsigned char *fileReadLine(void);
+extern char *readLine(vString *const vLine, FILE *const fp);
+extern char *readSourceLine(vString *const vLine, fpos_t location,
+ long *const pSeekValue);
+
+#endif /* _READ_H */
diff --git a/third_party/ctags/readtags.c b/third_party/ctags/readtags.c
new file mode 100644
index 000000000..646a51b55
--- /dev/null
+++ b/third_party/ctags/readtags.c
@@ -0,0 +1,805 @@
+/*
+ * $Id: readtags.c 592 2007-07-31 03:30:41Z dhiebert $
+ *
+ * Copyright (c) 1996-2003, Darren Hiebert
+ *
+ * This source code is released into the public domain.
+ *
+ * This module contains functions for reading tag files.
+ */
+#include "libc/calls/calls.h"
+#include "libc/calls/weirdtypes.h"
+#include "libc/conv/conv.h"
+#include "libc/errno.h"
+#include "libc/log/log.h"
+#include "libc/mem/mem.h"
+#include "libc/stdio/stdio.h"
+#include "libc/str/str.h"
+#include "third_party/ctags/readtags.h"
+
+/*
+ * MACROS
+ */
+#define TAB '\t'
+
+/*
+ * DATA DECLARATIONS
+ */
+typedef struct {
+ size_t size;
+ char *buffer;
+} vstring;
+
+/* Information about current tag file */
+struct sTagFile {
+ /* has the file been opened and this structure initialized? */
+ short initialized;
+ /* format of tag file */
+ short format;
+ /* how is the tag file sorted? */
+ sortType sortMethod;
+ /* pointer to file structure */
+ FILE *fp;
+ /* file position of first character of `line' */
+ off_t pos;
+ /* size of tag file in seekable positions */
+ off_t size;
+ /* last line read */
+ vstring line;
+ /* name of tag in last line read */
+ vstring name;
+ /* defines tag search state */
+ struct {
+ /* file position of last match for tag */
+ off_t pos;
+ /* name of tag last searched for */
+ char *name;
+ /* length of name for partial matches */
+ size_t nameLength;
+ /* peforming partial match */
+ short partial;
+ /* ignoring case */
+ short ignorecase;
+ } search;
+ /* miscellaneous extension fields */
+ struct {
+ /* number of entries in `list' */
+ unsigned short max;
+ /* list of key value pairs */
+ tagExtensionField *list;
+ } fields;
+ /* buffers to be freed at close */
+ struct {
+ /* name of program author */
+ char *author;
+ /* name of program */
+ char *name;
+ /* URL of distribution */
+ char *url;
+ /* program version */
+ char *version;
+ } program;
+};
+
+/*
+ * DATA DEFINITIONS
+ */
+const char *const EmptyString = "";
+const char *const PseudoTagPrefix = "!_";
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+/*
+ * Compare two strings, ignoring case.
+ * Return 0 for match, < 0 for smaller, > 0 for bigger
+ * Make sure case is folded to uppercase in comparison (like for 'sort -f')
+ * This makes a difference when one of the chars lies between upper and lower
+ * ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !)
+ */
+static int struppercmp(const char *s1, const char *s2) {
+ int result;
+ do {
+ result = toupper((int)*s1) - toupper((int)*s2);
+ } while (result == 0 && *s1++ != '\0' && *s2++ != '\0');
+ return result;
+}
+
+static int strnuppercmp(const char *s1, const char *s2, size_t n) {
+ int result;
+ do {
+ result = toupper((int)*s1) - toupper((int)*s2);
+ } while (result == 0 && --n > 0 && *s1++ != '\0' && *s2++ != '\0');
+ return result;
+}
+
+static int growString(vstring *s) {
+ int result = 0;
+ size_t newLength;
+ char *newLine;
+ if (s->size == 0) {
+ newLength = 128;
+ newLine = (char *)malloc(newLength);
+ *newLine = '\0';
+ } else {
+ newLength = 2 * s->size;
+ newLine = (char *)realloc(s->buffer, newLength);
+ }
+ if (newLine == NULL)
+ perror("string too large");
+ else {
+ s->buffer = newLine;
+ s->size = newLength;
+ result = 1;
+ }
+ return result;
+}
+
+/* Copy name of tag out of tag line */
+static void copyName(tagFile *const file) {
+ size_t length;
+ const char *end = strchr(file->line.buffer, '\t');
+ if (end == NULL) {
+ end = strchr(file->line.buffer, '\n');
+ if (end == NULL) end = strchr(file->line.buffer, '\r');
+ }
+ if (end != NULL)
+ length = end - file->line.buffer;
+ else
+ length = strlen(file->line.buffer);
+ while (length >= file->name.size) growString(&file->name);
+ strncpy(file->name.buffer, file->line.buffer, length);
+ file->name.buffer[length] = '\0';
+}
+
+static int readTagLineRaw(tagFile *const file) {
+ int result = 1;
+ int reReadLine;
+
+ /* If reading the line places any character other than a null or a
+ * newline at the last character position in the buffer (one less than
+ * the buffer size), then we must resize the buffer and reattempt to read
+ * the line.
+ */
+ do {
+ char *const pLastChar = file->line.buffer + file->line.size - 2;
+ char *line;
+
+ file->pos = ftell(file->fp);
+ reReadLine = 0;
+ *pLastChar = '\0';
+ line = fgets(file->line.buffer, (int)file->line.size, file->fp);
+ if (line == NULL) {
+ /* read error */
+ if (!feof(file->fp)) perror("readTagLine");
+ result = 0;
+ } else if (*pLastChar != '\0' && *pLastChar != '\n' && *pLastChar != '\r') {
+ /* buffer overflow */
+ growString(&file->line);
+ fseek(file->fp, file->pos, SEEK_SET);
+ reReadLine = 1;
+ } else {
+ size_t i = strlen(file->line.buffer);
+ while (i > 0 && (file->line.buffer[i - 1] == '\n' ||
+ file->line.buffer[i - 1] == '\r')) {
+ file->line.buffer[i - 1] = '\0';
+ --i;
+ }
+ }
+ } while (reReadLine && result);
+ if (result) copyName(file);
+ return result;
+}
+
+static int readTagLine(tagFile *const file) {
+ int result;
+ do {
+ result = readTagLineRaw(file);
+ } while (result && *file->name.buffer == '\0');
+ return result;
+}
+
+static tagResult growFields(tagFile *const file) {
+ tagResult result = TagFailure;
+ unsigned short newCount = (unsigned short)2 * file->fields.max;
+ tagExtensionField *newFields = (tagExtensionField *)realloc(
+ file->fields.list, newCount * sizeof(tagExtensionField));
+ if (newFields == NULL)
+ perror("too many extension fields");
+ else {
+ file->fields.list = newFields;
+ file->fields.max = newCount;
+ result = TagSuccess;
+ }
+ return result;
+}
+
+static void parseExtensionFields(tagFile *const file, tagEntry *const entry,
+ char *const string) {
+ char *p = string;
+ while (p != NULL && *p != '\0') {
+ while (*p == TAB) *p++ = '\0';
+ if (*p != '\0') {
+ char *colon;
+ char *field = p;
+ p = strchr(p, TAB);
+ if (p != NULL) *p++ = '\0';
+ colon = strchr(field, ':');
+ if (colon == NULL)
+ entry->kind = field;
+ else {
+ const char *key = field;
+ const char *value = colon + 1;
+ *colon = '\0';
+ if (strcmp(key, "kind") == 0)
+ entry->kind = value;
+ else if (strcmp(key, "file") == 0)
+ entry->fileScope = 1;
+ else if (strcmp(key, "line") == 0)
+ entry->address.lineNumber = atol(value);
+ else {
+ if (entry->fields.count == file->fields.max) growFields(file);
+ file->fields.list[entry->fields.count].key = key;
+ file->fields.list[entry->fields.count].value = value;
+ ++entry->fields.count;
+ }
+ }
+ }
+ }
+}
+
+static void parseTagLine(tagFile *file, tagEntry *const entry) {
+ int i;
+ char *p = file->line.buffer;
+ char *tab = strchr(p, TAB);
+
+ entry->fields.list = NULL;
+ entry->fields.count = 0;
+ entry->kind = NULL;
+ entry->fileScope = 0;
+
+ entry->name = p;
+ if (tab != NULL) {
+ *tab = '\0';
+ p = tab + 1;
+ entry->file = p;
+ tab = strchr(p, TAB);
+ if (tab != NULL) {
+ int fieldsPresent;
+ *tab = '\0';
+ p = tab + 1;
+ if (*p == '/' || *p == '?') {
+ /* parse pattern */
+ int delimiter = *(unsigned char *)p;
+ entry->address.lineNumber = 0;
+ entry->address.pattern = p;
+ do {
+ p = strchr(p + 1, delimiter);
+ } while (p != NULL && *(p - 1) == '\\');
+ if (p == NULL) {
+ /* invalid pattern */
+ } else
+ ++p;
+ } else if (isdigit((int)*(unsigned char *)p)) {
+ /* parse line number */
+ entry->address.pattern = p;
+ entry->address.lineNumber = atol(p);
+ while (isdigit((int)*(unsigned char *)p)) ++p;
+ } else {
+ /* invalid pattern */
+ }
+ fieldsPresent = (strncmp(p, ";\"", 2) == 0);
+ *p = '\0';
+ if (fieldsPresent) parseExtensionFields(file, entry, p + 2);
+ }
+ }
+ if (entry->fields.count > 0) entry->fields.list = file->fields.list;
+ for (i = entry->fields.count; i < file->fields.max; ++i) {
+ file->fields.list[i].key = NULL;
+ file->fields.list[i].value = NULL;
+ }
+}
+
+static char *duplicate(const char *str) {
+ char *result = NULL;
+ if (str != NULL) {
+ result = strdup(str);
+ if (result == NULL) perror(NULL);
+ }
+ return result;
+}
+
+static void readPseudoTags(tagFile *const file, tagFileInfo *const info) {
+ fpos_t startOfLine;
+ const size_t prefixLength = strlen(PseudoTagPrefix);
+ if (info != NULL) {
+ info->file.format = 1;
+ info->file.sort = TAG_UNSORTED;
+ info->program.author = NULL;
+ info->program.name = NULL;
+ info->program.url = NULL;
+ info->program.version = NULL;
+ }
+ while (1) {
+ fgetpos(file->fp, &startOfLine);
+ if (!readTagLine(file)) break;
+ if (strncmp(file->line.buffer, PseudoTagPrefix, prefixLength) != 0)
+ break;
+ else {
+ tagEntry entry;
+ const char *key, *value;
+ parseTagLine(file, &entry);
+ key = entry.name + prefixLength;
+ value = entry.file;
+ if (strcmp(key, "TAG_FILE_SORTED") == 0)
+ file->sortMethod = (sortType)atoi(value);
+ else if (strcmp(key, "TAG_FILE_FORMAT") == 0)
+ file->format = (short)atoi(value);
+ else if (strcmp(key, "TAG_PROGRAM_AUTHOR") == 0)
+ file->program.author = duplicate(value);
+ else if (strcmp(key, "TAG_PROGRAM_NAME") == 0)
+ file->program.name = duplicate(value);
+ else if (strcmp(key, "TAG_PROGRAM_URL") == 0)
+ file->program.url = duplicate(value);
+ else if (strcmp(key, "TAG_PROGRAM_VERSION") == 0)
+ file->program.version = duplicate(value);
+ if (info != NULL) {
+ info->file.format = file->format;
+ info->file.sort = file->sortMethod;
+ info->program.author = file->program.author;
+ info->program.name = file->program.name;
+ info->program.url = file->program.url;
+ info->program.version = file->program.version;
+ }
+ }
+ }
+ fsetpos(file->fp, &startOfLine);
+}
+
+static void gotoFirstLogicalTag(tagFile *const file) {
+ fpos_t startOfLine;
+ const size_t prefixLength = strlen(PseudoTagPrefix);
+ rewind(file->fp);
+ while (1) {
+ fgetpos(file->fp, &startOfLine);
+ if (!readTagLine(file)) break;
+ if (strncmp(file->line.buffer, PseudoTagPrefix, prefixLength) != 0) break;
+ }
+ fsetpos(file->fp, &startOfLine);
+}
+
+static tagFile *initialize(const char *const filePath,
+ tagFileInfo *const info) {
+ tagFile *result = (tagFile *)calloc((size_t)1, sizeof(tagFile));
+ if (result != NULL) {
+ growString(&result->line);
+ growString(&result->name);
+ result->fields.max = 20;
+ result->fields.list = (tagExtensionField *)calloc(
+ result->fields.max, sizeof(tagExtensionField));
+ result->fp = fopen(filePath, "r");
+ if (result->fp == NULL) {
+ free(result);
+ result = NULL;
+ info->status.error_number = errno;
+ } else {
+ fseek(result->fp, 0, SEEK_END);
+ result->size = ftell(result->fp);
+ rewind(result->fp);
+ readPseudoTags(result, info);
+ info->status.opened = 1;
+ result->initialized = 1;
+ }
+ }
+ return result;
+}
+
+static void terminate(tagFile *const file) {
+ fclose(file->fp);
+
+ free(file->line.buffer);
+ free(file->name.buffer);
+ free(file->fields.list);
+
+ if (file->program.author != NULL) free(file->program.author);
+ if (file->program.name != NULL) free(file->program.name);
+ if (file->program.url != NULL) free(file->program.url);
+ if (file->program.version != NULL) free(file->program.version);
+ if (file->search.name != NULL) free(file->search.name);
+
+ memset(file, 0, sizeof(tagFile));
+
+ free(file);
+}
+
+static tagResult readNext(tagFile *const file, tagEntry *const entry) {
+ tagResult result;
+ if (file == NULL || !file->initialized)
+ result = TagFailure;
+ else if (!readTagLine(file))
+ result = TagFailure;
+ else {
+ if (entry != NULL) parseTagLine(file, entry);
+ result = TagSuccess;
+ }
+ return result;
+}
+
+static const char *readFieldValue(const tagEntry *const entry,
+ const char *const key) {
+ const char *result = NULL;
+ int i;
+ if (strcmp(key, "kind") == 0)
+ result = entry->kind;
+ else if (strcmp(key, "file") == 0)
+ result = EmptyString;
+ else
+ for (i = 0; i < entry->fields.count && result == NULL; ++i)
+ if (strcmp(entry->fields.list[i].key, key) == 0)
+ result = entry->fields.list[i].value;
+ return result;
+}
+
+static int readTagLineSeek(tagFile *const file, const off_t pos) {
+ int result = 0;
+ if (fseek(file->fp, pos, SEEK_SET) == 0) {
+ result = readTagLine(file); /* read probable partial line */
+ if (pos > 0 && result) result = readTagLine(file); /* read complete line */
+ }
+ return result;
+}
+
+static int nameComparison(tagFile *const file) {
+ int result;
+ if (file->search.ignorecase) {
+ if (file->search.partial)
+ result = strnuppercmp(file->search.name, file->name.buffer,
+ file->search.nameLength);
+ else
+ result = struppercmp(file->search.name, file->name.buffer);
+ } else {
+ if (file->search.partial)
+ result = strncmp(file->search.name, file->name.buffer,
+ file->search.nameLength);
+ else
+ result = strcmp(file->search.name, file->name.buffer);
+ }
+ return result;
+}
+
+static void findFirstNonMatchBefore(tagFile *const file) {
+#define JUMP_BACK 512
+ int more_lines;
+ int comp;
+ off_t start = file->pos;
+ off_t pos = start;
+ do {
+ if (pos < (off_t)JUMP_BACK)
+ pos = 0;
+ else
+ pos = pos - JUMP_BACK;
+ more_lines = readTagLineSeek(file, pos);
+ comp = nameComparison(file);
+ } while (more_lines && comp == 0 && pos > 0 && pos < start);
+}
+
+static tagResult findFirstMatchBefore(tagFile *const file) {
+ tagResult result = TagFailure;
+ int more_lines;
+ off_t start = file->pos;
+ findFirstNonMatchBefore(file);
+ do {
+ more_lines = readTagLine(file);
+ if (nameComparison(file) == 0) result = TagSuccess;
+ } while (more_lines && result != TagSuccess && file->pos < start);
+ return result;
+}
+
+static tagResult findBinary(tagFile *const file) {
+ tagResult result = TagFailure;
+ off_t lower_limit = 0;
+ off_t upper_limit = file->size;
+ off_t last_pos = 0;
+ off_t pos = upper_limit / 2;
+ while (result != TagSuccess) {
+ if (!readTagLineSeek(file, pos)) {
+ /* in case we fell off end of file */
+ result = findFirstMatchBefore(file);
+ break;
+ } else if (pos == last_pos) {
+ /* prevent infinite loop if we backed up to beginning of file */
+ break;
+ } else {
+ const int comp = nameComparison(file);
+ last_pos = pos;
+ if (comp < 0) {
+ upper_limit = pos;
+ pos = lower_limit + ((upper_limit - lower_limit) / 2);
+ } else if (comp > 0) {
+ lower_limit = pos;
+ pos = lower_limit + ((upper_limit - lower_limit) / 2);
+ } else if (pos == 0)
+ result = TagSuccess;
+ else
+ result = findFirstMatchBefore(file);
+ }
+ }
+ return result;
+}
+
+static tagResult findSequential(tagFile *const file) {
+ tagResult result = TagFailure;
+ if (file->initialized) {
+ while (result == TagFailure && readTagLine(file)) {
+ if (nameComparison(file) == 0) result = TagSuccess;
+ }
+ }
+ return result;
+}
+
+static tagResult find(tagFile *const file, tagEntry *const entry,
+ const char *const name, const int options) {
+ tagResult result;
+ if (file->search.name != NULL) free(file->search.name);
+ file->search.name = duplicate(name);
+ file->search.nameLength = strlen(name);
+ file->search.partial = (options & TAG_PARTIALMATCH) != 0;
+ file->search.ignorecase = (options & TAG_IGNORECASE) != 0;
+ fseek(file->fp, 0, SEEK_END);
+ file->size = ftell(file->fp);
+ rewind(file->fp);
+ if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) ||
+ (file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase)) {
+#ifdef DEBUG
+ printf("\n");
+#endif
+ result = findBinary(file);
+ } else {
+#ifdef DEBUG
+ printf("\n");
+#endif
+ result = findSequential(file);
+ }
+
+ if (result != TagSuccess)
+ file->search.pos = file->size;
+ else {
+ file->search.pos = file->pos;
+ if (entry != NULL) parseTagLine(file, entry);
+ }
+ return result;
+}
+
+static tagResult findNext(tagFile *const file, tagEntry *const entry) {
+ tagResult result;
+ if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) ||
+ (file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase)) {
+ result = tagsNext(file, entry);
+ if (result == TagSuccess && nameComparison(file) != 0) result = TagFailure;
+ } else {
+ result = findSequential(file);
+ if (result == TagSuccess && entry != NULL) parseTagLine(file, entry);
+ }
+ return result;
+}
+
+/*
+ * EXTERNAL INTERFACE
+ */
+
+extern tagFile *tagsOpen(const char *const filePath, tagFileInfo *const info) {
+ return initialize(filePath, info);
+}
+
+extern tagResult tagsSetSortType(tagFile *const file, const sortType type) {
+ tagResult result = TagFailure;
+ if (file != NULL && file->initialized) {
+ file->sortMethod = type;
+ result = TagSuccess;
+ }
+ return result;
+}
+
+extern tagResult tagsFirst(tagFile *const file, tagEntry *const entry) {
+ tagResult result = TagFailure;
+ if (file != NULL && file->initialized) {
+ gotoFirstLogicalTag(file);
+ result = readNext(file, entry);
+ }
+ return result;
+}
+
+extern tagResult tagsNext(tagFile *const file, tagEntry *const entry) {
+ tagResult result = TagFailure;
+ if (file != NULL && file->initialized) result = readNext(file, entry);
+ return result;
+}
+
+extern const char *tagsField(const tagEntry *const entry,
+ const char *const key) {
+ const char *result = NULL;
+ if (entry != NULL) result = readFieldValue(entry, key);
+ return result;
+}
+
+extern tagResult tagsFind(tagFile *const file, tagEntry *const entry,
+ const char *const name, const int options) {
+ tagResult result = TagFailure;
+ if (file != NULL && file->initialized)
+ result = find(file, entry, name, options);
+ return result;
+}
+
+extern tagResult tagsFindNext(tagFile *const file, tagEntry *const entry) {
+ tagResult result = TagFailure;
+ if (file != NULL && file->initialized) result = findNext(file, entry);
+ return result;
+}
+
+extern tagResult tagsClose(tagFile *const file) {
+ tagResult result = TagFailure;
+ if (file != NULL && file->initialized) {
+ terminate(file);
+ result = TagSuccess;
+ }
+ return result;
+}
+
+/*
+ * TEST FRAMEWORK
+ */
+
+#ifdef READTAGS_MAIN
+
+static const char *TagFileName = "tags";
+static const char *ProgramName;
+static int extensionFields;
+static int SortOverride;
+static sortType SortMethod;
+
+static void printTag(const tagEntry *entry) {
+ int i;
+ int first = 1;
+ const char *separator = ";\"";
+ const char *const empty = "";
+/* "sep" returns a value only the first time it is evaluated */
+#define sep (first ? (first = 0, separator) : empty)
+ printf("%s\t%s\t%s", entry->name, entry->file, entry->address.pattern);
+ if (extensionFields) {
+ if (entry->kind != NULL && entry->kind[0] != '\0')
+ printf("%s\tkind:%s", sep, entry->kind);
+ if (entry->fileScope) printf("%s\tfile:", sep);
+#if 0
+ if (entry->address.lineNumber > 0)
+ printf ("%s\tline:%lu", sep, entry->address.lineNumber);
+#endif
+ for (i = 0; i < entry->fields.count; ++i)
+ printf("%s\t%s:%s", sep, entry->fields.list[i].key,
+ entry->fields.list[i].value);
+ }
+ putchar('\n');
+#undef sep
+}
+
+static void findTag(const char *const name, const int options) {
+ tagFileInfo info;
+ tagEntry entry;
+ tagFile *const file = tagsOpen(TagFileName, &info);
+ if (file == NULL) {
+ fprintf(stderr, "%s: cannot open tag file: %s: %s\n", ProgramName,
+ strerror(info.status.error_number), name);
+ exit(1);
+ } else {
+ if (SortOverride) tagsSetSortType(file, SortMethod);
+ if (tagsFind(file, &entry, name, options) == TagSuccess) {
+ do {
+ printTag(&entry);
+ } while (tagsFindNext(file, &entry) == TagSuccess);
+ }
+ tagsClose(file);
+ }
+}
+
+static void listTags(void) {
+ tagFileInfo info;
+ tagEntry entry;
+ tagFile *const file = tagsOpen(TagFileName, &info);
+ if (file == NULL) {
+ fprintf(stderr, "%s: cannot open tag file: %s: %s\n", ProgramName,
+ strerror(info.status.error_number), TagFileName);
+ exit(1);
+ } else {
+ while (tagsNext(file, &entry) == TagSuccess) printTag(&entry);
+ tagsClose(file);
+ }
+}
+
+const char *const Usage =
+ "Find tag file entries matching specified names.\n\n"
+ "Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n"
+ "Options:\n"
+ " -e Include extension fields in output.\n"
+ " -i Perform case-insensitive matching.\n"
+ " -l List all tags.\n"
+ " -p Perform partial matching.\n"
+ " -s[0|1|2] Override sort detection of tag file.\n"
+ " -t file Use specified tag file (default: \"tags\").\n"
+ "Note that options are acted upon as encountered, so order is "
+ "significant.\n";
+
+extern int main(int argc, char **argv) {
+ int options = 0;
+ int actionSupplied = 0;
+ int i;
+ ProgramName = argv[0];
+ if (argc == 1) {
+ fprintf(stderr, Usage, ProgramName);
+ exit(1);
+ }
+ for (i = 1; i < argc; ++i) {
+ const char *const arg = argv[i];
+ if (arg[0] != '-') {
+ findTag(arg, options);
+ actionSupplied = 1;
+ } else {
+ size_t j;
+ for (j = 1; arg[j] != '\0'; ++j) {
+ switch (arg[j]) {
+ case 'e':
+ extensionFields = 1;
+ break;
+ case 'i':
+ options |= TAG_IGNORECASE;
+ break;
+ case 'p':
+ options |= TAG_PARTIALMATCH;
+ break;
+ case 'l':
+ listTags();
+ actionSupplied = 1;
+ break;
+
+ case 't':
+ if (arg[j + 1] != '\0') {
+ TagFileName = arg + j + 1;
+ j += strlen(TagFileName);
+ } else if (i + 1 < argc)
+ TagFileName = argv[++i];
+ else {
+ fprintf(stderr, Usage, ProgramName);
+ exit(1);
+ }
+ break;
+ case 's':
+ SortOverride = 1;
+ ++j;
+ if (arg[j] == '\0')
+ SortMethod = TAG_SORTED;
+ else if (strchr("012", arg[j]) != NULL)
+ SortMethod = (sortType)(arg[j] - '0');
+ else {
+ fprintf(stderr, Usage, ProgramName);
+ exit(1);
+ }
+ break;
+ default:
+ fprintf(stderr, "%s: unknown option: %c\n", ProgramName, arg[j]);
+ exit(1);
+ break;
+ }
+ }
+ }
+ }
+ if (!actionSupplied) {
+ fprintf(stderr,
+ "%s: no action specified: specify tag name(s) or -l option\n",
+ ProgramName);
+ exit(1);
+ }
+ return 0;
+}
+
+#endif
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/readtags.h b/third_party/ctags/readtags.h
new file mode 100644
index 000000000..e38642690
--- /dev/null
+++ b/third_party/ctags/readtags.h
@@ -0,0 +1,224 @@
+#ifndef COSMOPOLITAN_THIRD_PARTY_CTAGS_READTAGS_H_
+#define COSMOPOLITAN_THIRD_PARTY_CTAGS_READTAGS_H_
+#if !(__ASSEMBLER__ + __LINKER__ + 0)
+COSMOPOLITAN_C_START_
+
+/*
+ * MACROS
+ */
+
+/* Options for tagsSetSortType() */
+typedef enum { TAG_UNSORTED, TAG_SORTED, TAG_FOLDSORTED } sortType;
+
+/* Options for tagsFind() */
+#define TAG_FULLMATCH 0x0
+#define TAG_PARTIALMATCH 0x1
+
+#define TAG_OBSERVECASE 0x0
+#define TAG_IGNORECASE 0x2
+
+/*
+ * DATA DECLARATIONS
+ */
+
+typedef enum { TagFailure = 0, TagSuccess = 1 } tagResult;
+
+struct sTagFile;
+
+typedef struct sTagFile tagFile;
+
+/* This structure contains information about the tag file. */
+typedef struct {
+
+ struct {
+ /* was the tag file successfully opened? */
+ int opened;
+
+ /* errno value when 'opened' is false */
+ int error_number;
+ } status;
+
+ /* information about the structure of the tag file */
+ struct {
+ /* format of tag file (1 = original, 2 = extended) */
+ short format;
+
+ /* how is the tag file sorted? */
+ sortType sort;
+ } file;
+
+ /* information about the program which created this tag file */
+ struct {
+ /* name of author of generating program (may be null) */
+ const char *author;
+
+ /* name of program (may be null) */
+ const char *name;
+
+ /* URL of distribution (may be null) */
+ const char *url;
+
+ /* program version (may be null) */
+ const char *version;
+ } program;
+
+} tagFileInfo;
+
+/* This structure contains information about an extension field for a tag.
+ * These exist at the end of the tag in the form "key:value").
+ */
+typedef struct {
+
+ /* the key of the extension field */
+ const char *key;
+
+ /* the value of the extension field (may be an empty string) */
+ const char *value;
+
+} tagExtensionField;
+
+/* This structure contains information about a specific tag. */
+typedef struct {
+
+ /* name of tag */
+ const char *name;
+
+ /* path of source file containing definition of tag */
+ const char *file;
+
+ /* address for locating tag in source file */
+ struct {
+ /* pattern for locating source line
+ * (may be NULL if not present) */
+ const char *pattern;
+
+ /* line number in source file of tag definition
+ * (may be zero if not known) */
+ unsigned long lineNumber;
+ } address;
+
+ /* kind of tag (may by name, character, or NULL if not known) */
+ const char *kind;
+
+ /* is tag of file-limited scope? */
+ short fileScope;
+
+ /* miscellaneous extension fields */
+ struct {
+ /* number of entries in `list' */
+ unsigned short count;
+
+ /* list of key value pairs */
+ tagExtensionField *list;
+ } fields;
+
+} tagEntry;
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+
+/*
+ * This function must be called before calling other functions in this
+ * library. It is passed the path to the tag file to read and a (possibly
+ * null) pointer to a structure which, if not null, will be populated with
+ * information about the tag file. If successful, the function will return a
+ * handle which must be supplied to other calls to read information from the
+ * tag file, and info.status.opened will be set to true. If unsuccessful,
+ * info.status.opened will be set to false and info.status.error_number will
+ * be set to the errno value representing the system error preventing the tag
+ * file from being successfully opened.
+ */
+extern tagFile *tagsOpen(const char *const filePath, tagFileInfo *const info);
+
+/*
+ * This function allows the client to override the normal automatic detection
+ * of how a tag file is sorted. Permissible values for `type' are
+ * TAG_UNSORTED, TAG_SORTED, TAG_FOLDSORTED. Tag files in the new extended
+ * format contain a key indicating whether or not they are sorted. However,
+ * tag files in the original format do not contain such a key even when
+ * sorted, preventing this library from taking advantage of fast binary
+ * lookups. If the client knows that such an unmarked tag file is indeed
+ * sorted (or not), it can override the automatic detection. Note that
+ * incorrect lookup results will result if a tag file is marked as sorted when
+ * it actually is not. The function will return TagSuccess if called on an
+ * open tag file or TagFailure if not.
+ */
+extern tagResult tagsSetSortType(tagFile *const file, const sortType type);
+
+/*
+ * Reads the first tag in the file, if any. It is passed the handle to an
+ * opened tag file and a (possibly null) pointer to a structure which, if not
+ * null, will be populated with information about the first tag file entry.
+ * The function will return TagSuccess another tag entry is found, or
+ * TagFailure if not (i.e. it reached end of file).
+ */
+extern tagResult tagsFirst(tagFile *const file, tagEntry *const entry);
+
+/*
+ * Step to the next tag in the file, if any. It is passed the handle to an
+ * opened tag file and a (possibly null) pointer to a structure which, if not
+ * null, will be populated with information about the next tag file entry. The
+ * function will return TagSuccess another tag entry is found, or TagFailure
+ * if not (i.e. it reached end of file). It will always read the first tag in
+ * the file immediately after calling tagsOpen().
+ */
+extern tagResult tagsNext(tagFile *const file, tagEntry *const entry);
+
+/*
+ * Retrieve the value associated with the extension field for a specified key.
+ * It is passed a pointer to a structure already populated with values by a
+ * previous call to tagsNext(), tagsFind(), or tagsFindNext(), and a string
+ * containing the key of the desired extension field. If no such field of the
+ * specified key exists, the function will return null.
+ */
+extern const char *tagsField(const tagEntry *const entry,
+ const char *const key);
+
+/*
+ * Find the first tag matching `name'. The structure pointed to by `entry'
+ * will be populated with information about the tag file entry. If a tag file
+ * is sorted using the C locale, a binary search algorithm is used to search
+ * the tag file, resulting in very fast tag lookups, even in huge tag files.
+ * Various options controlling the matches can be combined by bit-wise or-ing
+ * certain values together. The available values are:
+ *
+ * TAG_PARTIALMATCH
+ * Tags whose leading characters match `name' will qualify.
+ *
+ * TAG_FULLMATCH
+ * Only tags whose full lengths match `name' will qualify.
+ *
+ * TAG_IGNORECASE
+ * Matching will be performed in a case-insenstive manner. Note that
+ * this disables binary searches of the tag file.
+ *
+ * TAG_OBSERVECASE
+ * Matching will be performed in a case-senstive manner. Note that
+ * this enables binary searches of the tag file.
+ *
+ * The function will return TagSuccess if a tag matching the name is found, or
+ * TagFailure if not.
+ */
+extern tagResult tagsFind(tagFile *const file, tagEntry *const entry,
+ const char *const name, const int options);
+
+/*
+ * Find the next tag matching the name and options supplied to the most recent
+ * call to tagsFind() for the same tag file. The structure pointed to by
+ * `entry' will be populated with information about the tag file entry. The
+ * function will return TagSuccess if another tag matching the name is found,
+ * or TagFailure if not.
+ */
+extern tagResult tagsFindNext(tagFile *const file, tagEntry *const entry);
+
+/*
+ * Call tagsTerminate() at completion of reading the tag file, which will
+ * close the file and free any internal memory allocated. The function will
+ * return TagFailure is no file is currently open, TagSuccess otherwise.
+ */
+extern tagResult tagsClose(tagFile *const file);
+
+COSMOPOLITAN_C_END_
+#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
+#endif /* COSMOPOLITAN_THIRD_PARTY_CTAGS_READTAGS_H_ */
diff --git a/third_party/ctags/rexx.c b/third_party/ctags/rexx.c
new file mode 100644
index 000000000..6ca52201f
--- /dev/null
+++ b/third_party/ctags/rexx.c
@@ -0,0 +1,34 @@
+/*
+ * $Id: rexx.c 443 2006-05-30 04:37:13Z darren $
+ *
+ * Copyright (c) 2001-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for the REXX language
+ * (http://www.rexxla.org, http://www2.hursley.ibm.com/rexx).
+ */
+#include "third_party/ctags/general.h"
+/* always include first */
+#include "third_party/ctags/parse.h"
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void installRexxRegex(const langType language) {
+ addTagRegex(language, "^([A-Za-z0-9@#$\\.!?_]+)[ \t]*:", "\\1",
+ "s,subroutine,subroutines", NULL);
+}
+
+extern parserDefinition* RexxParser(void) {
+ static const char* const extensions[] = {"cmd", "rexx", "rx", NULL};
+ parserDefinition* const def = parserNew("REXX");
+ def->extensions = extensions;
+ def->initialize = installRexxRegex;
+ def->regex = TRUE;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/routines.c b/third_party/ctags/routines.c
new file mode 100644
index 000000000..0d92b60b4
--- /dev/null
+++ b/third_party/ctags/routines.c
@@ -0,0 +1,756 @@
+/*
+ * $Id: routines.c 536 2007-06-02 06:09:00Z elliotth $
+ *
+ * Copyright (c) 2002-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains a lose assortment of shared functions.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/calls/struct/stat.h"
+#include "libc/errno.h"
+#include "libc/fmt/fmt.h"
+#include "libc/log/log.h"
+#include "libc/mem/mem.h"
+#include "libc/stdio/temp.h"
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/routines.h"
+
+/*
+ * MACROS
+ */
+#ifndef TMPDIR
+#define TMPDIR "/tmp"
+#endif
+
+/* File type tests.
+ */
+#ifndef S_ISREG
+#if defined(S_IFREG) && !defined(AMIGA)
+#define S_ISREG(mode) ((mode)&S_IFREG)
+#else
+#define S_ISREG(mode) TRUE /* assume regular file */
+#endif
+#endif
+
+#ifndef S_ISLNK
+#ifdef S_IFLNK
+#define S_ISLNK(mode) (((mode)&S_IFMT) == S_IFLNK)
+#else
+#define S_ISLNK(mode) FALSE /* assume no soft links */
+#endif
+#endif
+
+#ifndef S_ISDIR
+#ifdef S_IFDIR
+#define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR)
+#else
+#define S_ISDIR(mode) FALSE /* assume no soft links */
+#endif
+#endif
+
+#ifndef S_IFMT
+#define S_IFMT 0
+#endif
+
+#ifndef S_IXUSR
+#define S_IXUSR 0
+#endif
+#ifndef S_IXGRP
+#define S_IXGRP 0
+#endif
+#ifndef S_IXOTH
+#define S_IXOTH 0
+#endif
+
+#ifndef S_IRUSR
+#define S_IRUSR 0400
+#endif
+#ifndef S_IWUSR
+#define S_IWUSR 0200
+#endif
+
+#ifndef S_ISUID
+#define S_ISUID 0
+#endif
+
+/* Hack for rediculous practice of Microsoft Visual C++.
+ */
+#if defined(WIN32)
+#if defined(_MSC_VER)
+#define stat _stat
+#define getcwd _getcwd
+#define currentdrive() (_getdrive() + 'A' - 1)
+#define PATH_MAX _MAX_PATH
+#elif defined(__BORLANDC__)
+#define PATH_MAX MAXPATH
+#define currentdrive() (getdisk() + 'A')
+#elif defined(DJGPP)
+#define currentdrive() (getdisk() + 'A')
+#else
+#define currentdrive() 'C'
+#endif
+#endif
+
+#ifndef PATH_MAX
+#define PATH_MAX 256
+#endif
+
+/*
+ * Miscellaneous macros
+ */
+#define selected(var, feature) (((int)(var) & (int)(feature)) == (int)feature)
+
+/*
+ * DATA DEFINITIONS
+ */
+#if defined(MSDOS_STYLE_PATH)
+const char *const PathDelimiters = ":/\\";
+#elif defined(VMS)
+const char *const PathDelimiters = ":]>";
+#endif
+
+char *CurrentDirectory;
+
+static const char *ExecutableProgram;
+static const char *ExecutableName;
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+#ifdef NEED_PROTO_STAT
+extern int stat(const char *, struct stat *);
+#endif
+#ifdef NEED_PROTO_LSTAT
+extern int lstat(const char *, struct stat *);
+#endif
+#if defined(MSDOS) || defined(WIN32) || defined(VMS) || defined(__EMX__) || \
+ defined(AMIGA)
+#define lstat(fn, buf) stat(fn, buf)
+#endif
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+extern void freeRoutineResources(void) {
+ if (CurrentDirectory != NULL) eFree(CurrentDirectory);
+}
+
+extern void setExecutableName(const char *const path) {
+ ExecutableProgram = path;
+ ExecutableName = baseFilename(path);
+#ifdef VAXC
+ {
+ /* remove filetype from executable name */
+ char *p = strrchr(ExecutableName, '.');
+ if (p != NULL) *p = '\0';
+ }
+#endif
+}
+
+extern const char *getExecutableName(void) {
+ return ExecutableName;
+}
+
+extern const char *getExecutablePath(void) {
+ return ExecutableProgram;
+}
+
+extern void error(const errorSelection selection, const char *const format,
+ ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ fprintf(errout, "%s: %s", getExecutableName(),
+ selected(selection, WARNING) ? "Warning: " : "");
+ vfprintf(errout, format, ap);
+ if (selected(selection, PERROR))
+#ifdef HAVE_STRERROR
+ fprintf(errout, " : %s", strerror(errno));
+#else
+ perror(" ");
+#endif
+ fputs("\n", errout);
+ va_end(ap);
+ if (selected(selection, FATAL)) exit(1);
+}
+
+/*
+ * Memory allocation functions
+ */
+
+extern void *eMalloc(const size_t size) {
+ void *buffer = malloc(size);
+
+ if (buffer == NULL) error(FATAL, "out of memory");
+
+ return buffer;
+}
+
+extern void *eCalloc(const size_t count, const size_t size) {
+ void *buffer = calloc(count, size);
+
+ if (buffer == NULL) error(FATAL, "out of memory");
+
+ return buffer;
+}
+
+extern void *eRealloc(void *const ptr, const size_t size) {
+ void *buffer;
+ if (ptr == NULL)
+ buffer = eMalloc(size);
+ else {
+ buffer = realloc(ptr, size);
+ if (buffer == NULL) error(FATAL, "out of memory");
+ }
+ return buffer;
+}
+
+extern void eFree(void *const ptr) {
+ Assert(ptr != NULL);
+ free(ptr);
+}
+
+/*
+ * String manipulation functions
+ */
+
+/*
+ * Compare two strings, ignoring case.
+ * Return 0 for match, < 0 for smaller, > 0 for bigger
+ * Make sure case is folded to uppercase in comparison (like for 'sort -f')
+ * This makes a difference when one of the chars lies between upper and lower
+ * ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !)
+ */
+extern int struppercmp(const char *s1, const char *s2) {
+ int result;
+ do {
+ result = toupper((int)*s1) - toupper((int)*s2);
+ } while (result == 0 && *s1++ != '\0' && *s2++ != '\0');
+ return result;
+}
+
+extern int strnuppercmp(const char *s1, const char *s2, size_t n) {
+ int result;
+ do {
+ result = toupper((int)*s1) - toupper((int)*s2);
+ } while (result == 0 && --n > 0 && *s1++ != '\0' && *s2++ != '\0');
+ return result;
+}
+
+#ifndef HAVE_STRSTR
+extern char *strstr(const char *str, const char *substr) {
+ const size_t length = strlen(substr);
+ const char *match = NULL;
+ const char *p;
+
+ for (p = str; *p != '\0' && match == NULL; ++p)
+ if (strncmp(p, substr, length) == 0) match = p;
+ return (char *)match;
+}
+#endif
+
+extern char *eStrdup(const char *str) {
+ char *result = xMalloc(strlen(str) + 1, char);
+ strcpy(result, str);
+ return result;
+}
+
+extern void toLowerString(char *str) {
+ while (*str != '\0') {
+ *str = tolower((int)*str);
+ ++str;
+ }
+}
+
+extern void toUpperString(char *str) {
+ while (*str != '\0') {
+ *str = toupper((int)*str);
+ ++str;
+ }
+}
+
+/* Newly allocated string containing lower case conversion of a string.
+ */
+extern char *newLowerString(const char *str) {
+ char *const result = xMalloc(strlen(str) + 1, char);
+ int i = 0;
+ do
+ result[i] = tolower((int)str[i]);
+ while (str[i++] != '\0');
+ return result;
+}
+
+/* Newly allocated string containing upper case conversion of a string.
+ */
+extern char *newUpperString(const char *str) {
+ char *const result = xMalloc(strlen(str) + 1, char);
+ int i = 0;
+ do
+ result[i] = toupper((int)str[i]);
+ while (str[i++] != '\0');
+ return result;
+}
+
+/*
+ * File system functions
+ */
+
+extern void setCurrentDirectory(void) {
+#ifndef AMIGA
+ char *buf;
+#endif
+ if (CurrentDirectory == NULL)
+ CurrentDirectory = xMalloc((size_t)(PATH_MAX + 1), char);
+#ifdef AMIGA
+ strcpy(CurrentDirectory, ".");
+#else
+ buf = getcwd(CurrentDirectory, PATH_MAX);
+ if (buf == NULL) perror("");
+#endif
+ if (CurrentDirectory[strlen(CurrentDirectory) - (size_t)1] !=
+ PATH_SEPARATOR) {
+ sprintf(CurrentDirectory + strlen(CurrentDirectory), "%c",
+ OUTPUT_PATH_SEPARATOR);
+ }
+}
+
+#ifdef AMIGA
+static boolean isAmigaDirectory(const char *const name) {
+ boolean result = FALSE;
+ struct FileInfoBlock *const fib = xMalloc(1, struct FileInfoBlock);
+ if (fib != NULL) {
+ const BPTR flock = Lock((UBYTE *)name, (long)ACCESS_READ);
+
+ if (flock != (BPTR)NULL) {
+ if (Examine(flock, fib))
+ result = ((fib->fib_DirEntryType >= 0) ? TRUE : FALSE);
+ UnLock(flock);
+ }
+ eFree(fib);
+ }
+ return result;
+}
+#endif
+
+/* For caching of stat() calls */
+extern fileStatus *eStat(const char *const fileName) {
+ struct stat status;
+ static fileStatus file;
+ if (file.name == NULL || strcmp(fileName, file.name) != 0) {
+ eStatFree(&file);
+ file.name = eStrdup(fileName);
+ if (lstat(file.name, &status) != 0)
+ file.exists = FALSE;
+ else {
+ file.isSymbolicLink = (boolean)S_ISLNK(status.st_mode);
+ if (file.isSymbolicLink && stat(file.name, &status) != 0)
+ file.exists = FALSE;
+ else {
+ file.exists = TRUE;
+#ifdef AMIGA
+ file.isDirectory = isAmigaDirectory(file.name);
+#else
+ file.isDirectory = (boolean)S_ISDIR(status.st_mode);
+#endif
+ file.isNormalFile = (boolean)(S_ISREG(status.st_mode));
+ file.isExecutable =
+ (boolean)((status.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0);
+ file.isSetuid = (boolean)((status.st_mode & S_ISUID) != 0);
+ file.size = status.st_size;
+ }
+ }
+ }
+ return &file;
+}
+
+extern void eStatFree(fileStatus *status) {
+ if (status->name != NULL) {
+ eFree(status->name);
+ status->name = NULL;
+ }
+}
+
+extern boolean doesFileExist(const char *const fileName) {
+ fileStatus *status = eStat(fileName);
+ return status->exists;
+}
+
+extern boolean isRecursiveLink(const char *const dirName) {
+ boolean result = FALSE;
+ fileStatus *status = eStat(dirName);
+ if (status->isSymbolicLink) {
+ char *const path = absoluteFilename(dirName);
+ while (path[strlen(path) - 1] == PATH_SEPARATOR)
+ path[strlen(path) - 1] = '\0';
+ while (!result && strlen(path) > (size_t)1) {
+ char *const separator = strrchr(path, PATH_SEPARATOR);
+ if (separator == NULL)
+ break;
+ else if (separator == path) /* backed up to root directory */
+ *(separator + 1) = '\0';
+ else
+ *separator = '\0';
+ result = isSameFile(path, dirName);
+ }
+ eFree(path);
+ }
+ return result;
+}
+
+#ifndef HAVE_FGETPOS
+
+extern int fgetpos(FILE *stream, fpos_t *pos) {
+ int result = 0;
+
+ *pos = ftell(stream);
+ if (*pos == -1L) result = -1;
+
+ return result;
+}
+
+extern int fsetpos(FILE *stream, fpos_t const *pos) {
+ return fseek(stream, *pos, SEEK_SET);
+}
+
+#endif
+
+/*
+ * Pathname manipulation (O/S dependent!!!)
+ */
+
+static boolean isPathSeparator(const int c) {
+ boolean result;
+#if defined(MSDOS_STYLE_PATH) || defined(VMS)
+ result = (boolean)(strchr(PathDelimiters, c) != NULL);
+#else
+ result = (boolean)(c == PATH_SEPARATOR);
+#endif
+ return result;
+}
+
+#if !defined(HAVE_STAT_ST_INO)
+
+static void canonicalizePath(char *const path __unused__) {
+#if defined(MSDOS_STYLE_PATH)
+ char *p;
+ for (p = path; *p != '\0'; ++p)
+ if (isPathSeparator(*p) && *p != ':') *p = PATH_SEPARATOR;
+#endif
+}
+
+#endif
+
+extern boolean isSameFile(const char *const name1, const char *const name2) {
+ boolean result = FALSE;
+#if defined(HAVE_STAT_ST_INO)
+ struct stat stat1, stat2;
+
+ if (stat(name1, &stat1) == 0 && stat(name2, &stat2) == 0)
+ result = (boolean)(stat1.st_ino == stat2.st_ino);
+#else
+ {
+ char *const n1 = absoluteFilename(name1);
+ char *const n2 = absoluteFilename(name2);
+ canonicalizePath(n1);
+ canonicalizePath(n2);
+#if defined(CASE_INSENSITIVE_FILENAMES)
+ result = (boolean)(strcasecmp(n1, n2) == 0);
+#else
+ result = (boolean)(strcmp(n1, n2) == 0);
+#endif
+ free(n1);
+ free(n2);
+ }
+#endif
+ return result;
+}
+
+extern const char *baseFilename(const char *const filePath) {
+#if defined(MSDOS_STYLE_PATH) || defined(VMS)
+ const char *tail = NULL;
+ unsigned int i;
+
+ /* Find whichever of the path delimiters is last.
+ */
+ for (i = 0; i < strlen(PathDelimiters); ++i) {
+ const char *sep = strrchr(filePath, PathDelimiters[i]);
+
+ if (sep > tail) tail = sep;
+ }
+#else
+ const char *tail = strrchr(filePath, PATH_SEPARATOR);
+#endif
+ if (tail == NULL)
+ tail = filePath;
+ else
+ ++tail; /* step past last delimiter */
+#ifdef VAXC
+ {
+ /* remove version number from filename */
+ char *p = strrchr((char *)tail, ';');
+ if (p != NULL) *p = '\0';
+ }
+#endif
+
+ return tail;
+}
+
+extern const char *fileExtension(const char *const fileName) {
+ const char *extension;
+ const char *pDelimiter = NULL;
+ const char *const base = baseFilename(fileName);
+#ifdef QDOS
+ pDelimiter = strrchr(base, '_');
+#endif
+ if (pDelimiter == NULL) pDelimiter = strrchr(base, '.');
+
+ if (pDelimiter == NULL)
+ extension = "";
+ else
+ extension = pDelimiter + 1; /* skip to first char of extension */
+
+ return extension;
+}
+
+extern boolean isAbsolutePath(const char *const path) {
+ boolean result = FALSE;
+#if defined(MSDOS_STYLE_PATH)
+ if (isPathSeparator(path[0]))
+ result = TRUE;
+ else if (isalpha(path[0]) && path[1] == ':') {
+ if (isPathSeparator(path[2]))
+ result = TRUE;
+ else
+ /* We don't support non-absolute file names with a drive
+ * letter, like `d:NAME' (it's too much hassle).
+ */
+ error(FATAL, "%s: relative file names with drive letters not supported",
+ path);
+ }
+#elif defined(VMS)
+ result = (boolean)(strchr(path, ':') != NULL);
+#else
+ result = isPathSeparator(path[0]);
+#endif
+ return result;
+}
+
+extern vString *combinePathAndFile(const char *const path,
+ const char *const file) {
+ vString *const filePath = vStringNew();
+#ifdef VMS
+ const char *const directoryId = strstr(file, ".DIR;1");
+
+ if (directoryId == NULL) {
+ const char *const versionId = strchr(file, ';');
+
+ vStringCopyS(filePath, path);
+ if (versionId == NULL)
+ vStringCatS(filePath, file);
+ else
+ vStringNCatS(filePath, file, versionId - file);
+ vStringCopyToLower(filePath, filePath);
+ } else {
+ /* File really is a directory; append it to the path.
+ * Gotcha: doesn't work with logical names.
+ */
+ vStringNCopyS(filePath, path, strlen(path) - 1);
+ vStringPut(filePath, '.');
+ vStringNCatS(filePath, file, directoryId - file);
+ if (strchr(path, '[') != NULL)
+ vStringPut(filePath, ']');
+ else
+ vStringPut(filePath, '>');
+ vStringTerminate(filePath);
+ }
+#else
+ const int lastChar = path[strlen(path) - 1];
+ boolean terminated = isPathSeparator(lastChar);
+
+ vStringCopyS(filePath, path);
+ if (!terminated) {
+ vStringPut(filePath, OUTPUT_PATH_SEPARATOR);
+ vStringTerminate(filePath);
+ }
+ vStringCatS(filePath, file);
+#endif
+
+ return filePath;
+}
+
+/* Return a newly-allocated string whose contents concatenate those of
+ * s1, s2, s3.
+ * Routine adapted from Gnu etags.
+ */
+static char *concat(const char *s1, const char *s2, const char *s3) {
+ int len1 = strlen(s1), len2 = strlen(s2), len3 = strlen(s3);
+ char *result = xMalloc(len1 + len2 + len3 + 1, char);
+
+ strcpy(result, s1);
+ strcpy(result + len1, s2);
+ strcpy(result + len1 + len2, s3);
+ result[len1 + len2 + len3] = '\0';
+
+ return result;
+}
+
+/* Return a newly allocated string containing the absolute file name of FILE
+ * given CWD (which should end with a slash).
+ * Routine adapted from Gnu etags.
+ */
+extern char *absoluteFilename(const char *file) {
+ char *slashp, *cp;
+ char *res = NULL;
+ if (isAbsolutePath(file)) {
+#ifdef MSDOS_STYLE_PATH
+ if (file[1] == ':')
+ res = eStrdup(file);
+ else {
+ char drive[3];
+ sprintf(drive, "%c:", currentdrive());
+ res = concat(drive, file, "");
+ }
+#else
+ res = eStrdup(file);
+#endif
+ } else
+ res = concat(CurrentDirectory, file, "");
+
+ /* Delete the "/dirname/.." and "/." substrings. */
+ slashp = strchr(res, PATH_SEPARATOR);
+ while (slashp != NULL && slashp[0] != '\0') {
+ if (slashp[1] == '.') {
+ if (slashp[2] == '.' &&
+ (slashp[3] == PATH_SEPARATOR || slashp[3] == '\0')) {
+ cp = slashp;
+ do
+ cp--;
+ while (cp >= res && !isAbsolutePath(cp));
+ if (cp < res) cp = slashp; /* the absolute name begins with "/.." */
+#ifdef MSDOS_STYLE_PATH
+ /* Under MSDOS and NT we get `d:/NAME' as absolute file name,
+ * so the luser could say `d:/../NAME'. We silently treat this
+ * as `d:/NAME'.
+ */
+ else if (cp[0] != PATH_SEPARATOR)
+ cp = slashp;
+#endif
+ memmove(cp, slashp + 3, strlen(slashp + 3) + 1);
+ slashp = cp;
+ continue;
+ } else if (slashp[2] == PATH_SEPARATOR || slashp[2] == '\0') {
+ memmove(slashp, slashp + 2, strlen(slashp + 2) + 1);
+ continue;
+ }
+ }
+ slashp = strchr(slashp + 1, PATH_SEPARATOR);
+ }
+
+ if (res[0] == '\0')
+ return eStrdup("/");
+ else {
+#ifdef MSDOS_STYLE_PATH
+ /* Canonicalize drive letter case. */
+ if (res[1] == ':' && islower(res[0])) res[0] = toupper(res[0]);
+#endif
+
+ return res;
+ }
+}
+
+/* Return a newly allocated string containing the absolute file name of dir
+ * where `file' resides given `CurrentDirectory'.
+ * Routine adapted from Gnu etags.
+ */
+extern char *absoluteDirname(char *file) {
+ char *slashp, *res;
+ char save;
+ slashp = strrchr(file, PATH_SEPARATOR);
+ if (slashp == NULL)
+ res = eStrdup(CurrentDirectory);
+ else {
+ save = slashp[1];
+ slashp[1] = '\0';
+ res = absoluteFilename(file);
+ slashp[1] = save;
+ }
+ return res;
+}
+
+/* Return a newly allocated string containing the file name of FILE relative
+ * to the absolute directory DIR (which should end with a slash).
+ * Routine adapted from Gnu etags.
+ */
+extern char *relativeFilename(const char *file, const char *dir) {
+ const char *fp, *dp;
+ char *absdir, *res;
+ int i;
+
+ /* Find the common root of file and dir (with a trailing slash). */
+ absdir = absoluteFilename(file);
+ fp = absdir;
+ dp = dir;
+ while (*fp++ == *dp++) continue;
+ fp--;
+ dp--; /* back to the first differing char */
+ do { /* look at the equal chars until path sep */
+ if (fp == absdir) return absdir; /* first char differs, give up */
+ fp--;
+ dp--;
+ } while (*fp != PATH_SEPARATOR);
+
+ /* Build a sequence of "../" strings for the resulting relative file name.
+ */
+ i = 0;
+ while ((dp = strchr(dp + 1, PATH_SEPARATOR)) != NULL) i += 1;
+ res = xMalloc(3 * i + strlen(fp + 1) + 1, char);
+ res[0] = '\0';
+ while (i-- > 0) strcat(res, "../");
+
+ /* Add the file name relative to the common root of file and dir. */
+ strcat(res, fp + 1);
+ free(absdir);
+
+ return res;
+}
+
+extern FILE *tempFile(const char *const mode, char **const pName) {
+ char *name;
+ FILE *fp;
+ int fd;
+#if defined(HAVE_MKSTEMP)
+ const char *const pattern = "tags.XXXXXX";
+ const char *tmpdir = NULL;
+ fileStatus *file = eStat(ExecutableProgram);
+ if (!file->isSetuid) tmpdir = getenv("TMPDIR");
+ if (tmpdir == NULL) tmpdir = TMPDIR;
+ name = xMalloc(strlen(tmpdir) + 1 + strlen(pattern) + 1, char);
+ sprintf(name, "%s%c%s", tmpdir, OUTPUT_PATH_SEPARATOR, pattern);
+ fd = mkstemp(name);
+ eStatFree(file);
+#elif defined(HAVE_TEMPNAM)
+ name = tempnam(TMPDIR, "tags");
+ if (name == NULL)
+ error(FATAL | PERROR, "cannot allocate temporary file name");
+ fd = open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+#else
+ name = xMalloc(L_tmpnam, char);
+ if (tmpnam(name) != name)
+ error(FATAL | PERROR, "cannot assign temporary file name");
+ fd = open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+#endif
+ if (fd == -1) error(FATAL | PERROR, "cannot open temporary file");
+ fp = fdopen(fd, mode);
+ if (fp == NULL) error(FATAL | PERROR, "cannot open temporary file");
+ DebugStatement(debugPrintf(DEBUG_STATUS, "opened temporary file %s\n", name);)
+ Assert(*pName == NULL);
+ *pName = name;
+ return fp;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/routines.h b/third_party/ctags/routines.h
new file mode 100644
index 000000000..94b90892d
--- /dev/null
+++ b/third_party/ctags/routines.h
@@ -0,0 +1,125 @@
+#ifndef _ROUTINES_H
+#define _ROUTINES_H
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/stdio/stdio.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * MACROS
+ */
+#define xMalloc(n, Type) (Type *)eMalloc((size_t)(n) * sizeof(Type))
+#define xCalloc(n, Type) (Type *)eCalloc((size_t)(n), sizeof(Type))
+#define xRealloc(p, n, Type) (Type *)eRealloc((p), (n) * sizeof(Type))
+
+/*
+ * Portability macros
+ */
+#ifndef PATH_SEPARATOR
+#if defined(MSDOS_STYLE_PATH)
+#define PATH_SEPARATOR '\\'
+#elif defined(QDOS)
+#define PATH_SEPARATOR '_'
+#else
+#define PATH_SEPARATOR '/'
+#endif
+#endif
+
+#if defined(MSDOS_STYLE_PATH) && defined(UNIX_PATH_SEPARATOR)
+#define OUTPUT_PATH_SEPARATOR '/'
+#else
+#define OUTPUT_PATH_SEPARATOR PATH_SEPARATOR
+#endif
+
+/*
+ * DATA DECLARATIONS
+ */
+#if defined(MSDOS_STYLE_PATH) || defined(VMS)
+extern const char *const PathDelimiters;
+#endif
+extern char *CurrentDirectory;
+typedef int errorSelection;
+enum eErrorTypes { FATAL = 1, WARNING = 2, PERROR = 4 };
+
+typedef struct {
+ /* Name of file for which status is valid */
+ char *name;
+
+ /* Does file exist? If not, members below do not contain valid data. */
+ boolean exists;
+
+ /* is file path a symbolic link to another file? */
+ boolean isSymbolicLink;
+
+ /* Is file (pointed to) a directory? */
+ boolean isDirectory;
+
+ /* Is file (pointed to) a normal file? */
+ boolean isNormalFile;
+
+ /* Is file (pointed to) executable? */
+ boolean isExecutable;
+
+ /* Is file (pointed to) setuid? */
+ boolean isSetuid;
+
+ /* Size of file (pointed to) */
+ unsigned long size;
+} fileStatus;
+
+/*
+ * FUNCTION PROTOTYPES
+ */
+extern void freeRoutineResources(void);
+extern void setExecutableName(const char *const path);
+extern const char *getExecutableName(void);
+extern const char *getExecutablePath(void);
+extern void error(const errorSelection selection, const char *const format, ...)
+ __printf__(2, 3);
+
+/* Memory allocation functions */
+#ifdef NEED_PROTO_MALLOC
+extern void *malloc(size_t);
+extern void *realloc(void *ptr, size_t);
+#endif
+extern void *eMalloc(const size_t size);
+extern void *eCalloc(const size_t count, const size_t size);
+extern void *eRealloc(void *const ptr, const size_t size);
+extern void eFree(void *const ptr);
+
+/* String manipulation functions */
+extern int struppercmp(const char *s1, const char *s2);
+extern int strnuppercmp(const char *s1, const char *s2, size_t n);
+#ifndef HAVE_STRSTR
+extern char *strstr(const char *str, const char *substr);
+#endif
+extern char *eStrdup(const char *str);
+extern void toLowerString(char *str);
+extern void toUpperString(char *str);
+extern char *newLowerString(const char *str);
+extern char *newUpperString(const char *str);
+
+/* File system functions */
+extern void setCurrentDirectory(void);
+extern fileStatus *eStat(const char *const fileName);
+extern void eStatFree(fileStatus *status);
+extern boolean doesFileExist(const char *const fileName);
+extern boolean isRecursiveLink(const char *const dirName);
+extern boolean isSameFile(const char *const name1, const char *const name2);
+#if defined(NEED_PROTO_FGETPOS)
+extern int fgetpos(FILE *stream, fpos_t *pos);
+extern int fsetpos(FILE *stream, fpos_t *pos);
+#endif
+extern const char *baseFilename(const char *const filePath);
+extern const char *fileExtension(const char *const fileName);
+extern boolean isAbsolutePath(const char *const path);
+extern vString *combinePathAndFile(const char *const path,
+ const char *const file);
+extern char *absoluteFilename(const char *file);
+extern char *absoluteDirname(char *file);
+extern char *relativeFilename(const char *file, const char *dir);
+extern FILE *tempFile(const char *const mode, char **const pName);
+
+#endif /* _ROUTINES_H */
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/ruby.c b/third_party/ctags/ruby.c
new file mode 100644
index 000000000..22e25dca1
--- /dev/null
+++ b/third_party/ctags/ruby.c
@@ -0,0 +1,344 @@
+/*
+ * $Id: ruby.c 571 2007-06-24 23:32:14Z elliotth $
+ *
+ * Copyright (c) 2000-2001, Thaddeus Covert
+ * Copyright (c) 2002 Matthias Veit
+ * Copyright (c) 2004 Elliott Hughes
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for Ruby language
+ * files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DECLARATIONS
+ */
+typedef enum {
+ K_UNDEFINED = -1,
+ K_CLASS,
+ K_METHOD,
+ K_MODULE,
+ K_SINGLETON
+} rubyKind;
+
+/*
+ * DATA DEFINITIONS
+ */
+static kindOption RubyKinds[] = {
+ {TRUE, 'c', "class", "classes"},
+ {TRUE, 'f', "method", "methods"},
+ {TRUE, 'm', "module", "modules"},
+ {TRUE, 'F', "singleton method", "singleton methods"}};
+
+static stringList* nesting = 0;
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+/*
+ * Returns a string describing the scope in 'list'.
+ * We record the current scope as a list of entered scopes.
+ * Scopes corresponding to 'if' statements and the like are
+ * represented by empty strings. Scopes corresponding to
+ * modules and classes are represented by the name of the
+ * module or class.
+ */
+static vString* stringListToScope(const stringList* list) {
+ unsigned int i;
+ unsigned int chunks_output = 0;
+ vString* result = vStringNew();
+ const unsigned int max = stringListCount(list);
+ for (i = 0; i < max; ++i) {
+ vString* chunk = stringListItem(list, i);
+ if (vStringLength(chunk) > 0) {
+ vStringCatS(result, (chunks_output++ > 0) ? "." : "");
+ vStringCatS(result, vStringValue(chunk));
+ }
+ }
+ return result;
+}
+
+/*
+ * Attempts to advance 's' past 'literal'.
+ * Returns TRUE if it did, FALSE (and leaves 's' where
+ * it was) otherwise.
+ */
+static boolean canMatch(const unsigned char** s, const char* literal) {
+ const int literal_length = strlen(literal);
+ const unsigned char next_char = *(*s + literal_length);
+ if (strncmp((const char*)*s, literal, literal_length) != 0) {
+ return FALSE;
+ }
+ /* Additionally check that we're at the end of a token. */
+ if (!(next_char == 0 || isspace(next_char) || next_char == '(')) {
+ return FALSE;
+ }
+ *s += literal_length;
+ return TRUE;
+}
+
+/*
+ * Attempts to advance 'cp' past a Ruby operator method name. Returns
+ * TRUE if successful (and copies the name into 'name'), FALSE otherwise.
+ */
+static boolean parseRubyOperator(vString* name, const unsigned char** cp) {
+ static const char* RUBY_OPERATORS[] = {
+ "[]", "[]=", "**", "!", "~", "+@", "-@", "*", "/", "%",
+ "+", "-", ">>", "<<", "&", "^", "|", "<=", "<", ">",
+ ">=", "<=>", "==", "===", "!=", "=~", "!~", "`", 0};
+ int i;
+ for (i = 0; RUBY_OPERATORS[i] != 0; ++i) {
+ if (canMatch(cp, RUBY_OPERATORS[i])) {
+ vStringCatS(name, RUBY_OPERATORS[i]);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/*
+ * Emits a tag for the given 'name' of kind 'kind' at the current nesting.
+ */
+static void emitRubyTag(vString* name, rubyKind kind) {
+ tagEntryInfo tag;
+ vString* scope;
+
+ vStringTerminate(name);
+ scope = stringListToScope(nesting);
+
+ initTagEntry(&tag, vStringValue(name));
+ if (vStringLength(scope) > 0) {
+ tag.extensionFields.scope[0] = "class";
+ tag.extensionFields.scope[1] = vStringValue(scope);
+ }
+ tag.kindName = RubyKinds[kind].name;
+ tag.kind = RubyKinds[kind].letter;
+ makeTagEntry(&tag);
+
+ stringListAdd(nesting, vStringNewCopy(name));
+
+ vStringClear(name);
+ vStringDelete(scope);
+}
+
+/* Tests whether 'ch' is a character in 'list'. */
+static boolean charIsIn(char ch, const char* list) {
+ return (strchr(list, ch) != 0);
+}
+
+/* Advances 'cp' over leading whitespace. */
+static void skipWhitespace(const unsigned char** cp) {
+ while (isspace(**cp)) {
+ ++*cp;
+ }
+}
+
+/*
+ * Copies the characters forming an identifier from *cp into
+ * name, leaving *cp pointing to the character after the identifier.
+ */
+static rubyKind parseIdentifier(const unsigned char** cp, vString* name,
+ rubyKind kind) {
+ /* Method names are slightly different to class and variable names.
+ * A method name may optionally end with a question mark, exclamation
+ * point or equals sign. These are all part of the name.
+ * A method name may also contain a period if it's a singleton method.
+ */
+ const char* also_ok = (kind == K_METHOD) ? "_.?!=" : "_";
+
+ skipWhitespace(cp);
+
+ /* Check for an anonymous (singleton) class such as "class << HTTP". */
+ if (kind == K_CLASS && **cp == '<' && *(*cp + 1) == '<') {
+ return K_UNDEFINED;
+ }
+
+ /* Check for operators such as "def []=(key, val)". */
+ if (kind == K_METHOD || kind == K_SINGLETON) {
+ if (parseRubyOperator(name, cp)) {
+ return kind;
+ }
+ }
+
+ /* Copy the identifier into 'name'. */
+ while (**cp != 0 && (isalnum(**cp) || charIsIn(**cp, also_ok))) {
+ char last_char = **cp;
+
+ vStringPut(name, last_char);
+ ++*cp;
+
+ if (kind == K_METHOD) {
+ /* Recognize singleton methods. */
+ if (last_char == '.') {
+ vStringTerminate(name);
+ vStringClear(name);
+ return parseIdentifier(cp, name, K_SINGLETON);
+ }
+
+ /* Recognize characters which mark the end of a method name. */
+ if (charIsIn(last_char, "?!=")) {
+ break;
+ }
+ }
+ }
+ return kind;
+}
+
+static void readAndEmitTag(const unsigned char** cp, rubyKind expected_kind) {
+ if (isspace(**cp)) {
+ vString* name = vStringNew();
+ rubyKind actual_kind = parseIdentifier(cp, name, expected_kind);
+
+ if (actual_kind == K_UNDEFINED || vStringLength(name) == 0) {
+ /*
+ * What kind of tags should we create for code like this?
+ *
+ * %w(self.clfloor clfloor).each do |name|
+ * module_eval <<-"end;"
+ * def #{name}(x, y=1)
+ * q, r = x.divmod(y)
+ * q = q.to_i
+ * return q, r
+ * end
+ * end;
+ * end
+ *
+ * Or this?
+ *
+ * class << HTTP
+ *
+ * For now, we don't create any.
+ */
+ } else {
+ emitRubyTag(name, actual_kind);
+ }
+ vStringDelete(name);
+ }
+}
+
+static void enterUnnamedScope(void) {
+ stringListAdd(nesting, vStringNewInit(""));
+}
+
+static void findRubyTags(void) {
+ const unsigned char* line;
+ boolean inMultiLineComment = FALSE;
+
+ nesting = stringListNew();
+
+ /* FIXME: this whole scheme is wrong, because Ruby isn't line-based.
+ * You could perfectly well write:
+ *
+ * def
+ * method
+ * puts("hello")
+ * end
+ *
+ * if you wished, and this function would fail to recognize anything.
+ */
+ while ((line = fileReadLine()) != NULL) {
+ const unsigned char* cp = line;
+
+ if (canMatch(&cp, "=begin")) {
+ inMultiLineComment = TRUE;
+ continue;
+ }
+ if (canMatch(&cp, "=end")) {
+ inMultiLineComment = FALSE;
+ continue;
+ }
+
+ skipWhitespace(&cp);
+
+ /* Avoid mistakenly starting a scope for modifiers such as
+ *
+ * return if
+ *
+ * FIXME: this is fooled by code such as
+ *
+ * result = if
+ *
+ * else
+ *
+ * end
+ *
+ * FIXME: we're also fooled if someone does something heinous such as
+ *
+ * puts("hello") \
+ * unless
+ */
+ if (canMatch(&cp, "case") || canMatch(&cp, "for") || canMatch(&cp, "if") ||
+ canMatch(&cp, "unless") || canMatch(&cp, "while")) {
+ enterUnnamedScope();
+ }
+
+ /*
+ * "module M", "class C" and "def m" should only be at the beginning
+ * of a line.
+ */
+ if (canMatch(&cp, "module")) {
+ readAndEmitTag(&cp, K_MODULE);
+ } else if (canMatch(&cp, "class")) {
+ readAndEmitTag(&cp, K_CLASS);
+ } else if (canMatch(&cp, "def")) {
+ readAndEmitTag(&cp, K_METHOD);
+ }
+
+ while (*cp != '\0') {
+ /* FIXME: we don't cope with here documents,
+ * or regular expression literals, or ... you get the idea.
+ * Hopefully, the restriction above that insists on seeing
+ * definitions at the starts of lines should keep us out of
+ * mischief.
+ */
+ if (inMultiLineComment || isspace(*cp)) {
+ ++cp;
+ } else if (*cp == '#') {
+ /* FIXME: this is wrong, but there *probably* won't be a
+ * definition after an interpolated string (where # doesn't
+ * mean 'comment').
+ */
+ break;
+ } else if (canMatch(&cp, "begin") || canMatch(&cp, "do")) {
+ enterUnnamedScope();
+ } else if (canMatch(&cp, "end") && stringListCount(nesting) > 0) {
+ /* Leave the most recent scope. */
+ vStringDelete(stringListLast(nesting));
+ stringListRemoveLast(nesting);
+ } else if (*cp == '"') {
+ /* Skip string literals.
+ * FIXME: should cope with escapes and interpolation.
+ */
+ do {
+ ++cp;
+ } while (*cp != 0 && *cp != '"');
+ } else if (*cp != '\0') {
+ do
+ ++cp;
+ while (isalnum(*cp) || *cp == '_');
+ }
+ }
+ }
+ stringListDelete(nesting);
+}
+
+extern parserDefinition* RubyParser(void) {
+ static const char* const extensions[] = {"rb", "ruby", NULL};
+ parserDefinition* def = parserNew("Ruby");
+ def->kinds = RubyKinds;
+ def->kindCount = KIND_COUNT(RubyKinds);
+ def->extensions = extensions;
+ def->parser = findRubyTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/scheme.c b/third_party/ctags/scheme.c
new file mode 100644
index 000000000..cfa300ea6
--- /dev/null
+++ b/third_party/ctags/scheme.c
@@ -0,0 +1,90 @@
+/*
+ * $Id: scheme.c 443 2006-05-30 04:37:13Z darren $
+ *
+ * Copyright (c) 2000-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for Scheme language
+ * files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum {
+ K_FUNCTION,
+ K_SET,
+} schemeKind;
+
+static kindOption SchemeKinds[] = {
+ {TRUE, 'f', "function", "functions"},
+ {TRUE, 's', "set", "sets"},
+};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+/* Algorithm adapted from from GNU etags.
+ * Scheme tag functions
+ * look for (def... xyzzy
+ * look for (def... (xyzzy
+ * look for (def ... ((... (xyzzy ....
+ * look for (set! xyzzy
+ */
+static void readIdentifier(vString *const name, const unsigned char *cp) {
+ const unsigned char *p;
+ vStringClear(name);
+ /* Go till you get to white space or a syntactic break */
+ for (p = cp; *p != '\0' && *p != '(' && *p != ')' && !isspace(*p); p++)
+ vStringPut(name, (int)*p);
+ vStringTerminate(name);
+}
+
+static void findSchemeTags(void) {
+ vString *name = vStringNew();
+ const unsigned char *line;
+
+ while ((line = fileReadLine()) != NULL) {
+ const unsigned char *cp = line;
+
+ if (cp[0] == '(' && (cp[1] == 'D' || cp[1] == 'd') &&
+ (cp[2] == 'E' || cp[2] == 'e') && (cp[3] == 'F' || cp[3] == 'f')) {
+ while (!isspace(*cp)) cp++;
+ /* Skip over open parens and white space */
+ while (*cp != '\0' && (isspace(*cp) || *cp == '(')) cp++;
+ readIdentifier(name, cp);
+ makeSimpleTag(name, SchemeKinds, K_FUNCTION);
+ }
+ if (cp[0] == '(' && (cp[1] == 'S' || cp[1] == 's') &&
+ (cp[2] == 'E' || cp[2] == 'e') && (cp[3] == 'T' || cp[3] == 't') &&
+ (cp[4] == '!' || cp[4] == '!') && (isspace(cp[5]))) {
+ while (*cp != '\0' && !isspace(*cp)) cp++;
+ /* Skip over white space */
+ while (isspace(*cp)) cp++;
+ readIdentifier(name, cp);
+ makeSimpleTag(name, SchemeKinds, K_SET);
+ }
+ }
+ vStringDelete(name);
+}
+
+extern parserDefinition *SchemeParser(void) {
+ static const char *const extensions[] = {"SCM", "SM", "sch", "scheme",
+ "scm", "sm", NULL};
+ parserDefinition *def = parserNew("Scheme");
+ def->kinds = SchemeKinds;
+ def->kindCount = KIND_COUNT(SchemeKinds);
+ def->extensions = extensions;
+ def->parser = findSchemeTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/sh.c b/third_party/ctags/sh.c
new file mode 100644
index 000000000..0640b774c
--- /dev/null
+++ b/third_party/ctags/sh.c
@@ -0,0 +1,87 @@
+/*
+ * $Id: sh.c 443 2006-05-30 04:37:13Z darren $
+ *
+ * Copyright (c) 2000-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for scripts for the
+ * Bourne shell (and its derivatives, the Korn and Z shells).
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum { K_FUNCTION } shKind;
+
+static kindOption ShKinds[] = {{TRUE, 'f', "function", "functions"}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+/* Reject any tag "main" from a file named "configure". These appear in
+ * here-documents in GNU autoconf scripts and will add a haystack to the
+ * needle.
+ */
+static boolean hackReject(const vString* const tagName) {
+ const char* const scriptName = baseFilename(vStringValue(File.name));
+ boolean result = (boolean)(strcmp(scriptName, "configure") == 0 &&
+ strcmp(vStringValue(tagName), "main") == 0);
+ return result;
+}
+
+static void findShTags(void) {
+ vString* name = vStringNew();
+ const unsigned char* line;
+
+ while ((line = fileReadLine()) != NULL) {
+ const unsigned char* cp = line;
+ boolean functionFound = FALSE;
+
+ if (line[0] == '#') continue;
+
+ while (isspace(*cp)) cp++;
+ if (strncmp((const char*)cp, "function", (size_t)8) == 0 &&
+ isspace((int)cp[8])) {
+ functionFound = TRUE;
+ cp += 8;
+ if (!isspace((int)*cp)) continue;
+ while (isspace((int)*cp)) ++cp;
+ }
+ if (!(isalnum((int)*cp) || *cp == '_')) continue;
+ while (isalnum((int)*cp) || *cp == '_') {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ while (isspace((int)*cp)) ++cp;
+ if (*cp++ == '(') {
+ while (isspace((int)*cp)) ++cp;
+ if (*cp == ')' && !hackReject(name)) functionFound = TRUE;
+ }
+ if (functionFound) makeSimpleTag(name, ShKinds, K_FUNCTION);
+ vStringClear(name);
+ }
+ vStringDelete(name);
+}
+
+extern parserDefinition* ShParser(void) {
+ static const char* const extensions[] = {"sh", "SH", "bsh", "bash",
+ "ksh", "zsh", NULL};
+ parserDefinition* def = parserNew("Sh");
+ def->kinds = ShKinds;
+ def->kindCount = KIND_COUNT(ShKinds);
+ def->extensions = extensions;
+ def->parser = findShTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/slang.c b/third_party/ctags/slang.c
new file mode 100644
index 000000000..fa168c429
--- /dev/null
+++ b/third_party/ctags/slang.c
@@ -0,0 +1,41 @@
+/*
+ * $Id: slang.c 443 2006-05-30 04:37:13Z darren $
+ *
+ * Copyright (c) 2000-2001, Francesc Rocher
+ *
+ * Author: Francesc Rocher .
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for S-Lang files.
+ */
+
+/*
+ * INCLUDE FILES
+ */
+#include "third_party/ctags/general.h" /* must always come first */
+#include "third_party/ctags/parse.h"
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+static void installSlangRegex (const langType language)
+{
+ addTagRegex (language,
+ "^.*define[ \t]+([A-Z_][A-Z0-9_]*)[^;]*$",
+ "\\1", "f,function,functions", "i");
+ addTagRegex (language,
+ "^[ \t]*implements[ \t]+\\([ \t]*\"([^\"]*)\"[ \t]*\\)[ \t]*;",
+ "\\1", "n,namespace,namespaces", NULL);
+}
+
+extern parserDefinition* SlangParser (void)
+{
+ static const char *const extensions [] = { "sl", NULL };
+ parserDefinition* const def = parserNew ("SLang");
+ def->extensions = extensions;
+ def->initialize = installSlangRegex;
+ def->regex = TRUE;
+ return def;
+}
diff --git a/third_party/ctags/sml.c b/third_party/ctags/sml.c
new file mode 100644
index 000000000..89e12fb89
--- /dev/null
+++ b/third_party/ctags/sml.c
@@ -0,0 +1,175 @@
+/*
+ * $Id: sml.c 536 2007-06-02 06:09:00Z elliotth $
+ *
+ * Copyright (c) 2002, Venkatesh Prasad Ranganath and Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for SML language files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DECLARATIONS
+ */
+typedef enum {
+ K_AND = -2,
+ K_NONE = -1,
+ K_EXCEPTION,
+ K_FUNCTION,
+ K_FUNCTOR,
+ K_SIGNATURE,
+ K_STRUCTURE,
+ K_TYPE,
+ K_VAL
+} smlKind;
+
+/*
+ * DATA DEFINITIONS
+ */
+static kindOption SmlKinds[] = {
+ {TRUE, 'e', "exception", "exception declarations"},
+ {TRUE, 'f', "function", "function definitions"},
+ {TRUE, 'c', "functor", "functor definitions"},
+ {TRUE, 's', "signature", "signature declarations"},
+ {TRUE, 'r', "structure", "structure declarations"},
+ {TRUE, 't', "type", "type definitions"},
+ {TRUE, 'v', "value", "value bindings"}};
+
+static struct {
+ const char *keyword;
+ smlKind kind;
+} SmlKeywordTypes[] = {{"abstype", K_TYPE}, {"and", K_AND},
+ {"datatype", K_TYPE}, {"exception", K_EXCEPTION},
+ {"functor", K_FUNCTOR}, {"fun", K_FUNCTION},
+ {"signature", K_SIGNATURE}, {"structure", K_STRUCTURE},
+ {"type", K_TYPE}, {"val", K_VAL}};
+
+static unsigned int CommentLevel = 0;
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void makeSmlTag(smlKind type, vString *name) {
+ tagEntryInfo tag;
+ initTagEntry(&tag, vStringValue(name));
+ tag.kindName = SmlKinds[type].name;
+ tag.kind = SmlKinds[type].letter;
+ makeTagEntry(&tag);
+}
+
+static const unsigned char *skipSpace(const unsigned char *cp) {
+ while (isspace((int)*cp)) ++cp;
+ return cp;
+}
+
+static boolean isIdentifier(int c) {
+ boolean result = FALSE;
+ /* Consider '_' as an delimiter to aid user in tracking it's usage. */
+ const char *const alternateIdentifiers = "!%&$#+-<>=/?@\\~'^|*_";
+ if (isalnum(c))
+ result = TRUE;
+ else if (c != '\0' && strchr(alternateIdentifiers, c) != NULL)
+ result = TRUE;
+ return result;
+}
+
+static const unsigned char *parseIdentifier(const unsigned char *cp,
+ vString *const identifier) {
+ boolean stringLit = FALSE;
+ vStringClear(identifier);
+ while (*cp != '\0' && (!isIdentifier((int)*cp) || stringLit)) {
+ int oneback = *cp;
+ cp++;
+ if (oneback == '(' && *cp == '*' && stringLit == FALSE) {
+ CommentLevel++;
+ return ++cp;
+ }
+ if (*cp == '"' && oneback != '\\') {
+ stringLit = TRUE;
+ continue;
+ }
+ if (stringLit && *cp == '"' && oneback != '\\') stringLit = FALSE;
+ }
+ if (strcmp((const char *)cp, "") == 0 || cp == NULL) return cp;
+
+ while (isIdentifier((int)*cp)) {
+ vStringPut(identifier, (int)*cp);
+ cp++;
+ }
+ vStringTerminate(identifier);
+ return cp;
+}
+
+static smlKind findNextIdentifier(const unsigned char **cp) {
+ smlKind result = K_NONE;
+ vString *const identifier = vStringNew();
+ unsigned int count = sizeof(SmlKeywordTypes) / sizeof(SmlKeywordTypes[0]);
+ unsigned int i;
+ *cp = parseIdentifier(*cp, identifier);
+ for (i = 0; i < count && result == K_NONE; ++i) {
+ const char *id = vStringValue(identifier);
+ if (strcmp(id, SmlKeywordTypes[i].keyword) == 0)
+ result = SmlKeywordTypes[i].kind;
+ }
+ vStringDelete(identifier);
+ return result;
+}
+
+static void findSmlTags(void) {
+ vString *const identifier = vStringNew();
+ const unsigned char *line;
+ smlKind lastTag = K_NONE;
+
+ while ((line = fileReadLine()) != NULL) {
+ const unsigned char *cp = skipSpace(line);
+ do {
+ smlKind foundTag;
+ if (CommentLevel != 0) {
+ cp = (const unsigned char *)strstr((const char *)cp, "*)");
+ if (cp == NULL)
+ continue;
+ else {
+ --CommentLevel;
+ cp += 2;
+ }
+ }
+ foundTag = findNextIdentifier(&cp);
+ if (foundTag != K_NONE) {
+ cp = skipSpace(cp);
+ cp = parseIdentifier(cp, identifier);
+ if (foundTag == K_AND)
+ makeSmlTag(lastTag, identifier);
+ else {
+ makeSmlTag(foundTag, identifier);
+ lastTag = foundTag;
+ }
+ }
+ if (strstr((const char *)cp, "(*") != NULL) {
+ cp += 2;
+ cp = (const unsigned char *)strstr((const char *)cp, "*)");
+ if (cp == NULL) ++CommentLevel;
+ }
+ } while (cp != NULL && strcmp((const char *)cp, "") != 0);
+ }
+ vStringDelete(identifier);
+}
+
+extern parserDefinition *SmlParser(void) {
+ static const char *const extensions[] = {"sml", "sig", NULL};
+ parserDefinition *def = parserNew("SML");
+ def->kinds = SmlKinds;
+ def->kindCount = KIND_COUNT(SmlKinds);
+ def->extensions = extensions;
+ def->parser = findSmlTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/sort.c b/third_party/ctags/sort.c
new file mode 100644
index 000000000..1de1f73f1
--- /dev/null
+++ b/third_party/ctags/sort.c
@@ -0,0 +1,196 @@
+/*
+ * $Id: sort.c 747 2009-11-06 02:33:37Z dhiebert $
+ *
+ * Copyright (c) 1996-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions to sort the tag entries.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "libc/fmt/fmt.h"
+#include "libc/mem/mem.h"
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/options.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/sort.h"
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+extern void catFile(const char *const name) {
+ FILE *const fp = fopen(name, "r");
+
+ if (fp != NULL) {
+ int c;
+ while ((c = getc(fp)) != EOF) putchar(c);
+ fflush(stdout);
+ fclose(fp);
+ }
+}
+
+#ifdef EXTERNAL_SORT
+
+#ifdef NON_CONST_PUTENV_PROTOTYPE
+#define PE_CONST
+#else
+#define PE_CONST const
+#endif
+
+extern void externalSortTags(const boolean toStdout) {
+ const char *const sortNormalCommand = "sort -u -o";
+ const char *const sortFoldedCommand = "sort -u -f -o";
+ const char *sortCommand =
+ Option.sorted == SO_FOLDSORTED ? sortFoldedCommand : sortNormalCommand;
+ PE_CONST char *const sortOrder1 = "LC_COLLATE=C";
+ PE_CONST char *const sortOrder2 = "LC_ALL=C";
+ const size_t length = 4 + strlen(sortOrder1) + strlen(sortOrder2) +
+ strlen(sortCommand) + (2 * strlen(tagFileName()));
+ char *const cmd = (char *)malloc(length + 1);
+ int ret = -1;
+
+ if (cmd != NULL) {
+ /* Ensure ASCII value sort order.
+ */
+#ifdef HAVE_SETENV
+ setenv("LC_COLLATE", "C", 1);
+ setenv("LC_ALL", "C", 1);
+ sprintf(cmd, "%s %s %s", sortCommand, tagFileName(), tagFileName());
+#else
+#ifdef HAVE_PUTENV
+ putenv(sortOrder1);
+ putenv(sortOrder2);
+ sprintf(cmd, "%s %s %s", sortCommand, tagFileName(), tagFileName());
+#else
+ sprintf(cmd, "%s %s %s %s %s", sortOrder1, sortOrder2, sortCommand,
+ tagFileName(), tagFileName());
+#endif
+#endif
+ verbose("system (\"%s\")\n", cmd);
+ ret = system(cmd);
+ free(cmd);
+ }
+ if (ret != 0)
+ error(FATAL | PERROR, "cannot sort tag file");
+ else if (toStdout)
+ catFile(tagFileName());
+}
+
+#else
+
+/*
+ * These functions provide a basic internal sort. No great memory
+ * optimization is performed (e.g. recursive subdivided sorts),
+ * so have lots of memory if you have large tag files.
+ */
+
+static void failedSort(FILE *const fp, const char *msg) {
+ const char *const cannotSort = "cannot sort tag file";
+ if (fp != NULL) fclose(fp);
+ if (msg == NULL)
+ error(FATAL | PERROR, "%s", cannotSort);
+ else
+ error(FATAL, "%s: %s", msg, cannotSort);
+}
+
+static int compareTagsFolded(const void *const one, const void *const two) {
+ const char *const line1 = *(const char *const *)one;
+ const char *const line2 = *(const char *const *)two;
+
+ return struppercmp(line1, line2);
+}
+
+static int compareTags(const void *const one, const void *const two) {
+ const char *const line1 = *(const char *const *)one;
+ const char *const line2 = *(const char *const *)two;
+
+ return strcmp(line1, line2);
+}
+
+static void writeSortedTags(char **const table, const size_t numTags,
+ const boolean toStdout) {
+ FILE *fp;
+ size_t i;
+
+ /* Write the sorted lines back into the tag file.
+ */
+ if (toStdout)
+ fp = stdout;
+ else {
+ fp = fopen(tagFileName(), "w");
+ if (fp == NULL) failedSort(fp, NULL);
+ }
+ for (i = 0; i < numTags; ++i) {
+ /* Here we filter out identical tag *lines* (including search
+ * pattern) if this is not an xref file.
+ */
+ if (i == 0 || Option.xref || strcmp(table[i], table[i - 1]) != 0)
+ if (fputs(table[i], fp) == EOF) failedSort(fp, NULL);
+ }
+ if (toStdout)
+ fflush(fp);
+ else
+ fclose(fp);
+}
+
+extern void internalSortTags(const boolean toStdout) {
+ vString *vLine = vStringNew();
+ FILE *fp = NULL;
+ const char *line;
+ size_t i;
+ int (*cmpFunc)(const void *, const void *);
+
+ /* Allocate a table of line pointers to be sorted.
+ */
+ size_t numTags = TagFile.numTags.added + TagFile.numTags.prev;
+ const size_t tableSize = numTags * sizeof(char *);
+ char **const table = (char **)malloc(tableSize); /* line pointers */
+ DebugStatement(size_t mallocSize = tableSize;) /* cumulative total */
+
+ cmpFunc =
+ Option.sorted == SO_FOLDSORTED ? compareTagsFolded : compareTags;
+ if (table == NULL) failedSort(fp, "out of memory");
+
+ /* Open the tag file and place its lines into allocated buffers.
+ */
+ fp = fopen(tagFileName(), "r");
+ if (fp == NULL) failedSort(fp, NULL);
+ for (i = 0; i < numTags && !feof(fp);) {
+ line = readLine(vLine, fp);
+ if (line == NULL) {
+ if (!feof(fp)) failedSort(fp, NULL);
+ break;
+ } else if (*line == '\0' || strcmp(line, "\n") == 0)
+ ; /* ignore blank lines */
+ else {
+ const size_t stringSize = strlen(line) + 1;
+
+ table[i] = (char *)malloc(stringSize);
+ if (table[i] == NULL) failedSort(fp, "out of memory");
+ DebugStatement(mallocSize += stringSize;) strcpy(table[i], line);
+ ++i;
+ }
+ }
+ numTags = i;
+ fclose(fp);
+ vStringDelete(vLine);
+
+ /* Sort the lines.
+ */
+ qsort(table, numTags, sizeof(*table), cmpFunc);
+
+ writeSortedTags(table, numTags, toStdout);
+
+ PrintStatus(("sort memory: %ld bytes\n", (long)mallocSize));
+ for (i = 0; i < numTags; ++i) free(table[i]);
+ free(table);
+}
+
+#endif
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/sort.h b/third_party/ctags/sort.h
new file mode 100644
index 000000000..15e12e035
--- /dev/null
+++ b/third_party/ctags/sort.h
@@ -0,0 +1,32 @@
+/*
+* $Id: sort.h 443 2006-05-30 04:37:13Z darren $
+*
+* Copyright (c) 1998-2002, Darren Hiebert
+*
+* This source code is released for free distribution under the terms of the
+* GNU General Public License.
+*
+* External interface to sort.c
+*/
+#ifndef _SORT_H
+#define _SORT_H
+
+/*
+* INCLUDE FILES
+*/
+#include "third_party/ctags/general.h" /* must always come first */
+
+/*
+* FUNCTION PROTOTYPES
+*/
+extern void catFile (const char *const name);
+
+#ifdef EXTERNAL_SORT
+extern void externalSortTags (const boolean toStdout);
+#else
+extern void internalSortTags (const boolean toStdout);
+#endif
+
+#endif /* _SORT_H */
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/sql.c b/third_party/ctags/sql.c
new file mode 100644
index 000000000..da5c8167e
--- /dev/null
+++ b/third_party/ctags/sql.c
@@ -0,0 +1,2174 @@
+/*
+ * $Id: sql.c 761 2010-06-04 12:40:28Z dfishburn $
+ *
+ * Copyright (c) 2002-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the
+ * terms of the GNU General Public License.
+ *
+ * This module contains functions for generating tags for PL/SQL language
+ * files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * On-line "Oracle Database PL/SQL Language Reference":
+ * http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28370/toc.htm
+ *
+ * Sample PL/SQL code is available from:
+ * http://www.orafaq.com/faqscrpt.htm#GENPLSQL
+ *
+ * On-line SQL Anywhere Documentation
+ * http://www.ianywhere.com/developer/product_manuals/sqlanywhere/index.html
+ */
+
+/*
+ * MACROS
+ */
+#define isType(token, t) (boolean)((token)->type == (t))
+#define isKeyword(token, k) (boolean)((token)->keyword == (k))
+
+/*
+ * DATA DECLARATIONS
+ */
+
+typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
+
+/*
+ * Used to specify type of keyword.
+ */
+typedef enum eKeywordId {
+ KEYWORD_NONE = -1,
+ KEYWORD_is,
+ KEYWORD_begin,
+ KEYWORD_body,
+ KEYWORD_cursor,
+ KEYWORD_declare,
+ KEYWORD_end,
+ KEYWORD_function,
+ KEYWORD_if,
+ KEYWORD_else,
+ KEYWORD_elseif,
+ KEYWORD_endif,
+ KEYWORD_loop,
+ KEYWORD_while,
+ KEYWORD_case,
+ KEYWORD_for,
+ KEYWORD_do,
+ KEYWORD_call,
+ KEYWORD_package,
+ KEYWORD_pragma,
+ KEYWORD_procedure,
+ KEYWORD_record,
+ KEYWORD_object,
+ KEYWORD_ref,
+ KEYWORD_rem,
+ KEYWORD_return,
+ KEYWORD_returns,
+ KEYWORD_subtype,
+ KEYWORD_table,
+ KEYWORD_trigger,
+ KEYWORD_type,
+ KEYWORD_index,
+ KEYWORD_event,
+ KEYWORD_publication,
+ KEYWORD_service,
+ KEYWORD_domain,
+ KEYWORD_datatype,
+ KEYWORD_result,
+ KEYWORD_url,
+ KEYWORD_internal,
+ KEYWORD_external,
+ KEYWORD_when,
+ KEYWORD_then,
+ KEYWORD_variable,
+ KEYWORD_exception,
+ KEYWORD_at,
+ KEYWORD_on,
+ KEYWORD_primary,
+ KEYWORD_references,
+ KEYWORD_unique,
+ KEYWORD_check,
+ KEYWORD_constraint,
+ KEYWORD_foreign,
+ KEYWORD_ml_table,
+ KEYWORD_ml_table_lang,
+ KEYWORD_ml_table_dnet,
+ KEYWORD_ml_table_java,
+ KEYWORD_ml_table_chk,
+ KEYWORD_ml_conn,
+ KEYWORD_ml_conn_lang,
+ KEYWORD_ml_conn_dnet,
+ KEYWORD_ml_conn_java,
+ KEYWORD_ml_conn_chk,
+ KEYWORD_ml_prop,
+ KEYWORD_local,
+ KEYWORD_temporary,
+ KEYWORD_drop,
+ KEYWORD_view,
+ KEYWORD_synonym,
+ KEYWORD_handler,
+ KEYWORD_comment,
+ KEYWORD_create,
+ KEYWORD_go
+} keywordId;
+
+/*
+ * Used to determine whether keyword is valid for the token language and
+ * what its ID is.
+ */
+typedef struct sKeywordDesc {
+ const char *name;
+ keywordId id;
+} keywordDesc;
+
+typedef enum eTokenType {
+ TOKEN_UNDEFINED,
+ TOKEN_BLOCK_LABEL_BEGIN,
+ TOKEN_BLOCK_LABEL_END,
+ TOKEN_CHARACTER,
+ TOKEN_CLOSE_PAREN,
+ TOKEN_COLON,
+ TOKEN_SEMICOLON,
+ TOKEN_COMMA,
+ TOKEN_IDENTIFIER,
+ TOKEN_KEYWORD,
+ TOKEN_OPEN_PAREN,
+ TOKEN_OPERATOR,
+ TOKEN_OTHER,
+ TOKEN_STRING,
+ TOKEN_PERIOD,
+ TOKEN_OPEN_CURLY,
+ TOKEN_CLOSE_CURLY,
+ TOKEN_OPEN_SQUARE,
+ TOKEN_CLOSE_SQUARE,
+ TOKEN_TILDE,
+ TOKEN_FORWARD_SLASH,
+ TOKEN_EQUAL
+} tokenType;
+
+typedef struct sTokenInfoSQL {
+ tokenType type;
+ keywordId keyword;
+ vString *string;
+ vString *scope;
+ int begin_end_nest_lvl;
+ unsigned long lineNumber;
+ fpos_t filePosition;
+} tokenInfo;
+
+/*
+ * DATA DEFINITIONS
+ */
+
+static langType Lang_sql;
+
+static jmp_buf Exception;
+
+typedef enum {
+ SQLTAG_CURSOR,
+ SQLTAG_PROTOTYPE,
+ SQLTAG_FUNCTION,
+ SQLTAG_FIELD,
+ SQLTAG_LOCAL_VARIABLE,
+ SQLTAG_BLOCK_LABEL,
+ SQLTAG_PACKAGE,
+ SQLTAG_PROCEDURE,
+ SQLTAG_RECORD,
+ SQLTAG_SUBTYPE,
+ SQLTAG_TABLE,
+ SQLTAG_TRIGGER,
+ SQLTAG_VARIABLE,
+ SQLTAG_INDEX,
+ SQLTAG_EVENT,
+ SQLTAG_PUBLICATION,
+ SQLTAG_SERVICE,
+ SQLTAG_DOMAIN,
+ SQLTAG_VIEW,
+ SQLTAG_SYNONYM,
+ SQLTAG_MLTABLE,
+ SQLTAG_MLCONN,
+ SQLTAG_MLPROP,
+ SQLTAG_COUNT
+} sqlKind;
+
+static kindOption SqlKinds[] = {
+ {TRUE, 'c', "cursor", "cursors"},
+ {FALSE, 'd', "prototype", "prototypes"},
+ {TRUE, 'f', "function", "functions"},
+ {TRUE, 'F', "field", "record fields"},
+ {FALSE, 'l', "local", "local variables"},
+ {TRUE, 'L', "label", "block label"},
+ {TRUE, 'P', "package", "packages"},
+ {TRUE, 'p', "procedure", "procedures"},
+ {FALSE, 'r', "record", "records"},
+ {TRUE, 's', "subtype", "subtypes"},
+ {TRUE, 't', "table", "tables"},
+ {TRUE, 'T', "trigger", "triggers"},
+ {TRUE, 'v', "variable", "variables"},
+ {TRUE, 'i', "index", "indexes"},
+ {TRUE, 'e', "event", "events"},
+ {TRUE, 'U', "publication", "publications"},
+ {TRUE, 'R', "service", "services"},
+ {TRUE, 'D', "domain", "domains"},
+ {TRUE, 'V', "view", "views"},
+ {TRUE, 'n', "synonym", "synonyms"},
+ {TRUE, 'x', "mltable", "MobiLink Table Scripts"},
+ {TRUE, 'y', "mlconn", "MobiLink Conn Scripts"},
+ {TRUE, 'z', "mlprop", "MobiLink Properties "}};
+
+static const keywordDesc SqlKeywordTable[] = {
+ /* keyword keyword ID */
+ {"as", KEYWORD_is},
+ {"is", KEYWORD_is},
+ {"begin", KEYWORD_begin},
+ {"body", KEYWORD_body},
+ {"cursor", KEYWORD_cursor},
+ {"declare", KEYWORD_declare},
+ {"end", KEYWORD_end},
+ {"function", KEYWORD_function},
+ {"if", KEYWORD_if},
+ {"else", KEYWORD_else},
+ {"elseif", KEYWORD_elseif},
+ {"endif", KEYWORD_endif},
+ {"loop", KEYWORD_loop},
+ {"while", KEYWORD_while},
+ {"case", KEYWORD_case},
+ {"for", KEYWORD_for},
+ {"do", KEYWORD_do},
+ {"call", KEYWORD_call},
+ {"package", KEYWORD_package},
+ {"pragma", KEYWORD_pragma},
+ {"procedure", KEYWORD_procedure},
+ {"record", KEYWORD_record},
+ {"object", KEYWORD_object},
+ {"ref", KEYWORD_ref},
+ {"rem", KEYWORD_rem},
+ {"return", KEYWORD_return},
+ {"returns", KEYWORD_returns},
+ {"subtype", KEYWORD_subtype},
+ {"table", KEYWORD_table},
+ {"trigger", KEYWORD_trigger},
+ {"type", KEYWORD_type},
+ {"index", KEYWORD_index},
+ {"event", KEYWORD_event},
+ {"publication", KEYWORD_publication},
+ {"service", KEYWORD_service},
+ {"domain", KEYWORD_domain},
+ {"datatype", KEYWORD_datatype},
+ {"result", KEYWORD_result},
+ {"url", KEYWORD_url},
+ {"internal", KEYWORD_internal},
+ {"external", KEYWORD_external},
+ {"when", KEYWORD_when},
+ {"then", KEYWORD_then},
+ {"variable", KEYWORD_variable},
+ {"exception", KEYWORD_exception},
+ {"at", KEYWORD_at},
+ {"on", KEYWORD_on},
+ {"primary", KEYWORD_primary},
+ {"references", KEYWORD_references},
+ {"unique", KEYWORD_unique},
+ {"check", KEYWORD_check},
+ {"constraint", KEYWORD_constraint},
+ {"foreign", KEYWORD_foreign},
+ {"ml_add_table_script", KEYWORD_ml_table},
+ {"ml_add_lang_table_script", KEYWORD_ml_table_lang},
+ {"ml_add_dnet_table_script", KEYWORD_ml_table_dnet},
+ {"ml_add_java_table_script", KEYWORD_ml_table_java},
+ {"ml_add_lang_table_script_chk", KEYWORD_ml_table_chk},
+ {"ml_add_connection_script", KEYWORD_ml_conn},
+ {"ml_add_lang_connection_script", KEYWORD_ml_conn_lang},
+ {"ml_add_dnet_connection_script", KEYWORD_ml_conn_dnet},
+ {"ml_add_java_connection_script", KEYWORD_ml_conn_java},
+ {"ml_add_lang_conn_script_chk", KEYWORD_ml_conn_chk},
+ {"ml_add_property", KEYWORD_ml_prop},
+ {"local", KEYWORD_local},
+ {"temporary", KEYWORD_temporary},
+ {"drop", KEYWORD_drop},
+ {"view", KEYWORD_view},
+ {"synonym", KEYWORD_synonym},
+ {"handler", KEYWORD_handler},
+ {"comment", KEYWORD_comment},
+ {"create", KEYWORD_create},
+ {"go", KEYWORD_go}};
+
+/*
+ * FUNCTION DECLARATIONS
+ */
+
+/* Recursive calls */
+static void parseBlock(tokenInfo *const token, const boolean local);
+static void parseDeclare(tokenInfo *const token, const boolean local);
+static void parseKeywords(tokenInfo *const token);
+static void parseSqlFile(tokenInfo *const token);
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static boolean isIdentChar1(const int c) {
+ /*
+ * Other databases are less restrictive on the first character of
+ * an identifier.
+ * isIdentChar1 is used to identify the first character of an
+ * identifier, so we are removing some restrictions.
+ */
+ return (boolean)(isalpha(c) || c == '@' || c == '_');
+}
+
+static boolean isIdentChar(const int c) {
+ return (boolean)(isalpha(c) || isdigit(c) || c == '$' || c == '@' ||
+ c == '_' || c == '#');
+}
+
+static boolean isCmdTerm(tokenInfo *const token) {
+ DebugStatement(debugPrintf(DEBUG_PARSE,
+ "\n isCmdTerm: token same tt:%d tk:%d\n",
+ token->type, token->keyword););
+
+ /*
+ * Based on the various customer sites I have been at
+ * the most common command delimiters are
+ * ;
+ * ~
+ * /
+ * go
+ * This routine will check for any of these, more
+ * can easily be added by modifying readToken and
+ * either adding the character to:
+ * enum eTokenType
+ * enum eTokenType
+ */
+ return (isType(token, TOKEN_SEMICOLON) || isType(token, TOKEN_TILDE) ||
+ isType(token, TOKEN_FORWARD_SLASH) || isKeyword(token, KEYWORD_go));
+}
+
+static boolean isMatchedEnd(tokenInfo *const token, int nest_lvl) {
+ boolean terminated = FALSE;
+ /*
+ * Since different forms of SQL allow the use of
+ * BEGIN
+ * ...
+ * END
+ * blocks, some statements may not be terminated using
+ * the standard delimiters:
+ * ;
+ * ~
+ * /
+ * go
+ * This routine will check to see if we encounter and END
+ * for the matching nest level of BEGIN ... END statements.
+ * If we find one, then we can assume, the statement was terminated
+ * since we have fallen through to the END statement of the BEGIN
+ * block.
+ */
+ if (nest_lvl > 0 && isKeyword(token, KEYWORD_end)) {
+ if (token->begin_end_nest_lvl == nest_lvl) terminated = TRUE;
+ }
+
+ return terminated;
+}
+
+static void buildSqlKeywordHash(void) {
+ const size_t count = sizeof(SqlKeywordTable) / sizeof(SqlKeywordTable[0]);
+ size_t i;
+ for (i = 0; i < count; ++i) {
+ const keywordDesc *const p = &SqlKeywordTable[i];
+ addKeyword(p->name, Lang_sql, (int)p->id);
+ }
+}
+
+static tokenInfo *newToken(void) {
+ tokenInfo *const token = xMalloc(1, tokenInfo);
+
+ token->type = TOKEN_UNDEFINED;
+ token->keyword = KEYWORD_NONE;
+ token->string = vStringNew();
+ token->scope = vStringNew();
+ token->begin_end_nest_lvl = 0;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+
+ return token;
+}
+
+static void deleteToken(tokenInfo *const token) {
+ vStringDelete(token->string);
+ vStringDelete(token->scope);
+ eFree(token);
+}
+
+/*
+ * Tag generation functions
+ */
+
+static void makeConstTag(tokenInfo *const token, const sqlKind kind) {
+ if (SqlKinds[kind].enabled) {
+ const char *const name = vStringValue(token->string);
+ tagEntryInfo e;
+ initTagEntry(&e, name);
+
+ e.lineNumber = token->lineNumber;
+ e.filePosition = token->filePosition;
+ e.kindName = SqlKinds[kind].name;
+ e.kind = SqlKinds[kind].letter;
+
+ makeTagEntry(&e);
+ }
+}
+
+static void makeSqlTag(tokenInfo *const token, const sqlKind kind) {
+ vString *fulltag;
+
+ if (SqlKinds[kind].enabled) {
+ /*
+ * If a scope has been added to the token, change the token
+ * string to include the scope when making the tag.
+ */
+ if (vStringLength(token->scope) > 0) {
+ fulltag = vStringNew();
+ vStringCopy(fulltag, token->scope);
+ vStringCatS(fulltag, ".");
+ vStringCatS(fulltag, vStringValue(token->string));
+ vStringTerminate(fulltag);
+ vStringCopy(token->string, fulltag);
+ vStringDelete(fulltag);
+ }
+ makeConstTag(token, kind);
+ }
+}
+
+/*
+ * Parsing functions
+ */
+
+static void parseString(vString *const string, const int delimiter) {
+ boolean end = FALSE;
+ while (!end) {
+ int c = fileGetc();
+ if (c == EOF) end = TRUE;
+ /*
+ else if (c == '\\')
+ {
+ c = fileGetc(); // This maybe a ' or ". //
+ vStringPut(string, c);
+ }
+ */
+ else if (c == delimiter)
+ end = TRUE;
+ else
+ vStringPut(string, c);
+ }
+ vStringTerminate(string);
+}
+
+/* Read a C identifier beginning with "firstChar" and places it into
+ * "name".
+ */
+static void parseIdentifier(vString *const string, const int firstChar) {
+ int c = firstChar;
+ Assert(isIdentChar1(c));
+ do {
+ vStringPut(string, c);
+ c = fileGetc();
+ } while (isIdentChar(c));
+ vStringTerminate(string);
+ if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */
+}
+
+static void readToken(tokenInfo *const token) {
+ int c;
+
+ token->type = TOKEN_UNDEFINED;
+ token->keyword = KEYWORD_NONE;
+ vStringClear(token->string);
+
+getNextChar:
+ do {
+ c = fileGetc();
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ /*
+ * Added " to the list of ignores, not sure what this
+ * might break but it gets by this issue:
+ * create table "t1" (...)
+ *
+ * Darren, the code passes all my tests for both
+ * Oracle and SQL Anywhere, but maybe you can tell me
+ * what this may effect.
+ */
+ } while (c == '\t' || c == ' ' || c == '\n');
+
+ switch (c) {
+ case EOF:
+ longjmp(Exception, (int)ExceptionEOF);
+ break;
+ case '(':
+ token->type = TOKEN_OPEN_PAREN;
+ break;
+ case ')':
+ token->type = TOKEN_CLOSE_PAREN;
+ break;
+ case ':':
+ token->type = TOKEN_COLON;
+ break;
+ case ';':
+ token->type = TOKEN_SEMICOLON;
+ break;
+ case '.':
+ token->type = TOKEN_PERIOD;
+ break;
+ case ',':
+ token->type = TOKEN_COMMA;
+ break;
+ case '{':
+ token->type = TOKEN_OPEN_CURLY;
+ break;
+ case '}':
+ token->type = TOKEN_CLOSE_CURLY;
+ break;
+ case '~':
+ token->type = TOKEN_TILDE;
+ break;
+ case '[':
+ token->type = TOKEN_OPEN_SQUARE;
+ break;
+ case ']':
+ token->type = TOKEN_CLOSE_SQUARE;
+ break;
+ case '=':
+ token->type = TOKEN_EQUAL;
+ break;
+
+ case '\'':
+ case '"':
+ token->type = TOKEN_STRING;
+ parseString(token->string, c);
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+
+ case '-':
+ c = fileGetc();
+ if (c == '-') /* -- is this the start of a comment? */
+ {
+ fileSkipToCharacter('\n');
+ goto getNextChar;
+ } else {
+ if (!isspace(c)) fileUngetc(c);
+ token->type = TOKEN_OPERATOR;
+ }
+ break;
+
+ case '<':
+ case '>': {
+ const int initial = c;
+ int d = fileGetc();
+ if (d == initial) {
+ if (initial == '<')
+ token->type = TOKEN_BLOCK_LABEL_BEGIN;
+ else
+ token->type = TOKEN_BLOCK_LABEL_END;
+ } else {
+ fileUngetc(d);
+ token->type = TOKEN_UNDEFINED;
+ }
+ break;
+ }
+
+ case '\\':
+ c = fileGetc();
+ if (c != '\\' && c != '"' && c != '\'' && !isspace(c)) fileUngetc(c);
+ token->type = TOKEN_CHARACTER;
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+
+ case '/': {
+ int d = fileGetc();
+ if ((d != '*') && /* is this the start of a comment? */
+ (d != '/')) /* is a one line comment? */
+ {
+ token->type = TOKEN_FORWARD_SLASH;
+ fileUngetc(d);
+ } else {
+ if (d == '*') {
+ do {
+ fileSkipToCharacter('*');
+ c = fileGetc();
+ if (c == '/')
+ break;
+ else
+ fileUngetc(c);
+ } while (c != EOF && c != '\0');
+ goto getNextChar;
+ } else if (d == '/') /* is this the start of a comment? */
+ {
+ fileSkipToCharacter('\n');
+ goto getNextChar;
+ }
+ }
+ break;
+ }
+
+ default:
+ if (!isIdentChar1(c))
+ token->type = TOKEN_UNDEFINED;
+ else {
+ parseIdentifier(token->string, c);
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ token->keyword = analyzeToken(token->string, Lang_sql);
+ if (isKeyword(token, KEYWORD_rem)) {
+ vStringClear(token->string);
+ fileSkipToCharacter('\n');
+ goto getNextChar;
+ } else if (isKeyword(token, KEYWORD_NONE))
+ token->type = TOKEN_IDENTIFIER;
+ else
+ token->type = TOKEN_KEYWORD;
+ }
+ break;
+ }
+}
+
+/*
+ * Token parsing functions
+ */
+
+/*
+ * static void addContext (tokenInfo* const parent, const tokenInfo* const
+ *child)
+ * {
+ * if (vStringLength (parent->string) > 0)
+ * {
+ * vStringCatS (parent->string, ".");
+ * }
+ * vStringCatS (parent->string, vStringValue(child->string));
+ * vStringTerminate(parent->string);
+ * }
+ */
+
+static void addToScope(tokenInfo *const token, vString *const extra) {
+ if (vStringLength(token->scope) > 0) {
+ vStringCatS(token->scope, ".");
+ }
+ vStringCatS(token->scope, vStringValue(extra));
+ vStringTerminate(token->scope);
+}
+
+/*
+ * Scanning functions
+ */
+
+static void findToken(tokenInfo *const token, const tokenType type) {
+ while (!isType(token, type)) {
+ readToken(token);
+ }
+}
+
+static void findCmdTerm(tokenInfo *const token, const boolean check_first) {
+ int begin_end_nest_lvl = token->begin_end_nest_lvl;
+
+ if (check_first) {
+ if (isCmdTerm(token)) return;
+ }
+ do {
+ readToken(token);
+ } while (!isCmdTerm(token) && !isMatchedEnd(token, begin_end_nest_lvl));
+}
+
+static void skipToMatched(tokenInfo *const token) {
+ int nest_level = 0;
+ tokenType open_token;
+ tokenType close_token;
+
+ switch (token->type) {
+ case TOKEN_OPEN_PAREN:
+ open_token = TOKEN_OPEN_PAREN;
+ close_token = TOKEN_CLOSE_PAREN;
+ break;
+ case TOKEN_OPEN_CURLY:
+ open_token = TOKEN_OPEN_CURLY;
+ close_token = TOKEN_CLOSE_CURLY;
+ break;
+ case TOKEN_OPEN_SQUARE:
+ open_token = TOKEN_OPEN_SQUARE;
+ close_token = TOKEN_CLOSE_SQUARE;
+ break;
+ default:
+ return;
+ }
+
+ /*
+ * This routine will skip to a matching closing token.
+ * It will also handle nested tokens like the (, ) below.
+ * ( name varchar(30), text binary(10) )
+ */
+
+ if (isType(token, open_token)) {
+ nest_level++;
+ while (!(isType(token, close_token) && (nest_level == 0))) {
+ readToken(token);
+ if (isType(token, open_token)) {
+ nest_level++;
+ }
+ if (isType(token, close_token)) {
+ if (nest_level > 0) {
+ nest_level--;
+ }
+ }
+ }
+ readToken(token);
+ }
+}
+
+static void copyToken(tokenInfo *const dest, tokenInfo *const src) {
+ dest->lineNumber = src->lineNumber;
+ dest->filePosition = src->filePosition;
+ dest->type = src->type;
+ dest->keyword = src->keyword;
+ vStringCopy(dest->string, src->string);
+ vStringCopy(dest->scope, src->scope);
+}
+
+static void skipArgumentList(tokenInfo *const token) {
+ /*
+ * Other databases can have arguments with fully declared
+ * datatypes:
+ * ( name varchar(30), text binary(10) )
+ * So we must check for nested open and closing parantheses
+ */
+
+ if (isType(token, TOKEN_OPEN_PAREN)) /* arguments? */
+ {
+ skipToMatched(token);
+ }
+}
+
+static void parseSubProgram(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+ vString *saveScope = vStringNew();
+
+ /*
+ * This must handle both prototypes and the body of
+ * the procedures.
+ *
+ * Prototype:
+ * FUNCTION func_name RETURN integer;
+ * PROCEDURE proc_name( parameters );
+ * Procedure
+ * FUNCTION GET_ML_USERNAME RETURN VARCHAR2
+ * IS
+ * BEGIN
+ * RETURN v_sync_user_id;
+ * END GET_ML_USERNAME;
+ *
+ * PROCEDURE proc_name( parameters )
+ * IS
+ * BEGIN
+ * END;
+ * CREATE PROCEDURE proc_name( parameters )
+ * EXTERNAL NAME ... ;
+ * CREATE PROCEDURE proc_name( parameters )
+ * BEGIN
+ * END;
+ *
+ * CREATE FUNCTION f_GetClassName(
+ * IN @object VARCHAR(128)
+ * ,IN @code VARCHAR(128)
+ * )
+ * RETURNS VARCHAR(200)
+ * DETERMINISTIC
+ * BEGIN
+ *
+ * IF( @object = 'user_state' ) THEN
+ * SET something = something;
+ * END IF;
+ *
+ * RETURN @name;
+ * END;
+ *
+ * Note, a Package adds scope to the items within.
+ * create or replace package demo_pkg is
+ * test_var number;
+ * function test_func return varchar2;
+ * function more.test_func2 return varchar2;
+ * end demo_pkg;
+ * So the tags generated here, contain the package name:
+ * demo_pkg.test_var
+ * demo_pkg.test_func
+ * demo_pkg.more.test_func2
+ */
+ const sqlKind kind =
+ isKeyword(token, KEYWORD_function) ? SQLTAG_FUNCTION : SQLTAG_PROCEDURE;
+ Assert(isKeyword(token, KEYWORD_function) ||
+ isKeyword(token, KEYWORD_procedure));
+
+ vStringCopy(saveScope, token->scope);
+ readToken(token);
+ copyToken(name, token);
+ readToken(token);
+
+ if (isType(token, TOKEN_PERIOD)) {
+ /*
+ * If this is an Oracle package, then the token->scope should
+ * already be set. If this is the case, also add this value to the
+ * scope.
+ * If this is not an Oracle package, chances are the scope should be
+ * blank and the value just read is the OWNER or CREATOR of the
+ * function and should not be considered part of the scope.
+ */
+ if (vStringLength(saveScope) > 0) {
+ addToScope(token, name->string);
+ }
+ readToken(token);
+ copyToken(name, token);
+ readToken(token);
+ }
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ /* Reads to the next token after the TOKEN_CLOSE_PAREN */
+ skipArgumentList(token);
+ }
+
+ if (kind == SQLTAG_FUNCTION) {
+ if (isKeyword(token, KEYWORD_return) || isKeyword(token, KEYWORD_returns)) {
+ /* Read datatype */
+ readToken(token);
+ /*
+ * Read token after which could be the
+ * command terminator if a prototype
+ * or an open parantheses
+ */
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ /* Reads to the next token after the TOKEN_CLOSE_PAREN */
+ skipArgumentList(token);
+ }
+ }
+ }
+ if (isCmdTerm(token)) {
+ makeSqlTag(name, SQLTAG_PROTOTYPE);
+ } else {
+ while (
+ !(isKeyword(token, KEYWORD_is) || isKeyword(token, KEYWORD_begin) ||
+ isKeyword(token, KEYWORD_at) || isKeyword(token, KEYWORD_internal) ||
+ isKeyword(token, KEYWORD_external) || isKeyword(token, KEYWORD_url) ||
+ isType(token, TOKEN_EQUAL) || isCmdTerm(token))) {
+ if (isKeyword(token, KEYWORD_result)) {
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ /* Reads to the next token after the TOKEN_CLOSE_PAREN */
+ skipArgumentList(token);
+ }
+ } else {
+ readToken(token);
+ }
+ }
+ if (isKeyword(token, KEYWORD_at) || isKeyword(token, KEYWORD_url) ||
+ isKeyword(token, KEYWORD_internal) ||
+ isKeyword(token, KEYWORD_external)) {
+ addToScope(token, name->string);
+ if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING) ||
+ !isKeyword(token, KEYWORD_NONE))
+ makeSqlTag(name, kind);
+
+ vStringClear(token->scope);
+ }
+ if (isType(token, TOKEN_EQUAL)) readToken(token);
+
+ if (isKeyword(token, KEYWORD_declare)) parseDeclare(token, FALSE);
+
+ if (isKeyword(token, KEYWORD_is) || isKeyword(token, KEYWORD_begin)) {
+ addToScope(token, name->string);
+ if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING) ||
+ !isKeyword(token, KEYWORD_NONE))
+ makeSqlTag(name, kind);
+
+ parseBlock(token, TRUE);
+ vStringClear(token->scope);
+ }
+ }
+ vStringCopy(token->scope, saveScope);
+ deleteToken(name);
+ vStringDelete(saveScope);
+}
+
+static void parseRecord(tokenInfo *const token) {
+ /*
+ * Make it a bit forgiving, this is called from
+ * multiple functions, parseTable, parseType
+ */
+ if (!isType(token, TOKEN_OPEN_PAREN)) readToken(token);
+
+ Assert(isType(token, TOKEN_OPEN_PAREN));
+ do {
+ if (isType(token, TOKEN_COMMA) || isType(token, TOKEN_OPEN_PAREN))
+ readToken(token);
+
+ /*
+ * Create table statements can end with various constraints
+ * which must be excluded from the SQLTAG_FIELD.
+ * create table t1 (
+ * c1 integer,
+ * c2 char(30),
+ * c3 numeric(10,5),
+ * c4 integer,
+ * constraint whatever,
+ * primary key(c1),
+ * foreign key (),
+ * check ()
+ * )
+ */
+ if (!(isKeyword(token, KEYWORD_primary) ||
+ isKeyword(token, KEYWORD_references) ||
+ isKeyword(token, KEYWORD_unique) || isKeyword(token, KEYWORD_check) ||
+ isKeyword(token, KEYWORD_constraint) ||
+ isKeyword(token, KEYWORD_foreign))) {
+ if (isType(token, TOKEN_IDENTIFIER) || isType(token, TOKEN_STRING))
+ makeSqlTag(token, SQLTAG_FIELD);
+ }
+
+ while (!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN) ||
+ isType(token, TOKEN_OPEN_PAREN))) {
+ readToken(token);
+ /*
+ * A table structure can look like this:
+ * create table t1 (
+ * c1 integer,
+ * c2 char(30),
+ * c3 numeric(10,5),
+ * c4 integer
+ * )
+ * We can't just look for a COMMA or CLOSE_PAREN
+ * since that will not deal with the numeric(10,5)
+ * case. So we need to skip the argument list
+ * when we find an open paren.
+ */
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ /* Reads to the next token after the TOKEN_CLOSE_PAREN */
+ skipArgumentList(token);
+ }
+ }
+ } while (!isType(token, TOKEN_CLOSE_PAREN));
+}
+
+static void parseType(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+ vString *saveScope = vStringNew();
+
+ vStringCopy(saveScope, token->scope);
+ /* If a scope has been set, add it to the name */
+ addToScope(name, token->scope);
+ readToken(name);
+ if (isType(name, TOKEN_IDENTIFIER)) {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_is)) {
+ readToken(token);
+ addToScope(token, name->string);
+ switch (token->keyword) {
+ case KEYWORD_record:
+ case KEYWORD_object:
+ makeSqlTag(name, SQLTAG_RECORD);
+ parseRecord(token);
+ break;
+
+ case KEYWORD_table:
+ makeSqlTag(name, SQLTAG_TABLE);
+ break;
+
+ case KEYWORD_ref:
+ readToken(token);
+ if (isKeyword(token, KEYWORD_cursor)) makeSqlTag(name, SQLTAG_CURSOR);
+ break;
+
+ default:
+ break;
+ }
+ vStringClear(token->scope);
+ }
+ }
+ vStringCopy(token->scope, saveScope);
+ deleteToken(name);
+ vStringDelete(saveScope);
+}
+
+static void parseSimple(tokenInfo *const token, const sqlKind kind) {
+ /* This will simply make the tagname from the first word found */
+ readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER) || isType(token, TOKEN_STRING))
+ makeSqlTag(token, kind);
+}
+
+static void parseDeclare(tokenInfo *const token, const boolean local) {
+ /*
+ * PL/SQL declares are of this format:
+ * IS|AS
+ * [declare]
+ * CURSOR curname ...
+ * varname1 datatype;
+ * varname2 datatype;
+ * varname3 datatype;
+ * begin
+ */
+
+ if (isKeyword(token, KEYWORD_declare)) readToken(token);
+ while (!isKeyword(token, KEYWORD_begin) && !isKeyword(token, KEYWORD_end)) {
+ switch (token->keyword) {
+ case KEYWORD_cursor:
+ parseSimple(token, SQLTAG_CURSOR);
+ break;
+ case KEYWORD_function:
+ parseSubProgram(token);
+ break;
+ case KEYWORD_procedure:
+ parseSubProgram(token);
+ break;
+ case KEYWORD_subtype:
+ parseSimple(token, SQLTAG_SUBTYPE);
+ break;
+ case KEYWORD_trigger:
+ parseSimple(token, SQLTAG_TRIGGER);
+ break;
+ case KEYWORD_type:
+ parseType(token);
+ break;
+
+ default:
+ if (isType(token, TOKEN_IDENTIFIER)) {
+ if (local) {
+ makeSqlTag(token, SQLTAG_LOCAL_VARIABLE);
+ } else {
+ makeSqlTag(token, SQLTAG_VARIABLE);
+ }
+ }
+ break;
+ }
+ findToken(token, TOKEN_SEMICOLON);
+ readToken(token);
+ }
+}
+
+static void parseDeclareANSI(tokenInfo *const token, const boolean local) {
+ tokenInfo *const type = newToken();
+ /*
+ * ANSI declares are of this format:
+ * BEGIN
+ * DECLARE varname1 datatype;
+ * DECLARE varname2 datatype;
+ * ...
+ *
+ * This differ from PL/SQL where DECLARE preceeds the BEGIN block
+ * and the DECLARE keyword is not repeated.
+ */
+ while (isKeyword(token, KEYWORD_declare)) {
+ readToken(token);
+ readToken(type);
+
+ if (isKeyword(type, KEYWORD_cursor))
+ makeSqlTag(token, SQLTAG_CURSOR);
+ else if (isKeyword(token, KEYWORD_local) &&
+ isKeyword(type, KEYWORD_temporary)) {
+ /*
+ * DECLARE LOCAL TEMPORARY TABLE table_name (
+ * c1 int,
+ * c2 int
+ * );
+ */
+ readToken(token);
+ if (isKeyword(token, KEYWORD_table)) {
+ readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER) || isType(token, TOKEN_STRING)) {
+ makeSqlTag(token, SQLTAG_TABLE);
+ }
+ }
+ } else if (isType(token, TOKEN_IDENTIFIER) || isType(token, TOKEN_STRING)) {
+ if (local)
+ makeSqlTag(token, SQLTAG_LOCAL_VARIABLE);
+ else
+ makeSqlTag(token, SQLTAG_VARIABLE);
+ }
+ findToken(token, TOKEN_SEMICOLON);
+ readToken(token);
+ }
+ deleteToken(type);
+}
+
+static void parseLabel(tokenInfo *const token) {
+ /*
+ * A label has this format:
+ * <>
+ * DECLARE
+ * v_senator VARCHAR2(100) := 'THURMOND, JESSE';
+ * BEGIN
+ * IF total_contributions (v_senator, 'TOBACCO') > 25000
+ * THEN
+ * <>
+ * DECLARE
+ * v_senator VARCHAR2(100) := 'WHATEVERIT, TAKES';
+ * BEGIN
+ * ...
+ */
+
+ Assert(isType(token, TOKEN_BLOCK_LABEL_BEGIN));
+ readToken(token);
+ if (isType(token, TOKEN_IDENTIFIER)) {
+ makeSqlTag(token, SQLTAG_BLOCK_LABEL);
+ readToken(token); /* read end of label */
+ }
+}
+
+static void parseStatements(tokenInfo *const token,
+ const boolean exit_on_endif) {
+ boolean isAnsi = TRUE;
+ boolean stmtTerm = FALSE;
+ do {
+
+ if (isType(token, TOKEN_BLOCK_LABEL_BEGIN))
+ parseLabel(token);
+ else {
+ switch (token->keyword) {
+ case KEYWORD_exception:
+ /*
+ * EXCEPTION
+ * ;
+ *
+ * Where an exception handler could be:
+ * BEGIN
+ * WHEN OTHERS THEN
+ * x := x + 3;
+ * END;
+ * In this case we need to skip this keyword and
+ * move on to the next token without reading until
+ * TOKEN_SEMICOLON;
+ */
+ readToken(token);
+ continue;
+
+ case KEYWORD_when:
+ /*
+ * WHEN statements can be used in exception clauses
+ * and CASE statements. The CASE statement should skip
+ * these given below we skip over to an END statement.
+ * But for an exception clause, we can have:
+ * EXCEPTION
+ * WHEN OTHERS THEN
+ * BEGIN
+ * x := x + 3;
+ * END;
+ * If we skip to the TOKEN_SEMICOLON, we miss the begin
+ * of a nested BEGIN END block. So read the next token
+ * after the THEN and restart the LOOP.
+ */
+ while (!isKeyword(token, KEYWORD_then)) readToken(token);
+
+ readToken(token);
+ continue;
+
+ case KEYWORD_if:
+ /*
+ * We do not want to look for a ; since for an empty
+ * IF block, it would skip over the END.
+ * IF...THEN
+ * END IF;
+ *
+ * IF...THEN
+ * ELSE
+ * END IF;
+ *
+ * IF...THEN
+ * ELSEIF...THEN
+ * ELSE
+ * END IF;
+ *
+ * or non-ANSI
+ * IF ...
+ * BEGIN
+ * END
+ */
+ while (!isKeyword(token, KEYWORD_then) &&
+ !isKeyword(token, KEYWORD_begin)) {
+ readToken(token);
+ }
+
+ if (isKeyword(token, KEYWORD_begin)) {
+ isAnsi = FALSE;
+ parseBlock(token, FALSE);
+
+ /*
+ * Handle the non-Ansi IF blocks.
+ * parseBlock consumes the END, so if the next
+ * token in a command terminator (like GO)
+ * we know we are done with this statement.
+ */
+ if (isCmdTerm(token)) stmtTerm = TRUE;
+ } else {
+ readToken(token);
+
+ while (!(isKeyword(token, KEYWORD_end) ||
+ isKeyword(token, KEYWORD_endif))) {
+ if (isKeyword(token, KEYWORD_else) ||
+ isKeyword(token, KEYWORD_elseif))
+ readToken(token);
+
+ parseStatements(token, TRUE);
+
+ if (isCmdTerm(token)) readToken(token);
+ }
+
+ /*
+ * parseStatements returns when it finds an END, an IF
+ * should follow the END for ANSI anyway.
+ * IF...THEN
+ * END IF;
+ */
+ if (isKeyword(token, KEYWORD_end)) readToken(token);
+
+ if (isKeyword(token, KEYWORD_if) ||
+ isKeyword(token, KEYWORD_endif)) {
+ readToken(token);
+ if (isCmdTerm(token)) stmtTerm = TRUE;
+ } else {
+ /*
+ * Well we need to do something here.
+ * There are lots of different END statements
+ * END;
+ * END CASE;
+ * ENDIF;
+ * ENDCASE;
+ */
+ }
+ }
+ break;
+
+ case KEYWORD_loop:
+ case KEYWORD_case:
+ case KEYWORD_for:
+ /*
+ * LOOP...
+ * END LOOP;
+ *
+ * CASE
+ * WHEN '1' THEN
+ * END CASE;
+ *
+ * FOR loop_name AS cursor_name CURSOR FOR ...
+ * DO
+ * END FOR;
+ */
+ if (isKeyword(token, KEYWORD_for)) {
+ /* loop name */
+ readToken(token);
+ /* AS */
+ readToken(token);
+
+ while (!isKeyword(token, KEYWORD_is)) {
+ /*
+ * If this is not an AS keyword this is
+ * not a proper FOR statement and should
+ * simply be ignored
+ */
+ return;
+ }
+
+ while (!isKeyword(token, KEYWORD_do)) readToken(token);
+ }
+
+ readToken(token);
+ while (!isKeyword(token, KEYWORD_end)) {
+ /*
+ if ( isKeyword (token, KEYWORD_else) ||
+ isKeyword (token, KEYWORD_elseif) )
+ readToken (token);
+ */
+
+ parseStatements(token, FALSE);
+
+ if (isCmdTerm(token)) readToken(token);
+ }
+
+ if (isKeyword(token, KEYWORD_end)) readToken(token);
+
+ /*
+ * Typically ended with
+ * END LOOP [loop name];
+ * END CASE
+ * END FOR [loop name];
+ */
+ if (isKeyword(token, KEYWORD_loop) ||
+ isKeyword(token, KEYWORD_case) || isKeyword(token, KEYWORD_for))
+ readToken(token);
+
+ if (isCmdTerm(token)) stmtTerm = TRUE;
+
+ break;
+
+ case KEYWORD_create:
+ readToken(token);
+ parseKeywords(token);
+ break;
+
+ case KEYWORD_declare:
+ case KEYWORD_begin:
+ parseBlock(token, TRUE);
+ break;
+
+ case KEYWORD_end:
+ break;
+
+ default:
+ readToken(token);
+ break;
+ }
+ /*
+ * Not all statements must end in a semi-colon
+ * begin
+ * if current publisher <> 'publish' then
+ * signal UE_FailStatement
+ * end if
+ * end;
+ * The last statement prior to an end ("signal" above) does
+ * not need a semi-colon, nor does the end if, since it is
+ * also the last statement prior to the end of the block.
+ *
+ * So we must read to the first semi-colon or an END block
+ */
+ while (!stmtTerm &&
+ !(isKeyword(token, KEYWORD_end) || (isCmdTerm(token)))) {
+ if (isKeyword(token, KEYWORD_endif) && exit_on_endif) return;
+
+ if (isType(token, TOKEN_COLON)) {
+ /*
+ * A : can signal a loop name
+ * myloop:
+ * LOOP
+ * LEAVE myloop;
+ * END LOOP;
+ * Unfortunately, labels do not have a
+ * cmd terminator, therefore we have to check
+ * if the next token is a keyword and process
+ * it accordingly.
+ */
+ readToken(token);
+ if (isKeyword(token, KEYWORD_loop) ||
+ isKeyword(token, KEYWORD_while) || isKeyword(token, KEYWORD_for))
+ /* parseStatements (token); */
+ return;
+ }
+
+ readToken(token);
+
+ if (isType(token, TOKEN_OPEN_PAREN) ||
+ isType(token, TOKEN_OPEN_CURLY) || isType(token, TOKEN_OPEN_SQUARE))
+ skipToMatched(token);
+
+ /*
+ * Since we know how to parse various statements
+ * if we detect them, parse them to completion
+ */
+ if (isType(token, TOKEN_BLOCK_LABEL_BEGIN) ||
+ isKeyword(token, KEYWORD_exception) ||
+ isKeyword(token, KEYWORD_loop) || isKeyword(token, KEYWORD_case) ||
+ isKeyword(token, KEYWORD_for) || isKeyword(token, KEYWORD_begin))
+ parseStatements(token, FALSE);
+ else if (isKeyword(token, KEYWORD_if))
+ parseStatements(token, TRUE);
+ }
+ }
+ /*
+ * We assumed earlier all statements ended with a command terminator.
+ * See comment above, now, only read if the current token
+ * is not a command terminator.
+ */
+ if (isCmdTerm(token) && !stmtTerm) stmtTerm = TRUE;
+
+ } while (!isKeyword(token, KEYWORD_end) &&
+ !(exit_on_endif && isKeyword(token, KEYWORD_endif)) && !stmtTerm);
+}
+
+static void parseBlock(tokenInfo *const token, const boolean local) {
+ if (isType(token, TOKEN_BLOCK_LABEL_BEGIN)) {
+ parseLabel(token);
+ readToken(token);
+ }
+ if (!isKeyword(token, KEYWORD_begin)) {
+ readToken(token);
+ /*
+ * These are Oracle style declares which generally come
+ * between an IS/AS and BEGIN block.
+ */
+ parseDeclare(token, local);
+ }
+ if (isKeyword(token, KEYWORD_begin)) {
+ readToken(token);
+ /*
+ * Check for ANSI declarations which always follow
+ * a BEGIN statement. This routine will not advance
+ * the token if none are found.
+ */
+ parseDeclareANSI(token, local);
+ token->begin_end_nest_lvl++;
+ while (!isKeyword(token, KEYWORD_end)) {
+ parseStatements(token, FALSE);
+
+ if (isCmdTerm(token)) readToken(token);
+ }
+ token->begin_end_nest_lvl--;
+
+ /*
+ * Read the next token (we will assume
+ * it is the command delimiter)
+ */
+ readToken(token);
+
+ /*
+ * Check if the END block is terminated
+ */
+ if (!isCmdTerm(token)) {
+ /*
+ * Not sure what to do here at the moment.
+ * I think the routine that calls parseBlock
+ * must expect the next token has already
+ * been read since it is possible this
+ * token is not a command delimiter.
+ */
+ /* findCmdTerm (token, FALSE); */
+ }
+ }
+}
+
+static void parsePackage(tokenInfo *const token) {
+ /*
+ * Packages can be specified in a number of ways:
+ * CREATE OR REPLACE PACKAGE pkg_name AS
+ * or
+ * CREATE OR REPLACE PACKAGE owner.pkg_name AS
+ * or by specifying a package body
+ * CREATE OR REPLACE PACKAGE BODY pkg_name AS
+ * CREATE OR REPLACE PACKAGE BODY owner.pkg_name AS
+ */
+ tokenInfo *const name = newToken();
+ readToken(name);
+ if (isKeyword(name, KEYWORD_body)) {
+ /*
+ * Ignore the BODY tag since we will process
+ * the body or prototypes in the same manner
+ */
+ readToken(name);
+ }
+ /* Check for owner.pkg_name */
+ while (!isKeyword(token, KEYWORD_is)) {
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ readToken(name);
+ }
+ }
+ if (isKeyword(token, KEYWORD_is)) {
+ if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING))
+ makeSqlTag(name, SQLTAG_PACKAGE);
+ addToScope(token, name->string);
+ parseBlock(token, FALSE);
+ vStringClear(token->scope);
+ }
+ findCmdTerm(token, FALSE);
+ deleteToken(name);
+}
+
+static void parseTable(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+
+ /*
+ * This deals with these formats:
+ * create table t1 (c1 int);
+ * create global tempoary table t2 (c1 int);
+ * create table "t3" (c1 int);
+ * create table bob.t4 (c1 int);
+ * create table bob."t5" (c1 int);
+ * create table "bob"."t6" (c1 int);
+ * create table bob."t7" (c1 int);
+ * Proxy tables use this format:
+ * create existing table bob."t7" AT '...';
+ * SQL Server and Sybase formats
+ * create table OnlyTable (
+ * create table dbo.HasOwner (
+ * create table [dbo].[HasOwnerSquare] (
+ * create table master.dbo.HasDb (
+ * create table master..HasDbNoOwner (
+ * create table [master].dbo.[HasDbAndOwnerSquare] (
+ * create table [master]..[HasDbNoOwnerSquare] (
+ */
+
+ /* This could be a database, owner or table name */
+ readToken(name);
+ if (isType(name, TOKEN_OPEN_SQUARE)) {
+ readToken(name);
+ /* Read close square */
+ readToken(token);
+ }
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ /*
+ * This could be a owner or table name.
+ * But this is also a special case since the table can be
+ * referenced with a blank owner:
+ * dbname..tablename
+ */
+ readToken(name);
+ if (isType(name, TOKEN_OPEN_SQUARE)) {
+ readToken(name);
+ /* Read close square */
+ readToken(token);
+ }
+ /* Check if a blank name was provided */
+ if (isType(name, TOKEN_PERIOD)) {
+ readToken(name);
+ if (isType(name, TOKEN_OPEN_SQUARE)) {
+ readToken(name);
+ /* Read close square */
+ readToken(token);
+ }
+ }
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ /* This can only be the table name */
+ readToken(name);
+ if (isType(name, TOKEN_OPEN_SQUARE)) {
+ readToken(name);
+ /* Read close square */
+ readToken(token);
+ }
+ readToken(token);
+ }
+ }
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) {
+ makeSqlTag(name, SQLTAG_TABLE);
+ vStringCopy(token->scope, name->string);
+ parseRecord(token);
+ vStringClear(token->scope);
+ }
+ } else if (isKeyword(token, KEYWORD_at)) {
+ if (isType(name, TOKEN_IDENTIFIER)) {
+ makeSqlTag(name, SQLTAG_TABLE);
+ }
+ }
+ findCmdTerm(token, FALSE);
+ deleteToken(name);
+}
+
+static void parseIndex(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+ tokenInfo *const owner = newToken();
+
+ /*
+ * This deals with these formats
+ * create index i1 on t1(c1) create index "i2" on t1(c1)
+ * create virtual unique clustered index "i3" on t1(c1)
+ * create unique clustered index "i4" on t1(c1)
+ * create clustered index "i5" on t1(c1)
+ * create bitmap index "i6" on t1(c1)
+ */
+
+ readToken(name);
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ readToken(name);
+ readToken(token);
+ }
+ if (isKeyword(token, KEYWORD_on) &&
+ (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING))) {
+ readToken(owner);
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ readToken(owner);
+ readToken(token);
+ }
+ addToScope(name, owner->string);
+ makeSqlTag(name, SQLTAG_INDEX);
+ }
+ findCmdTerm(token, FALSE);
+ deleteToken(name);
+ deleteToken(owner);
+}
+
+static void parseEvent(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+
+ /*
+ * This deals with these formats
+ * create event e1 handler begin end;
+ * create event "e2" handler begin end;
+ * create event dba."e3" handler begin end;
+ * create event "dba"."e4" handler begin end;
+ */
+
+ readToken(name);
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ readToken(name);
+ }
+ while (!(isKeyword(token, KEYWORD_handler) ||
+ (isType(token, TOKEN_SEMICOLON)))) {
+ readToken(token);
+ }
+
+ if (isKeyword(token, KEYWORD_handler) || isType(token, TOKEN_SEMICOLON)) {
+ makeSqlTag(name, SQLTAG_EVENT);
+ }
+
+ if (isKeyword(token, KEYWORD_handler)) {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_begin)) {
+ parseBlock(token, TRUE);
+ }
+ findCmdTerm(token, TRUE);
+ }
+ deleteToken(name);
+}
+
+static void parseTrigger(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+ tokenInfo *const table = newToken();
+
+ /*
+ * This deals with these formats
+ * create or replace trigger tr1 begin end;
+ * create trigger "tr2" begin end;
+ * drop trigger "droptr1";
+ * create trigger "tr3" CALL sp_something();
+ * create trigger "owner"."tr4" begin end;
+ * create trigger "tr5" not valid;
+ * create trigger "tr6" begin end;
+ */
+
+ readToken(name);
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ readToken(name);
+ readToken(token);
+ }
+
+ while (!isKeyword(token, KEYWORD_on) && !isCmdTerm(token)) {
+ readToken(token);
+ }
+
+ /*if (! isType (token, TOKEN_SEMICOLON) ) */
+ if (!isCmdTerm(token)) {
+ readToken(table);
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ readToken(table);
+ readToken(token);
+ }
+
+ while (!(isKeyword(token, KEYWORD_begin) ||
+ (isKeyword(token, KEYWORD_call)) || (isCmdTerm(token)))) {
+ if (isKeyword(token, KEYWORD_declare)) {
+ addToScope(token, name->string);
+ parseDeclare(token, TRUE);
+ vStringClear(token->scope);
+ } else
+ readToken(token);
+ }
+
+ if (isKeyword(token, KEYWORD_begin) || isKeyword(token, KEYWORD_call)) {
+ addToScope(name, table->string);
+ makeSqlTag(name, SQLTAG_TRIGGER);
+ addToScope(token, table->string);
+ if (isKeyword(token, KEYWORD_begin)) {
+ parseBlock(token, TRUE);
+ }
+ vStringClear(token->scope);
+ }
+ }
+
+ findCmdTerm(token, TRUE);
+ deleteToken(name);
+ deleteToken(table);
+}
+
+static void parsePublication(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+
+ /*
+ * This deals with these formats
+ * create or replace publication pu1 ()
+ * create publication "pu2" ()
+ * create publication dba."pu3" ()
+ * create publication "dba"."pu4" ()
+ */
+
+ readToken(name);
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ readToken(name);
+ readToken(token);
+ }
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) {
+ makeSqlTag(name, SQLTAG_PUBLICATION);
+ }
+ }
+ findCmdTerm(token, FALSE);
+ deleteToken(name);
+}
+
+static void parseService(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+
+ /*
+ * This deals with these formats
+ * CREATE SERVICE s1 TYPE 'HTML'
+ * AUTHORIZATION OFF USER DBA AS
+ * SELECT *
+ * FROM SYS.SYSTABLE;
+ * CREATE SERVICE "s2" TYPE 'HTML'
+ * AUTHORIZATION OFF USER DBA AS
+ * CALL sp_Something();
+ */
+
+ readToken(name);
+ readToken(token);
+ if (isKeyword(token, KEYWORD_type)) {
+ if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) {
+ makeSqlTag(name, SQLTAG_SERVICE);
+ }
+ }
+ findCmdTerm(token, FALSE);
+ deleteToken(name);
+}
+
+static void parseDomain(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+
+ /*
+ * This deals with these formats
+ * CREATE DOMAIN|DATATYPE [AS] your_name ...;
+ */
+
+ readToken(name);
+ if (isKeyword(name, KEYWORD_is)) {
+ readToken(name);
+ }
+ readToken(token);
+ if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) {
+ makeSqlTag(name, SQLTAG_DOMAIN);
+ }
+ findCmdTerm(token, FALSE);
+ deleteToken(name);
+}
+
+static void parseDrop(tokenInfo *const token) {
+ /*
+ * This deals with these formats
+ * DROP TABLE|PROCEDURE|DOMAIN|DATATYPE name;
+ *
+ * Just simply skip over these statements.
+ * They are often confused with PROCEDURE prototypes
+ * since the syntax is similar, this effectively deals with
+ * the issue for all types.
+ */
+
+ findCmdTerm(token, FALSE);
+}
+
+static void parseVariable(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+
+ /*
+ * This deals with these formats
+ * create variable varname1 integer;
+ * create variable @varname2 integer;
+ * create variable "varname3" integer;
+ * drop variable @varname3;
+ */
+
+ readToken(name);
+ readToken(token);
+ if ((isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) &&
+ !isType(token, TOKEN_SEMICOLON)) {
+ makeSqlTag(name, SQLTAG_VARIABLE);
+ }
+ findCmdTerm(token, TRUE);
+
+ deleteToken(name);
+}
+
+static void parseSynonym(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+
+ /*
+ * This deals with these formats
+ * create variable varname1 integer;
+ * create variable @varname2 integer;
+ * create variable "varname3" integer;
+ * drop variable @varname3;
+ */
+
+ readToken(name);
+ readToken(token);
+ if ((isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) &&
+ isKeyword(token, KEYWORD_for)) {
+ makeSqlTag(name, SQLTAG_SYNONYM);
+ }
+ findCmdTerm(token, TRUE);
+
+ deleteToken(name);
+}
+
+static void parseView(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+
+ /*
+ * This deals with these formats
+ * create variable varname1 integer;
+ * create variable @varname2 integer;
+ * create variable "varname3" integer;
+ * drop variable @varname3;
+ */
+
+ readToken(name);
+ readToken(token);
+ if (isType(token, TOKEN_PERIOD)) {
+ readToken(name);
+ readToken(token);
+ }
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ skipArgumentList(token);
+ }
+
+ while (!(isKeyword(token, KEYWORD_is) || isType(token, TOKEN_SEMICOLON))) {
+ readToken(token);
+ }
+
+ if ((isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) &&
+ isKeyword(token, KEYWORD_is)) {
+ makeSqlTag(name, SQLTAG_VIEW);
+ }
+
+ findCmdTerm(token, TRUE);
+
+ deleteToken(name);
+}
+
+static void parseMLTable(tokenInfo *const token) {
+ tokenInfo *const version = newToken();
+ tokenInfo *const table = newToken();
+ tokenInfo *const event = newToken();
+
+ /*
+ * This deals with these formats
+ * call dbo.ml_add_table_script( 'version', 'table_name', 'event',
+ * 'some SQL statement'
+ * );
+ */
+
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ readToken(version);
+ readToken(token);
+ while (!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) {
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_COMMA)) {
+ readToken(table);
+ readToken(token);
+ while (
+ !(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) {
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_COMMA)) {
+ readToken(event);
+
+ if (isType(version, TOKEN_STRING) && isType(table, TOKEN_STRING) &&
+ isType(event, TOKEN_STRING)) {
+ addToScope(version, table->string);
+ addToScope(version, event->string);
+ makeSqlTag(version, SQLTAG_MLTABLE);
+ }
+ }
+ if (!isType(token, TOKEN_CLOSE_PAREN))
+ findToken(token, TOKEN_CLOSE_PAREN);
+ }
+ }
+
+ findCmdTerm(token, TRUE);
+
+ deleteToken(version);
+ deleteToken(table);
+ deleteToken(event);
+}
+
+static void parseMLConn(tokenInfo *const token) {
+ tokenInfo *const version = newToken();
+ tokenInfo *const event = newToken();
+
+ /*
+ * This deals with these formats
+ * call ml_add_connection_script( 'version', 'event',
+ * 'some SQL statement'
+ * );
+ */
+
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ readToken(version);
+ readToken(token);
+ while (!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) {
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_COMMA)) {
+ readToken(event);
+
+ if (isType(version, TOKEN_STRING) && isType(event, TOKEN_STRING)) {
+ addToScope(version, event->string);
+ makeSqlTag(version, SQLTAG_MLCONN);
+ }
+ }
+ if (!isType(token, TOKEN_CLOSE_PAREN)) findToken(token, TOKEN_CLOSE_PAREN);
+ }
+
+ findCmdTerm(token, TRUE);
+
+ deleteToken(version);
+ deleteToken(event);
+}
+
+static void parseMLProp(tokenInfo *const token) {
+ tokenInfo *const component = newToken();
+ tokenInfo *const prop_set_name = newToken();
+ tokenInfo *const prop_name = newToken();
+
+ /*
+ * This deals with these formats
+ * ml_add_property (
+ * 'comp_name',
+ * 'prop_set_name',
+ * 'prop_name',
+ * 'prop_value'
+ * )
+ */
+
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ readToken(component);
+ readToken(token);
+ while (!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) {
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_COMMA)) {
+ readToken(prop_set_name);
+ readToken(token);
+ while (
+ !(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) {
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_COMMA)) {
+ readToken(prop_name);
+
+ if (isType(component, TOKEN_STRING) &&
+ isType(prop_set_name, TOKEN_STRING) &&
+ isType(prop_name, TOKEN_STRING)) {
+ addToScope(component, prop_set_name->string);
+ addToScope(component, prop_name->string);
+ makeSqlTag(component, SQLTAG_MLPROP);
+ }
+ }
+ if (!isType(token, TOKEN_CLOSE_PAREN))
+ findToken(token, TOKEN_CLOSE_PAREN);
+ }
+ }
+
+ findCmdTerm(token, TRUE);
+
+ deleteToken(component);
+ deleteToken(prop_set_name);
+ deleteToken(prop_name);
+}
+
+static void parseComment(tokenInfo *const token) {
+ /*
+ * This deals with this statement:
+ * COMMENT TO PRESERVE FORMAT ON PROCEDURE "DBA"."test" IS
+ * {create PROCEDURE DBA."test"()
+ * BEGIN
+ * signal dave;
+ * END
+ * }
+ * ;
+ * The comment can contain anything between the CURLY
+ * braces
+ * COMMENT ON USER "admin" IS
+ * 'Administration Group'
+ * ;
+ * Or it could be a simple string with no curly braces
+ */
+ while (!isKeyword(token, KEYWORD_is)) {
+ readToken(token);
+ }
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ findToken(token, TOKEN_CLOSE_CURLY);
+ }
+
+ findCmdTerm(token, TRUE);
+}
+
+static void parseKeywords(tokenInfo *const token) {
+ switch (token->keyword) {
+ case KEYWORD_begin:
+ parseBlock(token, FALSE);
+ break;
+ case KEYWORD_comment:
+ parseComment(token);
+ break;
+ case KEYWORD_cursor:
+ parseSimple(token, SQLTAG_CURSOR);
+ break;
+ case KEYWORD_datatype:
+ parseDomain(token);
+ break;
+ case KEYWORD_declare:
+ parseBlock(token, FALSE);
+ break;
+ case KEYWORD_domain:
+ parseDomain(token);
+ break;
+ case KEYWORD_drop:
+ parseDrop(token);
+ break;
+ case KEYWORD_event:
+ parseEvent(token);
+ break;
+ case KEYWORD_function:
+ parseSubProgram(token);
+ break;
+ case KEYWORD_if:
+ parseStatements(token, FALSE);
+ break;
+ case KEYWORD_index:
+ parseIndex(token);
+ break;
+ case KEYWORD_ml_table:
+ parseMLTable(token);
+ break;
+ case KEYWORD_ml_table_lang:
+ parseMLTable(token);
+ break;
+ case KEYWORD_ml_table_dnet:
+ parseMLTable(token);
+ break;
+ case KEYWORD_ml_table_java:
+ parseMLTable(token);
+ break;
+ case KEYWORD_ml_table_chk:
+ parseMLTable(token);
+ break;
+ case KEYWORD_ml_conn:
+ parseMLConn(token);
+ break;
+ case KEYWORD_ml_conn_lang:
+ parseMLConn(token);
+ break;
+ case KEYWORD_ml_conn_dnet:
+ parseMLConn(token);
+ break;
+ case KEYWORD_ml_conn_java:
+ parseMLConn(token);
+ break;
+ case KEYWORD_ml_conn_chk:
+ parseMLConn(token);
+ break;
+ case KEYWORD_ml_prop:
+ parseMLProp(token);
+ break;
+ case KEYWORD_package:
+ parsePackage(token);
+ break;
+ case KEYWORD_procedure:
+ parseSubProgram(token);
+ break;
+ case KEYWORD_publication:
+ parsePublication(token);
+ break;
+ case KEYWORD_service:
+ parseService(token);
+ break;
+ case KEYWORD_subtype:
+ parseSimple(token, SQLTAG_SUBTYPE);
+ break;
+ case KEYWORD_synonym:
+ parseSynonym(token);
+ break;
+ case KEYWORD_table:
+ parseTable(token);
+ break;
+ case KEYWORD_trigger:
+ parseTrigger(token);
+ break;
+ case KEYWORD_type:
+ parseType(token);
+ break;
+ case KEYWORD_variable:
+ parseVariable(token);
+ break;
+ case KEYWORD_view:
+ parseView(token);
+ break;
+ default:
+ break;
+ }
+}
+
+static void parseSqlFile(tokenInfo *const token) {
+ do {
+ readToken(token);
+
+ if (isType(token, TOKEN_BLOCK_LABEL_BEGIN))
+ parseLabel(token);
+ else
+ parseKeywords(token);
+ } while (!isKeyword(token, KEYWORD_end));
+}
+
+static void initialize(const langType language) {
+ Assert(sizeof(SqlKinds) / sizeof(SqlKinds[0]) == SQLTAG_COUNT);
+ Lang_sql = language;
+ buildSqlKeywordHash();
+}
+
+static void findSqlTags(void) {
+ tokenInfo *const token = newToken();
+ exception_t exception = (exception_t)(setjmp(Exception));
+
+ while (exception == ExceptionNone) parseSqlFile(token);
+
+ deleteToken(token);
+}
+
+extern parserDefinition *SqlParser(void) {
+ static const char *const extensions[] = {"sql", NULL};
+ parserDefinition *def = parserNew("SQL");
+ def->kinds = SqlKinds;
+ def->kindCount = KIND_COUNT(SqlKinds);
+ def->extensions = extensions;
+ def->parser = findSqlTags;
+ def->initialize = initialize;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */
diff --git a/third_party/ctags/strlist.c b/third_party/ctags/strlist.c
new file mode 100644
index 000000000..e81fde35c
--- /dev/null
+++ b/third_party/ctags/strlist.c
@@ -0,0 +1,237 @@
+/*
+ * $Id: strlist.c 443 2006-05-30 04:37:13Z darren $
+ *
+ * Copyright (c) 1999-2002, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions managing resizable string lists.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/strlist.h"
+#include "third_party/musl/fnmatch.h"
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+extern stringList *stringListNew(void) {
+ stringList *const result = xMalloc(1, stringList);
+ result->max = 0;
+ result->count = 0;
+ result->list = NULL;
+ return result;
+}
+
+extern void stringListAdd(stringList *const current, vString *string) {
+ enum { incrementalIncrease = 10 };
+ Assert(current != NULL);
+ if (current->list == NULL) {
+ Assert(current->max == 0);
+ current->count = 0;
+ current->max = incrementalIncrease;
+ current->list = xMalloc(current->max, vString *);
+ } else if (current->count == current->max) {
+ current->max += incrementalIncrease;
+ current->list = xRealloc(current->list, current->max, vString *);
+ }
+ current->list[current->count++] = string;
+}
+
+extern void stringListRemoveLast(stringList *const current) {
+ Assert(current != NULL);
+ Assert(current->count > 0);
+ --current->count;
+ current->list[current->count] = NULL;
+}
+
+/* Combine list `from' into `current', deleting `from' */
+extern void stringListCombine(stringList *const current,
+ stringList *const from) {
+ unsigned int i;
+ Assert(current != NULL);
+ Assert(from != NULL);
+ for (i = 0; i < from->count; ++i) {
+ stringListAdd(current, from->list[i]);
+ from->list[i] = NULL;
+ }
+ stringListDelete(from);
+}
+
+extern stringList *stringListNewFromArgv(const char *const *const argv) {
+ stringList *const result = stringListNew();
+ const char *const *p;
+ Assert(argv != NULL);
+ for (p = argv; *p != NULL; ++p) stringListAdd(result, vStringNewInit(*p));
+ return result;
+}
+
+extern stringList *stringListNewFromFile(const char *const fileName) {
+ stringList *result = NULL;
+ FILE *const fp = fopen(fileName, "r");
+ if (fp != NULL) {
+ result = stringListNew();
+ while (!feof(fp)) {
+ vString *const str = vStringNew();
+ readLine(str, fp);
+ vStringStripTrailing(str);
+ if (vStringLength(str) > 0)
+ stringListAdd(result, str);
+ else
+ vStringDelete(str);
+ }
+ }
+ return result;
+}
+
+extern unsigned int stringListCount(const stringList *const current) {
+ Assert(current != NULL);
+ return current->count;
+}
+
+extern vString *stringListItem(const stringList *const current,
+ const unsigned int indx) {
+ Assert(current != NULL);
+ return current->list[indx];
+}
+
+extern vString *stringListLast(const stringList *const current) {
+ Assert(current != NULL);
+ Assert(current->count > 0);
+ return current->list[current->count - 1];
+}
+
+extern void stringListClear(stringList *const current) {
+ unsigned int i;
+ Assert(current != NULL);
+ for (i = 0; i < current->count; ++i) {
+ vStringDelete(current->list[i]);
+ current->list[i] = NULL;
+ }
+ current->count = 0;
+}
+
+extern void stringListDelete(stringList *const current) {
+ if (current != NULL) {
+ if (current->list != NULL) {
+ stringListClear(current);
+ eFree(current->list);
+ current->list = NULL;
+ }
+ current->max = 0;
+ current->count = 0;
+ eFree(current);
+ }
+}
+
+static boolean compareString(const char *const string, vString *const itm) {
+ return (boolean)(strcmp(string, vStringValue(itm)) == 0);
+}
+
+static boolean compareStringInsensitive(const char *const string,
+ vString *const itm) {
+ return (boolean)(strcasecmp(string, vStringValue(itm)) == 0);
+}
+
+static int stringListIndex(const stringList *const current,
+ const char *const string,
+ boolean (*test)(const char *s, vString *const vs)) {
+ int result = -1;
+ unsigned int i;
+ Assert(current != NULL);
+ Assert(string != NULL);
+ Assert(test != NULL);
+ for (i = 0; result == -1 && i < current->count; ++i)
+ if ((*test)(string, current->list[i])) result = i;
+ return result;
+}
+
+extern boolean stringListHas(const stringList *const current,
+ const char *const string) {
+ boolean result = FALSE;
+ Assert(current != NULL);
+ result = stringListIndex(current, string, compareString) != -1;
+ return result;
+}
+
+extern boolean stringListHasInsensitive(const stringList *const current,
+ const char *const string) {
+ boolean result = FALSE;
+ Assert(current != NULL);
+ Assert(string != NULL);
+ result = stringListIndex(current, string, compareStringInsensitive) != -1;
+ return result;
+}
+
+extern boolean stringListHasTest(const stringList *const current,
+ boolean (*test)(const char *s)) {
+ boolean result = FALSE;
+ unsigned int i;
+ Assert(current != NULL);
+ for (i = 0; !result && i < current->count; ++i)
+ result = (*test)(vStringValue(current->list[i]));
+ return result;
+}
+
+extern boolean stringListRemoveExtension(stringList *const current,
+ const char *const extension) {
+ boolean result = FALSE;
+ int where;
+#ifdef CASE_INSENSITIVE_FILENAMES
+ where = stringListIndex(current, extension, compareStringInsensitive);
+#else
+ where = stringListIndex(current, extension, compareString);
+#endif
+ if (where != -1) {
+ memmove(current->list + where, current->list + where + 1,
+ (current->count - where) * sizeof(*current->list));
+ current->list[current->count - 1] = NULL;
+ --current->count;
+ result = TRUE;
+ }
+ return result;
+}
+
+extern boolean stringListExtensionMatched(const stringList *const current,
+ const char *const extension) {
+#ifdef CASE_INSENSITIVE_FILENAMES
+ return stringListHasInsensitive(current, extension);
+#else
+ return stringListHas(current, extension);
+#endif
+}
+
+static boolean fileNameMatched(const vString *const vpattern,
+ const char *const fileName) {
+ const char *const pattern = vStringValue(vpattern);
+#if defined(HAVE_FNMATCH)
+ return (boolean)(fnmatch(pattern, fileName, 0) == 0);
+#elif defined(CASE_INSENSITIVE_FILENAMES)
+ return (boolean)(strcasecmp(pattern, fileName) == 0);
+#else
+ return (boolean)(strcmp(pattern, fileName) == 0);
+#endif
+}
+
+extern boolean stringListFileMatched(const stringList *const current,
+ const char *const fileName) {
+ boolean result = FALSE;
+ unsigned int i;
+ for (i = 0; !result && i < stringListCount(current); ++i)
+ result = fileNameMatched(stringListItem(current, i), fileName);
+ return result;
+}
+
+extern void stringListPrint(const stringList *const current) {
+ unsigned int i;
+ Assert(current != NULL);
+ for (i = 0; i < current->count; ++i)
+ printf("%s%s", (i > 0) ? ", " : "", vStringValue(current->list[i]));
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/strlist.h b/third_party/ctags/strlist.h
new file mode 100644
index 000000000..5eb644b36
--- /dev/null
+++ b/third_party/ctags/strlist.h
@@ -0,0 +1,54 @@
+/*
+* $Id: strlist.h 443 2006-05-30 04:37:13Z darren $
+*
+* Copyright (c) 1999-2002, Darren Hiebert
+*
+* This source code is released for free distribution under the terms of the
+* GNU General Public License.
+*
+* Defines external interface to resizable string lists.
+*/
+#ifndef _STRLIST_H
+#define _STRLIST_H
+
+/*
+* INCLUDE FILES
+*/
+#include "third_party/ctags/general.h" /* must always come first */
+
+#include "third_party/ctags/vstring.h"
+
+/*
+* DATA DECLARATIONS
+*/
+typedef struct sStringList {
+ unsigned int max;
+ unsigned int count;
+ vString **list;
+} stringList;
+
+/*
+* FUNCTION PROTOTYPES
+*/
+extern stringList *stringListNew (void);
+extern void stringListAdd (stringList *const current, vString *string);
+extern void stringListRemoveLast (stringList *const current);
+extern void stringListCombine (stringList *const current, stringList *const from);
+extern stringList* stringListNewFromArgv (const char* const* const list);
+extern stringList* stringListNewFromFile (const char* const fileName);
+extern void stringListClear (stringList *const current);
+extern unsigned int stringListCount (const stringList *const current);
+extern vString* stringListItem (const stringList *const current, const unsigned int indx);
+extern vString* stringListLast (const stringList *const current);
+extern void stringListDelete (stringList *const current);
+extern boolean stringListHasInsensitive (const stringList *const current, const char *const string);
+extern boolean stringListHas (const stringList *const current, const char *const string);
+extern boolean stringListHasTest (const stringList *const current, boolean (*test)(const char *s));
+extern boolean stringListRemoveExtension (stringList* const current, const char* const extension);
+extern boolean stringListExtensionMatched (const stringList* const list, const char* const extension);
+extern boolean stringListFileMatched (const stringList* const list, const char* const str);
+extern void stringListPrint (const stringList *const current);
+
+#endif /* _STRLIST_H */
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/tcl.c b/third_party/ctags/tcl.c
new file mode 100644
index 000000000..977f2b32c
--- /dev/null
+++ b/third_party/ctags/tcl.c
@@ -0,0 +1,91 @@
+/*
+ * $Id: tcl.c 443 2006-05-30 04:37:13Z darren $
+ *
+ * Copyright (c) 2000-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for TCL scripts.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum { K_CLASS, K_METHOD, K_PROCEDURE } tclKind;
+
+static kindOption TclKinds[] = {{TRUE, 'c', "class", "classes"},
+ {TRUE, 'm', "method", "methods"},
+ {TRUE, 'p', "procedure", "procedures"}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static const unsigned char *makeTclTag(const unsigned char *cp,
+ vString *const name,
+ const tclKind kind) {
+ vStringClear(name);
+ while ((int)*cp != '\0' && !isspace((int)*cp)) {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ }
+ vStringTerminate(name);
+ makeSimpleTag(name, TclKinds, kind);
+ return cp;
+}
+
+static boolean match(const unsigned char *line, const char *word) {
+ return (boolean)(strncmp((const char *)line, word, strlen(word)) == 0);
+}
+
+static void findTclTags(void) {
+ vString *name = vStringNew();
+ const unsigned char *line;
+
+ while ((line = fileReadLine()) != NULL) {
+ const unsigned char *cp;
+
+ while (isspace(line[0])) ++line;
+
+ if (line[0] == '\0' || line[0] == '#') continue;
+
+ /* read first word */
+ for (cp = line; *cp != '\0' && !isspace((int)*cp); ++cp)
+ ;
+ if (!isspace((int)*cp)) continue;
+ while (isspace((int)*cp)) ++cp;
+ /* Now `line' points at first word and `cp' points at next word */
+
+ if (match(line, "proc"))
+ cp = makeTclTag(cp, name, K_PROCEDURE);
+ else if (match(line, "class") || match(line, "itcl::class"))
+ cp = makeTclTag(cp, name, K_CLASS);
+ else if (match(line, "public") || match(line, "protected") ||
+ match(line, "private")) {
+ if (match(cp, "method")) {
+ cp += 6;
+ while (isspace((int)*cp)) ++cp;
+ cp = makeTclTag(cp, name, K_METHOD);
+ }
+ }
+ }
+ vStringDelete(name);
+}
+
+extern parserDefinition *TclParser(void) {
+ static const char *const extensions[] = {"tcl", "tk", "wish", "itcl", NULL};
+ parserDefinition *def = parserNew("Tcl");
+ def->kinds = TclKinds;
+ def->kindCount = KIND_COUNT(TclKinds);
+ def->extensions = extensions;
+ def->parser = findTclTags;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/tex.c b/third_party/ctags/tex.c
new file mode 100644
index 000000000..b2197caab
--- /dev/null
+++ b/third_party/ctags/tex.c
@@ -0,0 +1,476 @@
+/*
+ * $Id: tex.c 666 2008-05-15 17:47:31Z dfishburn $
+ *
+ * Copyright (c) 2008, David Fishburn
+ *
+ * This source code is released for free distribution under the
+ * terms of the GNU General Public License.
+ *
+ * This module contains functions for generating tags for TeX
+ * language files.
+ *
+ * Tex language reference:
+ * http://en.wikibooks.org/wiki/TeX#The_Structure_of_TeX
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * MACROS
+ */
+#define isType(token, t) (boolean)((token)->type == (t))
+#define isKeyword(token, k) (boolean)((token)->keyword == (k))
+
+/*
+ * DATA DECLARATIONS
+ */
+
+typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
+
+/*
+ * Used to specify type of keyword.
+ */
+typedef enum eKeywordId {
+ KEYWORD_NONE = -1,
+ KEYWORD_chapter,
+ KEYWORD_section,
+ KEYWORD_subsection,
+ KEYWORD_subsubsection,
+ KEYWORD_part,
+ KEYWORD_paragraph,
+ KEYWORD_subparagraph,
+ KEYWORD_include
+} keywordId;
+
+/* Used to determine whether keyword is valid for the token language and
+ * what its ID is.
+ */
+typedef struct sKeywordDesc {
+ const char *name;
+ keywordId id;
+} keywordDesc;
+
+typedef enum eTokenType {
+ TOKEN_UNDEFINED,
+ TOKEN_CHARACTER,
+ TOKEN_CLOSE_PAREN,
+ TOKEN_COMMA,
+ TOKEN_KEYWORD,
+ TOKEN_OPEN_PAREN,
+ TOKEN_IDENTIFIER,
+ TOKEN_STRING,
+ TOKEN_OPEN_CURLY,
+ TOKEN_CLOSE_CURLY,
+ TOKEN_OPEN_SQUARE,
+ TOKEN_CLOSE_SQUARE,
+ TOKEN_QUESTION_MARK,
+ TOKEN_STAR
+} tokenType;
+
+typedef struct sTokenInfo {
+ tokenType type;
+ keywordId keyword;
+ vString *string;
+ vString *scope;
+ unsigned long lineNumber;
+ fpos_t filePosition;
+} tokenInfo;
+
+/*
+ * DATA DEFINITIONS
+ */
+
+static langType Lang_js;
+
+static jmp_buf Exception;
+
+typedef enum {
+ TEXTAG_CHAPTER,
+ TEXTAG_SECTION,
+ TEXTAG_SUBSECTION,
+ TEXTAG_SUBSUBSECTION,
+ TEXTAG_PART,
+ TEXTAG_PARAGRAPH,
+ TEXTAG_SUBPARAGRAPH,
+ TEXTAG_INCLUDE,
+ TEXTAG_COUNT
+} texKind;
+
+static kindOption TexKinds[] = {{TRUE, 'c', "chapter", "chapters"},
+ {TRUE, 's', "section", "sections"},
+ {TRUE, 'u', "subsection", "subsections"},
+ {TRUE, 'b', "subsubsection", "subsubsections"},
+ {TRUE, 'p', "part", "parts"},
+ {TRUE, 'P', "paragraph", "paragraphs"},
+ {TRUE, 'G', "subparagraph", "subparagraphs"},
+ {TRUE, 'i', "include", "includes"}};
+
+static const keywordDesc TexKeywordTable[] = {
+ /* keyword keyword ID */
+ {"chapter", KEYWORD_chapter},
+ {"section", KEYWORD_section},
+ {"subsection", KEYWORD_subsection},
+ {"subsubsection", KEYWORD_subsubsection},
+ {"part", KEYWORD_part},
+ {"paragraph", KEYWORD_paragraph},
+ {"subparagraph", KEYWORD_subparagraph},
+ {"include", KEYWORD_include}};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static boolean isIdentChar(const int c) {
+ return (boolean)(isalpha(c) || isdigit(c) || c == '$' || c == '_' ||
+ c == '#' || c == '-' || c == '.');
+}
+
+static void buildTexKeywordHash(void) {
+ const size_t count = sizeof(TexKeywordTable) / sizeof(TexKeywordTable[0]);
+ size_t i;
+ for (i = 0; i < count; ++i) {
+ const keywordDesc *const p = &TexKeywordTable[i];
+ addKeyword(p->name, Lang_js, (int)p->id);
+ }
+}
+
+static tokenInfo *newToken(void) {
+ tokenInfo *const token = xMalloc(1, tokenInfo);
+
+ token->type = TOKEN_UNDEFINED;
+ token->keyword = KEYWORD_NONE;
+ token->string = vStringNew();
+ token->scope = vStringNew();
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+
+ return token;
+}
+
+static void deleteToken(tokenInfo *const token) {
+ vStringDelete(token->string);
+ vStringDelete(token->scope);
+ eFree(token);
+}
+
+/*
+ * Tag generation functions
+ */
+
+static void makeConstTag(tokenInfo *const token, const texKind kind) {
+ if (TexKinds[kind].enabled) {
+ const char *const name = vStringValue(token->string);
+ tagEntryInfo e;
+ initTagEntry(&e, name);
+
+ e.lineNumber = token->lineNumber;
+ e.filePosition = token->filePosition;
+ e.kindName = TexKinds[kind].name;
+ e.kind = TexKinds[kind].letter;
+
+ makeTagEntry(&e);
+ }
+}
+
+static void makeTexTag(tokenInfo *const token, texKind kind) {
+ vString *fulltag;
+
+ if (TexKinds[kind].enabled) {
+ /*
+ * If a scope has been added to the token, change the token
+ * string to include the scope when making the tag.
+ */
+ if (vStringLength(token->scope) > 0) {
+ fulltag = vStringNew();
+ vStringCopy(fulltag, token->scope);
+ vStringCatS(fulltag, ".");
+ vStringCatS(fulltag, vStringValue(token->string));
+ vStringTerminate(fulltag);
+ vStringCopy(token->string, fulltag);
+ vStringDelete(fulltag);
+ }
+ makeConstTag(token, kind);
+ }
+}
+
+/*
+ * Parsing functions
+ */
+
+static void parseString(vString *const string, const int delimiter) {
+ boolean end = FALSE;
+ while (!end) {
+ int c = fileGetc();
+ if (c == EOF)
+ end = TRUE;
+ else if (c == '\\') {
+ c = fileGetc(); /* This maybe a ' or ". */
+ vStringPut(string, c);
+ } else if (c == delimiter)
+ end = TRUE;
+ else
+ vStringPut(string, c);
+ }
+ vStringTerminate(string);
+}
+
+/*
+ * Read a C identifier beginning with "firstChar" and places it into
+ * "name".
+ */
+static void parseIdentifier(vString *const string, const int firstChar) {
+ int c = firstChar;
+ Assert(isIdentChar(c));
+ do {
+ vStringPut(string, c);
+ c = fileGetc();
+ } while (isIdentChar(c));
+
+ vStringTerminate(string);
+ if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */
+}
+
+static void readToken(tokenInfo *const token) {
+ int c;
+
+ token->type = TOKEN_UNDEFINED;
+ token->keyword = KEYWORD_NONE;
+ vStringClear(token->string);
+
+getNextChar:
+ do {
+ c = fileGetc();
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ } while (c == '\t' || c == ' ' || c == '\n');
+
+ switch (c) {
+ case EOF:
+ longjmp(Exception, (int)ExceptionEOF);
+ break;
+ case '(':
+ token->type = TOKEN_OPEN_PAREN;
+ break;
+ case ')':
+ token->type = TOKEN_CLOSE_PAREN;
+ break;
+ case ',':
+ token->type = TOKEN_COMMA;
+ break;
+ case '{':
+ token->type = TOKEN_OPEN_CURLY;
+ break;
+ case '}':
+ token->type = TOKEN_CLOSE_CURLY;
+ break;
+ case '[':
+ token->type = TOKEN_OPEN_SQUARE;
+ break;
+ case ']':
+ token->type = TOKEN_CLOSE_SQUARE;
+ break;
+ case '*':
+ token->type = TOKEN_STAR;
+ break;
+
+ case '\'':
+ case '"':
+ token->type = TOKEN_STRING;
+ parseString(token->string, c);
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+
+ case '\\':
+ /*
+ * All Tex tags start with a backslash.
+ * Check if the next character is an alpha character
+ * else it is not a potential tex tag.
+ */
+ c = fileGetc();
+ if (!isalpha(c))
+ fileUngetc(c);
+ else {
+ parseIdentifier(token->string, c);
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ token->keyword = analyzeToken(token->string, Lang_js);
+ if (isKeyword(token, KEYWORD_NONE))
+ token->type = TOKEN_IDENTIFIER;
+ else
+ token->type = TOKEN_KEYWORD;
+ }
+ break;
+
+ case '%':
+ fileSkipToCharacter('\n'); /* % are single line comments */
+ goto getNextChar;
+ break;
+
+ default:
+ if (!isIdentChar(c))
+ token->type = TOKEN_UNDEFINED;
+ else {
+ parseIdentifier(token->string, c);
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ token->type = TOKEN_IDENTIFIER;
+ }
+ break;
+ }
+}
+
+static void copyToken(tokenInfo *const dest, tokenInfo *const src) {
+ dest->lineNumber = src->lineNumber;
+ dest->filePosition = src->filePosition;
+ dest->type = src->type;
+ dest->keyword = src->keyword;
+ vStringCopy(dest->string, src->string);
+ vStringCopy(dest->scope, src->scope);
+}
+
+/*
+ * Scanning functions
+ */
+
+static boolean parseTag(tokenInfo *const token, texKind kind) {
+ tokenInfo *const name = newToken();
+ vString *fullname;
+ boolean useLongName = TRUE;
+
+ fullname = vStringNew();
+ vStringClear(fullname);
+
+ /*
+ * Tex tags are of these formats:
+ * \keyword{any number of words}
+ * \keyword[short desc]{any number of words}
+ * \keyword*[short desc]{any number of words}
+ *
+ * When a keyword is found, loop through all words within
+ * the curly braces for the tag name.
+ */
+
+ if (isType(token, TOKEN_KEYWORD)) {
+ copyToken(name, token);
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_SQUARE)) {
+ useLongName = FALSE;
+
+ readToken(token);
+ while (!isType(token, TOKEN_CLOSE_SQUARE)) {
+ if (isType(token, TOKEN_IDENTIFIER)) {
+ if (fullname->length > 0) vStringCatS(fullname, " ");
+ vStringCatS(fullname, vStringValue(token->string));
+ }
+ readToken(token);
+ }
+ vStringTerminate(fullname);
+ vStringCopy(name->string, fullname);
+ makeTexTag(name, kind);
+ }
+
+ if (isType(token, TOKEN_STAR)) {
+ readToken(token);
+ }
+
+ if (isType(token, TOKEN_OPEN_CURLY)) {
+ readToken(token);
+ while (!isType(token, TOKEN_CLOSE_CURLY)) {
+ /* if (isType (token, TOKEN_IDENTIFIER) && useLongName) */
+ if (useLongName) {
+ if (fullname->length > 0) vStringCatS(fullname, " ");
+ vStringCatS(fullname, vStringValue(token->string));
+ }
+ readToken(token);
+ }
+ if (useLongName) {
+ vStringTerminate(fullname);
+ vStringCopy(name->string, fullname);
+ makeTexTag(name, kind);
+ }
+ }
+
+ deleteToken(name);
+ vStringDelete(fullname);
+ return TRUE;
+}
+
+static void parseTexFile(tokenInfo *const token) {
+ do {
+ readToken(token);
+
+ if (isType(token, TOKEN_KEYWORD)) {
+ switch (token->keyword) {
+ case KEYWORD_chapter:
+ parseTag(token, TEXTAG_CHAPTER);
+ break;
+ case KEYWORD_section:
+ parseTag(token, TEXTAG_SECTION);
+ break;
+ case KEYWORD_subsection:
+ parseTag(token, TEXTAG_SUBSECTION);
+ break;
+ case KEYWORD_subsubsection:
+ parseTag(token, TEXTAG_SUBSUBSECTION);
+ break;
+ case KEYWORD_part:
+ parseTag(token, TEXTAG_PART);
+ break;
+ case KEYWORD_paragraph:
+ parseTag(token, TEXTAG_PARAGRAPH);
+ break;
+ case KEYWORD_subparagraph:
+ parseTag(token, TEXTAG_SUBPARAGRAPH);
+ break;
+ case KEYWORD_include:
+ parseTag(token, TEXTAG_INCLUDE);
+ break;
+ default:
+ break;
+ }
+ }
+ } while (TRUE);
+}
+
+static void initialize(const langType language) {
+ Assert(sizeof(TexKinds) / sizeof(TexKinds[0]) == TEXTAG_COUNT);
+ Lang_js = language;
+ buildTexKeywordHash();
+}
+
+static void findTexTags(void) {
+ tokenInfo *const token = newToken();
+ exception_t exception;
+
+ exception = (exception_t)(setjmp(Exception));
+ while (exception == ExceptionNone) parseTexFile(token);
+
+ deleteToken(token);
+}
+
+/* Create parser definition stucture */
+extern parserDefinition *TexParser(void) {
+ static const char *const extensions[] = {"tex", NULL};
+ parserDefinition *const def = parserNew("Tex");
+ def->extensions = extensions;
+ /*
+ * New definitions for parsing instead of regex
+ */
+ def->kinds = TexKinds;
+ def->kindCount = KIND_COUNT(TexKinds);
+ def->parser = findTexTags;
+ def->initialize = initialize;
+
+ return def;
+}
+/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */
diff --git a/third_party/ctags/verilog.c b/third_party/ctags/verilog.c
new file mode 100644
index 000000000..131e48d4d
--- /dev/null
+++ b/third_party/ctags/verilog.c
@@ -0,0 +1,293 @@
+/*
+ * $Id: verilog.c 753 2010-02-27 17:53:32Z elliotth $
+ *
+ * Copyright (c) 2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for the Verilog HDL
+ * (Hardware Description Language).
+ *
+ * Language definition documents:
+ * http://www.eg.bucknell.edu/~cs320/verilog/verilog-manual.html
+ * http://www.sutherland-hdl.com/on-line_ref_guide/vlog_ref_top.html
+ * http://www.verilog.com/VerilogBNF.html
+ * http://eesun.free.fr/DOC/VERILOG/verilog_manual1.html
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/get.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * DATA DECLARATIONS
+ */
+typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
+
+typedef enum {
+ K_UNDEFINED = -1,
+ K_CONSTANT,
+ K_EVENT,
+ K_FUNCTION,
+ K_MODULE,
+ K_NET,
+ K_PORT,
+ K_REGISTER,
+ K_TASK
+} verilogKind;
+
+typedef struct {
+ const char *keyword;
+ verilogKind kind;
+} keywordAssoc;
+
+/*
+ * DATA DEFINITIONS
+ */
+static int Ungetc;
+static int Lang_verilog;
+static jmp_buf Exception;
+
+static kindOption VerilogKinds[] = {
+ {TRUE, 'c', "constant", "constants (define, parameter, specparam)"},
+ {TRUE, 'e', "event", "events"},
+ {TRUE, 'f', "function", "functions"},
+ {TRUE, 'm', "module", "modules"},
+ {TRUE, 'n', "net", "net data types"},
+ {TRUE, 'p', "port", "ports"},
+ {TRUE, 'r', "register", "register data types"},
+ {TRUE, 't', "task", "tasks"},
+};
+
+static keywordAssoc VerilogKeywordTable[] = {
+ {"`define", K_CONSTANT},
+ {"event", K_EVENT},
+ {"function", K_FUNCTION},
+ {"inout", K_PORT},
+ {"input", K_PORT},
+ {"integer", K_REGISTER},
+ {"module", K_MODULE},
+ {"output", K_PORT},
+ {"parameter", K_CONSTANT},
+ {"real", K_REGISTER},
+ {"realtime", K_REGISTER},
+ {"reg", K_REGISTER},
+ {"specparam", K_CONSTANT},
+ {"supply0", K_NET},
+ {"supply1", K_NET},
+ {"task", K_TASK},
+ {"time", K_REGISTER},
+ {"tri0", K_NET},
+ {"tri1", K_NET},
+ {"triand", K_NET},
+ {"tri", K_NET},
+ {"trior", K_NET},
+ {"trireg", K_NET},
+ {"wand", K_NET},
+ {"wire", K_NET},
+ {"wor", K_NET},
+};
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static void initialize(const langType language) {
+ size_t i;
+ const size_t count =
+ sizeof(VerilogKeywordTable) / sizeof(VerilogKeywordTable[0]);
+ Lang_verilog = language;
+ for (i = 0; i < count; ++i) {
+ const keywordAssoc *const p = &VerilogKeywordTable[i];
+ addKeyword(p->keyword, language, (int)p->kind);
+ }
+}
+
+static void vUngetc(int c) {
+ Assert(Ungetc == '\0');
+ Ungetc = c;
+}
+
+static int vGetc(void) {
+ int c;
+ if (Ungetc == '\0')
+ c = fileGetc();
+ else {
+ c = Ungetc;
+ Ungetc = '\0';
+ }
+ if (c == '/') {
+ int c2 = fileGetc();
+ if (c2 == EOF)
+ longjmp(Exception, (int)ExceptionEOF);
+ else if (c2 == '/') /* strip comment until end-of-line */
+ {
+ do
+ c = fileGetc();
+ while (c != '\n' && c != EOF);
+ } else if (c2 == '*') /* strip block comment */
+ {
+ c = skipOverCComment();
+ } else {
+ fileUngetc(c2);
+ }
+ } else if (c == '"') /* strip string contents */
+ {
+ int c2;
+ do
+ c2 = fileGetc();
+ while (c2 != '"' && c2 != EOF);
+ c = '@';
+ }
+ if (c == EOF) longjmp(Exception, (int)ExceptionEOF);
+ return c;
+}
+
+static boolean isIdentifierCharacter(const int c) {
+ return (boolean)(isalnum(c) || c == '_' || c == '`');
+}
+
+static int skipWhite(int c) {
+ while (isspace(c)) c = vGetc();
+ return c;
+}
+
+static int skipPastMatch(const char *const pair) {
+ const int begin = pair[0], end = pair[1];
+ int matchLevel = 1;
+ int c;
+ do {
+ c = vGetc();
+ if (c == begin)
+ ++matchLevel;
+ else if (c == end)
+ --matchLevel;
+ } while (matchLevel > 0);
+ return vGetc();
+}
+
+static boolean readIdentifier(vString *const name, int c) {
+ vStringClear(name);
+ if (isIdentifierCharacter(c)) {
+ while (isIdentifierCharacter(c)) {
+ vStringPut(name, c);
+ c = vGetc();
+ }
+ vUngetc(c);
+ vStringTerminate(name);
+ }
+ return (boolean)(name->length > 0);
+}
+
+static void tagNameList(const verilogKind kind, int c) {
+ vString *name = vStringNew();
+ boolean repeat;
+ Assert(isIdentifierCharacter(c));
+ do {
+ repeat = FALSE;
+ if (isIdentifierCharacter(c)) {
+ readIdentifier(name, c);
+ makeSimpleTag(name, VerilogKinds, kind);
+ } else
+ break;
+ c = skipWhite(vGetc());
+ if (c == '[') c = skipPastMatch("[]");
+ c = skipWhite(c);
+ if (c == '=') {
+ c = skipWhite(vGetc());
+ if (c == '{')
+ skipPastMatch("{}");
+ else {
+ do
+ c = vGetc();
+ while (c != ',' && c != ';');
+ }
+ }
+ if (c == ',') {
+ c = skipWhite(vGetc());
+ repeat = TRUE;
+ } else
+ repeat = FALSE;
+ } while (repeat);
+ vStringDelete(name);
+ vUngetc(c);
+}
+
+static void findTag(vString *const name) {
+ const verilogKind kind =
+ (verilogKind)lookupKeyword(vStringValue(name), Lang_verilog);
+ if (kind == K_CONSTANT && vStringItem(name, 0) == '`') {
+ /* Bug #961001: Verilog compiler directives are line-based. */
+ int c = skipWhite(vGetc());
+ readIdentifier(name, c);
+ makeSimpleTag(name, VerilogKinds, kind);
+ /* Skip the rest of the line. */
+ do {
+ c = vGetc();
+ } while (c != '\n');
+ vUngetc(c);
+ } else if (kind != K_UNDEFINED) {
+ int c = skipWhite(vGetc());
+
+ /* Many keywords can have bit width.
+ * reg [3:0] net_name;
+ * inout [(`DBUSWIDTH-1):0] databus;
+ */
+ if (c == '(') c = skipPastMatch("()");
+ c = skipWhite(c);
+ if (c == '[') c = skipPastMatch("[]");
+ c = skipWhite(c);
+ if (c == '#') {
+ c = vGetc();
+ if (c == '(') c = skipPastMatch("()");
+ }
+ c = skipWhite(c);
+ if (isIdentifierCharacter(c)) tagNameList(kind, c);
+ }
+}
+
+static void findVerilogTags(void) {
+ vString *const name = vStringNew();
+ volatile boolean newStatement = TRUE;
+ volatile int c = '\0';
+ exception_t exception = (exception_t)setjmp(Exception);
+
+ if (exception == ExceptionNone)
+ while (c != EOF) {
+ c = vGetc();
+ switch (c) {
+ case ';':
+ case '\n':
+ newStatement = TRUE;
+ break;
+
+ case ' ':
+ case '\t':
+ break;
+
+ default:
+ if (newStatement && readIdentifier(name, c)) findTag(name);
+ newStatement = FALSE;
+ break;
+ }
+ }
+ vStringDelete(name);
+}
+
+extern parserDefinition *VerilogParser(void) {
+ static const char *const extensions[] = {"v", NULL};
+ parserDefinition *def = parserNew("Verilog");
+ def->kinds = VerilogKinds;
+ def->kindCount = KIND_COUNT(VerilogKinds);
+ def->extensions = extensions;
+ def->parser = findVerilogTags;
+ def->initialize = initialize;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4: */
diff --git a/third_party/ctags/vhdl.c b/third_party/ctags/vhdl.c
new file mode 100644
index 000000000..a738761c7
--- /dev/null
+++ b/third_party/ctags/vhdl.c
@@ -0,0 +1,737 @@
+/*
+ * $Id: vhdl.c 652 2008-04-18 03:51:47Z elliotth $
+ *
+ * Copyright (c) 2008, Nicolas Vincent
+ *
+ * This source code is released for free distribution under the terms of the
+ * GNU General Public License.
+ *
+ * This module contains functions for generating tags for VHDL files.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/debug.h"
+#include "third_party/ctags/entry.h"
+#include "third_party/ctags/keyword.h"
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/routines.h"
+#include "third_party/ctags/vstring.h"
+
+/*
+ * MACROS
+ */
+#define isType(token, t) (boolean)((token)->type == (t))
+#define isKeyword(token, k) (boolean)((token)->keyword == (k))
+
+/*
+ * DATA DECLARATIONS
+ */
+typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
+
+/*
+ * Used to specify type of keyword.
+ */
+typedef enum eKeywordId {
+ KEYWORD_NONE = -1,
+ KEYWORD_ABS,
+ KEYWORD_ACCESS,
+ KEYWORD_AFTER,
+ KEYWORD_ALIAS,
+ KEYWORD_ALL,
+ KEYWORD_AND,
+ KEYWORD_ARCHITECTURE,
+ KEYWORD_ARRAY,
+ KEYWORD_ASSERT,
+ KEYWORD_ATTRIBUTE,
+ KEYWORD_BEGIN,
+ KEYWORD_BLOCK,
+ KEYWORD_BODY,
+ KEYWORD_BUFFER,
+ KEYWORD_BUS,
+ KEYWORD_CASE,
+ KEYWORD_COMPONENT,
+ KEYWORD_CONFIGURATION,
+ KEYWORD_CONSTANT,
+ KEYWORD_DISCONNECT,
+ KEYWORD_DOWNTO,
+ KEYWORD_ELSE,
+ KEYWORD_ELSIF,
+ KEYWORD_END,
+ KEYWORD_ENTITY,
+ KEYWORD_EXIT,
+ KEYWORD_FILE,
+ KEYWORD_FOR,
+ KEYWORD_FUNCTION,
+ KEYWORD_GENERATE,
+ KEYWORD_GENERIC,
+ KEYWORD_GROUP,
+ KEYWORD_GUARDED,
+ KEYWORD_IF,
+ KEYWORD_IMPURE,
+ KEYWORD_IN,
+ KEYWORD_INERTIAL,
+ KEYWORD_INOUT,
+ KEYWORD_IS,
+ KEYWORD_LABEL,
+ KEYWORD_LIBRARY,
+ KEYWORD_LINKAGE,
+ KEYWORD_LITERAL,
+ KEYWORD_LOOP,
+ KEYWORD_MAP,
+ KEYWORD_MOD,
+ KEYWORD_NAND,
+ KEYWORD_NEW,
+ KEYWORD_NEXT,
+ KEYWORD_NOR,
+ KEYWORD_NOT,
+ KEYWORD_NULL,
+ KEYWORD_OF,
+ KEYWORD_ON,
+ KEYWORD_OPEN,
+ KEYWORD_OR,
+ KEYWORD_OTHERS,
+ KEYWORD_OUT,
+ KEYWORD_PACKAGE,
+ KEYWORD_PORT,
+ KEYWORD_POSTPONED,
+ KEYWORD_PROCEDURE,
+ KEYWORD_PROCESS,
+ KEYWORD_PURE,
+ KEYWORD_RANGE,
+ KEYWORD_RECORD,
+ KEYWORD_REGISTER,
+ KEYWORD_REJECT,
+ KEYWORD_RETURN,
+ KEYWORD_ROL,
+ KEYWORD_ROR,
+ KEYWORD_SELECT,
+ KEYWORD_SEVERITY,
+ KEYWORD_SIGNAL,
+ KEYWORD_SHARED,
+ KEYWORD_SLA,
+ KEYWORD_SLI,
+ KEYWORD_SRA,
+ KEYWORD_SRL,
+ KEYWORD_SUBTYPE,
+ KEYWORD_THEN,
+ KEYWORD_TO,
+ KEYWORD_TRANSPORT,
+ KEYWORD_TYPE,
+ KEYWORD_UNAFFECTED,
+ KEYWORD_UNITS,
+ KEYWORD_UNTIL,
+ KEYWORD_USE,
+ KEYWORD_VARIABLE,
+ KEYWORD_WAIT,
+ KEYWORD_WHEN,
+ KEYWORD_WHILE,
+ KEYWORD_WITH,
+ KEYWORD_XNOR,
+ KEYWORD_XOR
+} keywordId;
+
+/* Used to determine whether keyword is valid for the current language and
+ * what its ID is.
+ */
+typedef struct sKeywordDesc {
+ const char *name;
+ keywordId id;
+} keywordDesc;
+
+typedef enum eTokenType {
+ TOKEN_NONE, /* none */
+ TOKEN_OPEN_PAREN, /* ( */
+ TOKEN_CLOSE_PAREN, /* ) */
+ TOKEN_COMMA, /* the comma character */
+ TOKEN_IDENTIFIER,
+ TOKEN_KEYWORD,
+ TOKEN_PERIOD, /* . */
+ TOKEN_OPERATOR,
+ TOKEN_SEMICOLON, /* the semicolon character */
+ TOKEN_STRING
+} tokenType;
+
+typedef struct sTokenInfo {
+ tokenType type;
+ keywordId keyword;
+ vString *string; /* the name of the token */
+ vString *scope;
+ unsigned long lineNumber; /* line number of tag */
+ fpos_t filePosition; /* file position of line containing name */
+} tokenInfo;
+
+/*
+ * DATA DEFINITIONS
+ */
+static int Lang_vhdl;
+static jmp_buf Exception;
+
+/* Used to index into the VhdlKinds table. */
+typedef enum {
+ VHDLTAG_UNDEFINED = -1,
+ VHDLTAG_CONSTANT,
+ VHDLTAG_TYPE,
+ VHDLTAG_SUBTYPE,
+ VHDLTAG_RECORD,
+ VHDLTAG_ENTITY,
+ VHDLTAG_COMPONENT,
+ VHDLTAG_PROTOTYPE,
+ VHDLTAG_FUNCTION,
+ VHDLTAG_PROCEDURE,
+ VHDLTAG_PACKAGE,
+ VHDLTAG_LOCAL
+} vhdlKind;
+
+static kindOption VhdlKinds[] = {
+ {TRUE, 'c', "constant", "constant declarations"},
+ {TRUE, 't', "type", "type definitions"},
+ {TRUE, 'T', "subtype", "subtype definitions"},
+ {TRUE, 'r', "record", "record names"},
+ {TRUE, 'e', "entity", "entity declarations"},
+ {FALSE, 'C', "component", "component declarations"},
+ {FALSE, 'd', "prototype", "prototypes"},
+ {TRUE, 'f', "function", "function prototypes and declarations"},
+ {TRUE, 'p', "procedure", "procedure prototypes and declarations"},
+ {TRUE, 'P', "package", "package definitions"},
+ {FALSE, 'l', "local", "local definitions"}};
+
+static keywordDesc VhdlKeywordTable[] = {
+ {"abs", KEYWORD_ABS},
+ {"access", KEYWORD_ACCESS},
+ {"after", KEYWORD_AFTER},
+ {"alias", KEYWORD_ALIAS},
+ {"all", KEYWORD_ALL},
+ {"and", KEYWORD_AND},
+ {"architecture", KEYWORD_ARCHITECTURE},
+ {"array", KEYWORD_ARRAY},
+ {"assert", KEYWORD_ASSERT},
+ {"attribute", KEYWORD_ATTRIBUTE},
+ {"begin", KEYWORD_BEGIN},
+ {"block", KEYWORD_BLOCK},
+ {"body", KEYWORD_BODY},
+ {"buffer", KEYWORD_BUFFER},
+ {"bus", KEYWORD_BUS},
+ {"case", KEYWORD_CASE},
+ {"component", KEYWORD_COMPONENT},
+ {"configuration", KEYWORD_CONFIGURATION},
+ {"constant", KEYWORD_CONSTANT},
+ {"disconnect", KEYWORD_DISCONNECT},
+ {"downto", KEYWORD_DOWNTO},
+ {"else", KEYWORD_ELSE},
+ {"elsif", KEYWORD_ELSIF},
+ {"end", KEYWORD_END},
+ {"entity", KEYWORD_ENTITY},
+ {"exit", KEYWORD_EXIT},
+ {"file", KEYWORD_FILE},
+ {"for", KEYWORD_FOR},
+ {"function", KEYWORD_FUNCTION},
+ {"generate", KEYWORD_GENERATE},
+ {"generic", KEYWORD_GENERIC},
+ {"group", KEYWORD_GROUP},
+ {"guarded", KEYWORD_GUARDED},
+ {"if", KEYWORD_IF},
+ {"impure", KEYWORD_IMPURE},
+ {"in", KEYWORD_IN},
+ {"inertial", KEYWORD_INERTIAL},
+ {"inout", KEYWORD_INOUT},
+ {"is", KEYWORD_IS},
+ {"label", KEYWORD_LABEL},
+ {"library", KEYWORD_LIBRARY},
+ {"linkage", KEYWORD_LINKAGE},
+ {"literal", KEYWORD_LITERAL},
+ {"loop", KEYWORD_LOOP},
+ {"map", KEYWORD_MAP},
+ {"mod", KEYWORD_MOD},
+ {"nand", KEYWORD_NAND},
+ {"new", KEYWORD_NEW},
+ {"next", KEYWORD_NEXT},
+ {"nor", KEYWORD_NOR},
+ {"not", KEYWORD_NOT},
+ {"null", KEYWORD_NULL},
+ {"of", KEYWORD_OF},
+ {"on", KEYWORD_ON},
+ {"open", KEYWORD_OPEN},
+ {"or", KEYWORD_OR},
+ {"others", KEYWORD_OTHERS},
+ {"out", KEYWORD_OUT},
+ {"package", KEYWORD_PACKAGE},
+ {"port", KEYWORD_PORT},
+ {"postponed", KEYWORD_POSTPONED},
+ {"procedure", KEYWORD_PROCEDURE},
+ {"process", KEYWORD_PROCESS},
+ {"pure", KEYWORD_PURE},
+ {"range", KEYWORD_RANGE},
+ {"record", KEYWORD_RECORD},
+ {"register", KEYWORD_REGISTER},
+ {"reject", KEYWORD_REJECT},
+ {"return", KEYWORD_RETURN},
+ {"rol", KEYWORD_ROL},
+ {"ror", KEYWORD_ROR},
+ {"select", KEYWORD_SELECT},
+ {"severity", KEYWORD_SEVERITY},
+ {"signal", KEYWORD_SIGNAL},
+ {"shared", KEYWORD_SHARED},
+ {"sla", KEYWORD_SLA},
+ {"sli", KEYWORD_SLI},
+ {"sra", KEYWORD_SRA},
+ {"srl", KEYWORD_SRL},
+ {"subtype", KEYWORD_SUBTYPE},
+ {"then", KEYWORD_THEN},
+ {"to", KEYWORD_TO},
+ {"transport", KEYWORD_TRANSPORT},
+ {"type", KEYWORD_TYPE},
+ {"unaffected", KEYWORD_UNAFFECTED},
+ {"units", KEYWORD_UNITS},
+ {"until", KEYWORD_UNTIL},
+ {"use", KEYWORD_USE},
+ {"variable", KEYWORD_VARIABLE},
+ {"wait", KEYWORD_WAIT},
+ {"when", KEYWORD_WHEN},
+ {"while", KEYWORD_WHILE},
+ {"with", KEYWORD_WITH},
+ {"xnor", KEYWORD_XNOR},
+ {"xor", KEYWORD_XOR}};
+
+/*
+ * FUNCTION DECLARATIONS
+ */
+static void parseKeywords(tokenInfo *const token, boolean local);
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+static boolean isIdentChar1(const int c) {
+ return (boolean)(isalpha(c) || c == '_');
+}
+
+static boolean isIdentChar(const int c) {
+ return (boolean)(isalpha(c) || isdigit(c) || c == '_');
+}
+
+static boolean isIdentifierMatch(const tokenInfo *const token,
+ const vString *const name) {
+ return (boolean)(
+ isType(token, TOKEN_IDENTIFIER) &&
+ strcasecmp(vStringValue(token->string), vStringValue(name)) == 0);
+ /* XXX this is copy/paste from eiffel.c and slightly modified */
+ /* shouldn't we use strNcasecmp ? */
+}
+
+static boolean isKeywordOrIdent(const tokenInfo *const token,
+ const keywordId keyword,
+ const vString *const name) {
+ return (boolean)(isKeyword(token, keyword) || isIdentifierMatch(token, name));
+}
+
+static tokenInfo *newToken(void) {
+ tokenInfo *const token = xMalloc(1, tokenInfo);
+ token->type = TOKEN_NONE;
+ token->keyword = KEYWORD_NONE;
+ token->string = vStringNew();
+ token->scope = vStringNew();
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ return token;
+}
+
+static void deleteToken(tokenInfo *const token) {
+ if (token != NULL) {
+ vStringDelete(token->string);
+ vStringDelete(token->scope);
+ eFree(token);
+ }
+}
+
+/*
+ * Parsing functions
+ */
+
+static void parseString(vString *const string, const int delimiter) {
+ boolean end = FALSE;
+ while (!end) {
+ int c = fileGetc();
+ if (c == EOF)
+ end = TRUE;
+ else if (c == '\\') {
+ c = fileGetc(); /* This maybe a ' or ". */
+ vStringPut(string, c);
+ } else if (c == delimiter)
+ end = TRUE;
+ else
+ vStringPut(string, c);
+ }
+ vStringTerminate(string);
+}
+
+/* Read a VHDL identifier beginning with "firstChar" and place it into "name".
+ */
+static void parseIdentifier(vString *const string, const int firstChar) {
+ int c = firstChar;
+ Assert(isIdentChar1(c));
+ do {
+ vStringPut(string, c);
+ c = fileGetc();
+ } while (isIdentChar(c));
+ vStringTerminate(string);
+ if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */
+}
+
+static void readToken(tokenInfo *const token) {
+ int c;
+
+ token->type = TOKEN_NONE;
+ token->keyword = KEYWORD_NONE;
+ vStringClear(token->string);
+
+getNextChar:
+ do {
+ c = fileGetc();
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ } while (c == '\t' || c == ' ' || c == '\n');
+
+ switch (c) {
+ case EOF:
+ longjmp(Exception, (int)ExceptionEOF);
+ break;
+ case '(':
+ token->type = TOKEN_OPEN_PAREN;
+ break;
+ case ')':
+ token->type = TOKEN_CLOSE_PAREN;
+ break;
+ case ';':
+ token->type = TOKEN_SEMICOLON;
+ break;
+ case '.':
+ token->type = TOKEN_PERIOD;
+ break;
+ case ',':
+ token->type = TOKEN_COMMA;
+ break;
+ case '\'': /* only single char are inside simple quotes */
+ break; /* or it is for attributes so we don't care */
+ case '"':
+ token->type = TOKEN_STRING;
+ parseString(token->string, c);
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ break;
+ case '-':
+ c = fileGetc();
+ if (c == '-') /* start of a comment */
+ {
+ fileSkipToCharacter('\n');
+ goto getNextChar;
+ } else {
+ if (!isspace(c)) fileUngetc(c);
+ token->type = TOKEN_OPERATOR;
+ }
+ break;
+ default:
+ if (!isIdentChar1(c))
+ token->type = TOKEN_NONE;
+ else {
+ parseIdentifier(token->string, c);
+ token->lineNumber = getSourceLineNumber();
+ token->filePosition = getInputFilePosition();
+ token->keyword = analyzeToken(token->string, Lang_vhdl);
+ if (isKeyword(token, KEYWORD_NONE))
+ token->type = TOKEN_IDENTIFIER;
+ else
+ token->type = TOKEN_KEYWORD;
+ }
+ break;
+ }
+}
+
+static void skipToKeyword(const keywordId keyword) {
+ tokenInfo *const token = newToken();
+ do {
+ readToken(token);
+ } while (!isKeyword(token, keyword));
+ deleteToken(token);
+}
+
+static void skipToMatched(tokenInfo *const token) {
+ int nest_level = 0;
+ tokenType open_token;
+ tokenType close_token;
+
+ switch (token->type) {
+ case TOKEN_OPEN_PAREN:
+ open_token = TOKEN_OPEN_PAREN;
+ close_token = TOKEN_CLOSE_PAREN;
+ break;
+ default:
+ return;
+ }
+
+ /*
+ * This routine will skip to a matching closing token.
+ * It will also handle nested tokens like the (, ) below.
+ * ( name varchar(30), text binary(10) )
+ */
+ if (isType(token, open_token)) {
+ nest_level++;
+ while (!(isType(token, close_token) && (nest_level == 0))) {
+ readToken(token);
+ if (isType(token, open_token)) {
+ nest_level++;
+ }
+ if (isType(token, close_token)) {
+ if (nest_level > 0) {
+ nest_level--;
+ }
+ }
+ }
+ readToken(token);
+ }
+}
+
+static void makeConstTag(tokenInfo *const token, const vhdlKind kind) {
+ if (VhdlKinds[kind].enabled) {
+ const char *const name = vStringValue(token->string);
+ tagEntryInfo e;
+ initTagEntry(&e, name);
+ e.lineNumber = token->lineNumber;
+ e.filePosition = token->filePosition;
+ e.kindName = VhdlKinds[kind].name;
+ e.kind = VhdlKinds[kind].letter;
+ makeTagEntry(&e);
+ }
+}
+
+static void makeVhdlTag(tokenInfo *const token, const vhdlKind kind) {
+ if (VhdlKinds[kind].enabled) {
+ /*
+ * If a scope has been added to the token, change the token
+ * string to include the scope when making the tag.
+ */
+ if (vStringLength(token->scope) > 0) {
+ vString *fulltag = vStringNew();
+ vStringCopy(fulltag, token->scope);
+ vStringCatS(fulltag, ".");
+ vStringCatS(fulltag, vStringValue(token->string));
+ vStringTerminate(fulltag);
+ vStringCopy(token->string, fulltag);
+ vStringDelete(fulltag);
+ }
+ makeConstTag(token, kind);
+ }
+}
+
+static void initialize(const langType language) {
+ size_t i;
+ const size_t count = sizeof(VhdlKeywordTable) / sizeof(VhdlKeywordTable[0]);
+ Lang_vhdl = language;
+ for (i = 0; i < count; ++i) {
+ const keywordDesc *const p = &VhdlKeywordTable[i];
+ addKeyword(p->name, language, (int)p->id);
+ }
+}
+
+static void parsePackage(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+ Assert(isKeyword(token, KEYWORD_PACKAGE));
+ readToken(token);
+ if (isKeyword(token, KEYWORD_BODY)) {
+ readToken(name);
+ makeVhdlTag(name, VHDLTAG_PACKAGE);
+ } else if (isType(token, TOKEN_IDENTIFIER)) {
+ makeVhdlTag(token, VHDLTAG_PACKAGE);
+ }
+ deleteToken(name);
+}
+
+static void parseModule(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+ const vhdlKind kind =
+ isKeyword(token, KEYWORD_ENTITY) ? VHDLTAG_ENTITY : VHDLTAG_COMPONENT;
+ Assert(isKeyword(token, KEYWORD_ENTITY) ||
+ isKeyword(token, KEYWORD_COMPONENT));
+ readToken(name);
+ if (kind == VHDLTAG_COMPONENT) {
+ makeVhdlTag(name, VHDLTAG_COMPONENT);
+ skipToKeyword(KEYWORD_END);
+ fileSkipToCharacter(';');
+ } else {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_IS)) {
+ makeVhdlTag(name, VHDLTAG_ENTITY);
+ skipToKeyword(KEYWORD_END);
+ fileSkipToCharacter(';');
+ }
+ }
+ deleteToken(name);
+}
+
+static void parseRecord(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+ Assert(isKeyword(token, KEYWORD_RECORD));
+ readToken(name);
+ do {
+ readToken(token); /* should be a colon */
+ fileSkipToCharacter(';');
+ makeVhdlTag(name, VHDLTAG_RECORD);
+ readToken(name);
+ } while (!isKeyword(name, KEYWORD_END));
+ fileSkipToCharacter(';');
+ deleteToken(name);
+}
+
+static void parseTypes(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+ const vhdlKind kind =
+ isKeyword(token, KEYWORD_TYPE) ? VHDLTAG_TYPE : VHDLTAG_SUBTYPE;
+ Assert(isKeyword(token, KEYWORD_TYPE) || isKeyword(token, KEYWORD_SUBTYPE));
+ readToken(name);
+ readToken(token);
+ if (isKeyword(token, KEYWORD_IS)) {
+ readToken(token); /* type */
+ if (isKeyword(token, KEYWORD_RECORD)) {
+ makeVhdlTag(name, kind);
+ /*TODO: make tags of the record's names */
+ parseRecord(token);
+ } else {
+ makeVhdlTag(name, kind);
+ }
+ }
+ deleteToken(name);
+}
+
+static void parseConstant(boolean local) {
+ tokenInfo *const name = newToken();
+ readToken(name);
+ if (local) {
+ makeVhdlTag(name, VHDLTAG_LOCAL);
+ } else {
+ makeVhdlTag(name, VHDLTAG_CONSTANT);
+ }
+ fileSkipToCharacter(';');
+ deleteToken(name);
+}
+
+static void parseSubProgram(tokenInfo *const token) {
+ tokenInfo *const name = newToken();
+ boolean endSubProgram = FALSE;
+ const vhdlKind kind =
+ isKeyword(token, KEYWORD_FUNCTION) ? VHDLTAG_FUNCTION : VHDLTAG_PROCEDURE;
+ Assert(isKeyword(token, KEYWORD_FUNCTION) ||
+ isKeyword(token, KEYWORD_PROCEDURE));
+ readToken(name); /* the name of the function or procedure */
+ readToken(token);
+ if (isType(token, TOKEN_OPEN_PAREN)) {
+ skipToMatched(token);
+ }
+
+ if (kind == VHDLTAG_FUNCTION) {
+ if (isKeyword(token, KEYWORD_RETURN)) {
+ /* Read datatype */
+ readToken(token);
+ while (!isKeyword(token, KEYWORD_IS) && !isType(token, TOKEN_SEMICOLON)) {
+ readToken(token);
+ }
+ }
+ }
+
+ if (isType(token, TOKEN_SEMICOLON)) {
+ makeVhdlTag(name, VHDLTAG_PROTOTYPE);
+ } else if (isKeyword(token, KEYWORD_IS)) {
+ if (kind == VHDLTAG_FUNCTION) {
+ makeVhdlTag(name, VHDLTAG_FUNCTION);
+ do {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_END)) {
+ readToken(token);
+ endSubProgram =
+ isKeywordOrIdent(token, KEYWORD_FUNCTION, name->string);
+ fileSkipToCharacter(';');
+ } else {
+ parseKeywords(token, TRUE);
+ }
+ } while (!endSubProgram);
+ } else {
+ makeVhdlTag(name, VHDLTAG_PROCEDURE);
+ do {
+ readToken(token);
+ if (isKeyword(token, KEYWORD_END)) {
+ readToken(token);
+ endSubProgram =
+ isKeywordOrIdent(token, KEYWORD_PROCEDURE, name->string);
+ fileSkipToCharacter(';');
+ } else {
+ parseKeywords(token, TRUE);
+ }
+ } while (!endSubProgram);
+ }
+ }
+ deleteToken(name);
+}
+
+/* TODO */
+/* records */
+static void parseKeywords(tokenInfo *const token, boolean local) {
+ switch (token->keyword) {
+ case KEYWORD_END:
+ fileSkipToCharacter(';');
+ break;
+ case KEYWORD_CONSTANT:
+ parseConstant(local);
+ break;
+ case KEYWORD_TYPE:
+ parseTypes(token);
+ break;
+ case KEYWORD_SUBTYPE:
+ parseTypes(token);
+ break;
+ case KEYWORD_ENTITY:
+ parseModule(token);
+ break;
+ case KEYWORD_COMPONENT:
+ parseModule(token);
+ break;
+ case KEYWORD_FUNCTION:
+ parseSubProgram(token);
+ break;
+ case KEYWORD_PROCEDURE:
+ parseSubProgram(token);
+ break;
+ case KEYWORD_PACKAGE:
+ parsePackage(token);
+ break;
+ default:
+ break;
+ }
+}
+
+static void parseVhdlFile(tokenInfo *const token) {
+ do {
+ readToken(token);
+ parseKeywords(token, FALSE);
+ } while (!isKeyword(token, KEYWORD_END));
+}
+
+static void findVhdlTags(void) {
+ tokenInfo *const token = newToken();
+ exception_t exception = (exception_t)(setjmp(Exception));
+
+ while (exception == ExceptionNone) parseVhdlFile(token);
+
+ deleteToken(token);
+}
+
+extern parserDefinition *VhdlParser(void) {
+ static const char *const extensions[] = {"vhdl", "vhd", NULL};
+ parserDefinition *def = parserNew("VHDL");
+ def->kinds = VhdlKinds;
+ def->kindCount = KIND_COUNT(VhdlKinds);
+ def->extensions = extensions;
+ def->parser = findVhdlTags;
+ def->initialize = initialize;
+ return def;
+}
+
+/* vi:set tabstop=4 shiftwidth=4 noet: */
diff --git a/third_party/ctags/vim.c b/third_party/ctags/vim.c
new file mode 100644
index 000000000..396ce8d51
--- /dev/null
+++ b/third_party/ctags/vim.c
@@ -0,0 +1,547 @@
+/*
+ * $Id: vim.c 762 2010-07-28 11:38:19Z dfishburn $
+ *
+ * Copyright (c) 2000-2003, Darren Hiebert
+ *
+ * This source code is released for free distribution under the
+ * terms of the GNU General Public License.
+ *
+ * Thanks are due to Jay Glanville for significant improvements.
+ *
+ * This module contains functions for generating tags for user-defined
+ * functions for the Vim editor.
+ */
+#include "third_party/ctags/general.h"
+/* must always come first */
+#include "third_party/ctags/parse.h"
+#include "third_party/ctags/read.h"
+#include "third_party/ctags/vstring.h"
+
+#if 0
+typedef struct sLineInfo {
+ tokenType type;
+ keywordId keyword;
+ vString * string;
+ vString * scope;
+ unsigned long lineNumber;
+ fpos_t filePosition;
+} lineInfo;
+#endif
+
+/*
+ * DATA DEFINITIONS
+ */
+typedef enum { K_AUGROUP, K_COMMAND, K_FUNCTION, K_MAP, K_VARIABLE } vimKind;
+
+static kindOption VimKinds[] = {
+ {TRUE, 'a', "augroup", "autocommand groups"},
+ {TRUE, 'c', "command", "user-defined commands"},
+ {TRUE, 'f', "function", "function definitions"},
+ {TRUE, 'm', "map", "maps"},
+ {TRUE, 'v', "variable", "variable definitions"},
+};
+
+/*
+ * DATA DECLARATIONS
+ */
+
+#if 0
+typedef enum eException {
+ ExceptionNone, ExceptionEOF
+} exception_t;
+#endif
+
+/*
+ * DATA DEFINITIONS
+ */
+
+#if 0
+static jmp_buf Exception;
+#endif
+
+/*
+ * FUNCTION DEFINITIONS
+ */
+
+/* This function takes a char pointer, tries to find a scope separator in the
+ * string, and if it does, returns a pointer to the character after the colon,
+ * and the character defining the scope.
+ * If a colon is not found, it returns the original pointer.
+ */
+static const unsigned char *skipPrefix(const unsigned char *name, int *scope) {
+ const unsigned char *result = name;
+ int counter;
+ size_t length;
+ length = strlen((const char *)name);
+ if (scope != NULL) *scope = '\0';
+ if (length > 3 && name[1] == ':') {
+ if (scope != NULL) *scope = *name;
+ result = name + 2;
+ } else if (length > 5 &&
+ strncasecmp((const char *)name, "", (size_t)5) == 0) {
+ if (scope != NULL) *scope = *name;
+ result = name + 5;
+ } else {
+ /*
+ * Vim7 check for dictionaries or autoload function names
+ */
+ counter = 0;
+ do {
+ switch (name[counter]) {
+ case '.':
+ /* Set the scope to d - Dictionary */
+ *scope = 'd';
+ break;
+ case '#':
+ /* Set the scope to a - autoload */
+ *scope = 'a';
+ break;
+ }
+ ++counter;
+ } while (isalnum((int)name[counter]) || name[counter] == '_' ||
+ name[counter] == '.' || name[counter] == '#');
+ }
+ return result;
+}
+
+static boolean isMap(const unsigned char *line) {
+ /*
+ * There are many different short cuts for specifying a map.
+ * This routine should capture all the permutations.
+ */
+ if (strncmp((const char *)line, "map", (size_t)3) == 0 ||
+ strncmp((const char *)line, "nm", (size_t)2) == 0 ||
+ strncmp((const char *)line, "nma", (size_t)3) == 0 ||
+ strncmp((const char *)line, "nmap", (size_t)4) == 0 ||
+ strncmp((const char *)line, "vm", (size_t)2) == 0 ||
+ strncmp((const char *)line, "vma", (size_t)3) == 0 ||
+ strncmp((const char *)line, "vmap", (size_t)4) == 0 ||
+ strncmp((const char *)line, "om", (size_t)2) == 0 ||
+ strncmp((const char *)line, "oma", (size_t)3) == 0 ||
+ strncmp((const char *)line, "omap", (size_t)4) == 0 ||
+ strncmp((const char *)line, "im", (size_t)2) == 0 ||
+ strncmp((const char *)line, "ima", (size_t)3) == 0 ||
+ strncmp((const char *)line, "imap", (size_t)4) == 0 ||
+ strncmp((const char *)line, "lm", (size_t)2) == 0 ||
+ strncmp((const char *)line, "lma", (size_t)3) == 0 ||
+ strncmp((const char *)line, "lmap", (size_t)4) == 0 ||
+ strncmp((const char *)line, "cm", (size_t)2) == 0 ||
+ strncmp((const char *)line, "cma", (size_t)3) == 0 ||
+ strncmp((const char *)line, "cmap", (size_t)4) == 0 ||
+ strncmp((const char *)line, "no", (size_t)2) == 0 ||
+ strncmp((const char *)line, "nor", (size_t)3) == 0 ||
+ strncmp((const char *)line, "nore", (size_t)4) == 0 ||
+ strncmp((const char *)line, "norem", (size_t)5) == 0 ||
+ strncmp((const char *)line, "norema", (size_t)6) == 0 ||
+ strncmp((const char *)line, "noremap", (size_t)7) == 0 ||
+ strncmp((const char *)line, "nno", (size_t)3) == 0 ||
+ strncmp((const char *)line, "nnor", (size_t)4) == 0 ||
+ strncmp((const char *)line, "nnore", (size_t)5) == 0 ||
+ strncmp((const char *)line, "nnorem", (size_t)6) == 0 ||
+ strncmp((const char *)line, "nnorema", (size_t)7) == 0 ||
+ strncmp((const char *)line, "nnoremap", (size_t)8) == 0 ||
+ strncmp((const char *)line, "vno", (size_t)3) == 0 ||
+ strncmp((const char *)line, "vnor", (size_t)4) == 0 ||
+ strncmp((const char *)line, "vnore", (size_t)5) == 0 ||
+ strncmp((const char *)line, "vnorem", (size_t)6) == 0 ||
+ strncmp((const char *)line, "vnorema", (size_t)7) == 0 ||
+ strncmp((const char *)line, "vnoremap", (size_t)8) == 0 ||
+ strncmp((const char *)line, "ono", (size_t)3) == 0 ||
+ strncmp((const char *)line, "onor", (size_t)4) == 0 ||
+ strncmp((const char *)line, "onore", (size_t)5) == 0 ||
+ strncmp((const char *)line, "onorem", (size_t)6) == 0 ||
+ strncmp((const char *)line, "onorema", (size_t)7) == 0 ||
+ strncmp((const char *)line, "onoremap", (size_t)8) == 0 ||
+ strncmp((const char *)line, "ino", (size_t)3) == 0 ||
+ strncmp((const char *)line, "inor", (size_t)4) == 0 ||
+ strncmp((const char *)line, "inore", (size_t)5) == 0 ||
+ strncmp((const char *)line, "inorem", (size_t)6) == 0 ||
+ strncmp((const char *)line, "inorema", (size_t)7) == 0 ||
+ strncmp((const char *)line, "inoremap", (size_t)8) == 0 ||
+ strncmp((const char *)line, "lno", (size_t)3) == 0 ||
+ strncmp((const char *)line, "lnor", (size_t)4) == 0 ||
+ strncmp((const char *)line, "lnore", (size_t)5) == 0 ||
+ strncmp((const char *)line, "lnorem", (size_t)6) == 0 ||
+ strncmp((const char *)line, "lnorema", (size_t)7) == 0 ||
+ strncmp((const char *)line, "lnoremap", (size_t)8) == 0 ||
+ strncmp((const char *)line, "cno", (size_t)3) == 0 ||
+ strncmp((const char *)line, "cnor", (size_t)4) == 0 ||
+ strncmp((const char *)line, "cnore", (size_t)5) == 0 ||
+ strncmp((const char *)line, "cnorem", (size_t)6) == 0 ||
+ strncmp((const char *)line, "cnorema", (size_t)7) == 0 ||
+ strncmp((const char *)line, "cnoremap", (size_t)8) == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static const unsigned char *readVimLine(void) {
+ const unsigned char *line;
+
+ while ((line = fileReadLine()) != NULL) {
+ while (isspace((int)*line)) ++line;
+
+ if ((int)*line == '"') continue; /* skip comment */
+
+ break;
+ }
+
+ return line;
+}
+
+static void parseFunction(const unsigned char *line) {
+ vString *name = vStringNew();
+ /* boolean inFunction = FALSE; */
+ int scope;
+
+ const unsigned char *cp = line + 1;
+
+ if ((int)*++cp == 'n' && (int)*++cp == 'c' && (int)*++cp == 't' &&
+ (int)*++cp == 'i' && (int)*++cp == 'o' && (int)*++cp == 'n')
+ ++cp;
+ if ((int)*cp == '!') ++cp;
+ if (isspace((int)*cp)) {
+ while (*cp && isspace((int)*cp)) ++cp;
+
+ if (*cp) {
+ cp = skipPrefix(cp, &scope);
+ if (isupper((int)*cp) || scope == 's' || /* script scope */
+ scope == '<' || /* script scope */
+ scope == 'd' || /* dictionary */
+ scope == 'a') /* autoload */
+ {
+ do {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ } while (isalnum((int)*cp) || *cp == '_' || *cp == '.' || *cp == '#');
+ vStringTerminate(name);
+ makeSimpleTag(name, VimKinds, K_FUNCTION);
+ vStringClear(name);
+ }
+ }
+ }
+
+ /* TODO - update struct to indicate inside function */
+ while ((line = readVimLine()) != NULL) {
+ /*
+ * Vim7 added the for/endfo[r] construct, so we must first
+ * check for an "endfo", before a "endf"
+ */
+ if ((!strncmp((const char *)line, "endfo", (size_t)5) == 0) &&
+ (strncmp((const char *)line, "endf", (size_t)4) == 0))
+ break;
+ /* TODO - call parseVimLine */
+ }
+ vStringDelete(name);
+}
+
+static void parseAutogroup(const unsigned char *line) {
+ vString *name = vStringNew();
+
+ /* Found Autocommand Group (augroup) */
+ const unsigned char *cp = line + 2;
+ if ((int)*++cp == 'r' && (int)*++cp == 'o' && (int)*++cp == 'u' &&
+ (int)*++cp == 'p')
+ ++cp;
+ if (isspace((int)*cp)) {
+ while (*cp && isspace((int)*cp)) ++cp;
+
+ if (*cp) {
+ if (strncasecmp((const char *)cp, "end", (size_t)3) != 0) {
+ do {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ } while (isalnum((int)*cp) || *cp == '_');
+ vStringTerminate(name);
+ makeSimpleTag(name, VimKinds, K_AUGROUP);
+ vStringClear(name);
+ }
+ }
+ }
+ vStringDelete(name);
+}
+
+static boolean parseCommand(const unsigned char *line) {
+ vString *name = vStringNew();
+ boolean cmdProcessed = TRUE;
+
+ /*
+ * Found a user-defined command
+ *
+ * They can have many options preceeded by a dash
+ * command! -nargs=+ -complete Select :call s:DB_execSql("select " .
+ * ) The name of the command should be the first word not preceeded by
+ * a dash
+ *
+ */
+ const unsigned char *cp = line;
+
+ if ((int)*cp == '\\') {
+ /*
+ * We are recursively calling this function is the command
+ * has been continued on to the next line
+ *
+ * Vim statements can be continued onto a newline using a \
+ * to indicate the previous line is continuing.
+ *
+ * com -nargs=1 -bang -complete=customlist,EditFileComplete
+ * \ EditFile edit
+ *
+ * If the following lines do not have a line continuation
+ * the command must not be spanning multiple lines and should
+ * be synatically incorrect.
+ */
+ if ((int)*cp == '\\') ++cp;
+
+ while (*cp && isspace((int)*cp)) ++cp;
+ } else if ((!strncmp((const char *)line, "comp", (size_t)4) == 0) &&
+ (!strncmp((const char *)line, "comc", (size_t)4) == 0) &&
+ (strncmp((const char *)line, "com", (size_t)3) == 0)) {
+ cp += 2;
+ if ((int)*++cp == 'm' && (int)*++cp == 'a' && (int)*++cp == 'n' &&
+ (int)*++cp == 'd')
+ ++cp;
+
+ if ((int)*cp == '!') ++cp;
+
+ if ((int)*cp != ' ') {
+ /*
+ * :command must be followed by a space. If it is not, it is
+ * not a valid command.
+ * Treat the line as processed and continue.
+ */
+ cmdProcessed = TRUE;
+ goto cleanUp;
+ }
+
+ while (*cp && isspace((int)*cp)) ++cp;
+ } else {
+ /*
+ * We are recursively calling this function. If it does not start
+ * with "com" or a line continuation character, we have moved off
+ * the command line and should let the other routines parse this file.
+ */
+ cmdProcessed = FALSE;
+ goto cleanUp;
+ }
+
+ /*
+ * Strip off any spaces and options which are part of the command.
+ * These should preceed the command name.
+ */
+ do {
+ if (isspace((int)*cp)) {
+ ++cp;
+ } else if (*cp == '-') {
+ /*
+ * Read until the next space which separates options or the name
+ */
+ while (*cp && !isspace((int)*cp)) ++cp;
+ } else
+ break;
+ } while (*cp);
+
+ if (!*cp) {
+ /*
+ * We have reached the end of the line without finding the command name.
+ * Read the next line and continue processing it as a command.
+ */
+ line = readVimLine();
+ parseCommand(line);
+ goto cleanUp;
+ }
+
+ do {
+ vStringPut(name, (int)*cp);
+ ++cp;
+ } while (isalnum((int)*cp) || *cp == '_');
+
+ vStringTerminate(name);
+ makeSimpleTag(name, VimKinds, K_COMMAND);
+ vStringClear(name);
+
+cleanUp:
+ vStringDelete(name);
+
+ return cmdProcessed;
+}
+
+static void parseLet(const unsigned char *line) {
+ vString *name = vStringNew();
+
+ /* we've found a variable declared outside of a function!! */
+ const unsigned char *cp = line + 3;
+ const unsigned char *np = line;
+ /* get the name */
+ if (isspace((int)*cp)) {
+ while (*cp && isspace((int)*cp)) ++cp;
+
+ /*
+ * Ignore lets which set:
+ * & - local buffer vim settings
+ * @ - registers
+ * [ - Lists or Dictionaries
+ */
+ if (!*cp || *cp == '&' || *cp == '@' || *cp == '[') goto cleanUp;
+
+ /*
+ * Ignore vim variables which are read only
+ * v: - Vim variables.
+ */
+ np = cp;
+ ++np;
+ if ((int)*cp == 'v' && (int)*np == ':') goto cleanUp;
+
+ /* deal with spaces, $, @ and & */
+ while (*cp && *cp != '$' && !isalnum((int)*cp)) ++cp;
+
+ if (!*cp) goto cleanUp;
+
+ /* cp = skipPrefix (cp, &scope); */
+ do {
+ if (!*cp) break;
+
+ vStringPut(name, (int)*cp);
+ ++cp;
+ } while (isalnum((int)*cp) || *cp == '_' || *cp == '#' || *cp == ':' ||
+ *cp == '$');
+ vStringTerminate(name);
+ makeSimpleTag(name, VimKinds, K_VARIABLE);
+ vStringClear(name);
+ }
+
+cleanUp:
+ vStringDelete(name);
+}
+
+static boolean parseMap(const unsigned char *line) {
+ vString *name = vStringNew();
+
+ const unsigned char *cp = line;
+
+ /* Remove map */
+ while (*cp && isalnum((int)*cp)) ++cp;
+
+ if ((int)*cp == '!') ++cp;
+
+ /*
+ * Maps follow this basic format
+ * map
+ * nnoremap :Tlist
+ * map scdt GetColumnDataType
+ * inoremap ,,, diwi<pa>pa>kA
+ * inoremap ( =PreviewFunctionSignature()
+ *
+ * The Vim help shows the various special arguments available to a map:
+ * 1.2 SPECIAL ARGUMENTS *:map-arguments*
+ *
+ *
+ *