diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6e83ee..3e0894b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,30 +19,128 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v4 - with: - submodules: true - - name: Build + - name: build run: | ./configure make -j$(getconf _NPROCESSORS_ONLN) - macos: - name: macOS - runs-on: macos-latest - strategy: - fail-fast: false + linux-32bits: + name: Linux 32-bits + runs-on: ubuntu-latest + defaults: + run: + shell: alpine.sh {0} steps: - uses: actions/checkout@v4 - - name: Build + - uses: jirutka/setup-alpine@v1 + with: + arch: x86 + packages: "build-base make" + - name: build + run: | + ./configure + make CONFIG_32BIT=y + + linux-s390x: + runs-on: ubuntu-latest + defaults: + run: + shell: alpine.sh {0} + steps: + - uses: actions/checkout@v3 + - uses: jirutka/setup-alpine@v1 + with: + arch: s390x + packages: "build-base make" + - name: build + run: | + ./configure + make + + macos: + name: macOS + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-12, macos-14] + steps: + - uses: actions/checkout@v4 + - name: build run: | ./configure make -j$(getconf _NPROCESSORS_ONLN) +# windows-mingw: +# runs-on: windows-latest +# strategy: +# fail-fast: false +# matrix: +# sys: +# - mingw32 +# - mingw64 +# - clang64 +# - ucrt64 +# defaults: +# run: +# shell: msys2 {0} +# steps: +# - uses: actions/checkout@v4 +# - name: Setup MSYS2 +# uses: msys2/setup-msys2@v2 +# with: +# msystem: ${{matrix.sys}} +# install: >- +# git +# make +# pacboy: >- +# ninja:p +# toolchain:p +# - name: build +# run: | +# ./configure +# make +# +# cygwin: +# runs-on: windows-latest +# defaults: +# run: +# shell: C:\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' +# env: +# CYGWIN_NOWINPATH: 1 +# CHERE_INVOKING: 1 +# steps: +# - name: Set up Cygwin +# uses: cygwin/cygwin-install-action@master +# with: +# packages: make gcc-g++ bash +# +# - uses: actions/checkout@v4 +# +# - name: build +# run: | +# ./configure +# make + + openbsd: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: build + uses: vmactions/openbsd-vm@v1 + with: + usesh: true + prepare: | + pkg_add gmake + run: | + ./configure + gmake MAKE=gmake + freebsd: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Build + test + - name: build uses: vmactions/freebsd-vm@v1 with: usesh: true @@ -50,27 +148,18 @@ jobs: pkg install -y gmake run: | ./configure - gmake + gmake MAKE=gmake - qemu-alpine: + netbsd: runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - platform: - - i386 - - arm32v6 - - arm32v7 - - arm64v8 - - s390x - steps: - uses: actions/checkout@v4 + - name: build + uses: vmactions/netbsd-vm@v1 with: - submodules: recursive - - name: Get qemu - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - - name: Run tests on ${{ matrix.platform }} - run: docker run --rm --interactive --mount type=bind,source=$(pwd),target=/host ${{ matrix.platform }}/alpine sh -c "apk add git patch make gcc libc-dev && cd /host && ./configure && make" - + usesh: true + prepare: | + /usr/sbin/pkg_add gmake + run: | + ./configure + gmake MAKE=gmake diff --git a/Makefile b/Makefile index 6637124..8f37f77 100644 --- a/Makefile +++ b/Makefile @@ -90,7 +90,7 @@ TARGETLIBS:= ifeq (,$(TARGET)) TARGET:=qe -TARGETS:=kmaps ligatures tqe +TARGETS:=kmaps ligatures tqe qe-manual.md TOP:=1 else TOP:=0 @@ -102,6 +102,11 @@ endif OBJS:= qe.o cutils.o util.o color.o charset.o buffer.o search.o input.o display.o \ qescript.o modes/hex.o +ifdef CONFIG_32BIT +CFLAGS += -m32 +LDFLAGS += -m32 +endif + ifdef TARGET_TINY ECHO_CFLAGS += -DCONFIG_TINY CFLAGS += -DCONFIG_TINY -Os diff --git a/README.md b/README.md index 2350da3..a6f2002 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Read the file [qe-doc.html](qe-doc.html). ## Licensing QEmacs is released under the MIT license. -(read the accompagning [LICENCE](LICENCE) file). +(read the accompanying [LICENCE](LICENCE) file). ## Contributing to QEmacs diff --git a/configure b/configure index 4eedf85..8beb64f 100755 --- a/configure +++ b/configure @@ -5,14 +5,16 @@ # Copyright (c) 2002 Fabrice Bellard # Copyright (c) 2002-2023 Charlie Gordon # - +# # set temporary file name if test ! -z "$TMPDIR" ; then - TMPDIR1="${TMPDIR}" -elif test ! -z "$TEMPDIR" ; then - TMPDIR1="${TEMPDIR}" + TMPDIR1="${TMPDIR}" else - TMPDIR1="/tmp" + if test ! -z "$TEMPDIR" ; then + TMPDIR1="${TEMPDIR}" + else + TMPDIR1="/tmp" + fi fi TMPC="${TMPDIR1}/qemacs-conf-${RANDOM}-$$-${RANDOM}.c" @@ -24,8 +26,24 @@ TMPMAK="${TMPDIR1}/qemacs-conf-${RANDOM}-$$-${RANDOM}.mak" # default parameters prefix="/usr/local" cross_prefix="" -cc="gcc" -host_cc="gcc" + +cc="cc" +host_cc="cc" +if test `which clang 2>/dev/null` ; then + cc="clang" + host_cc="clang" +else + if test `which gcc 2>/dev/null` ; then + cc="gcc" + host_cc="gcc" + else + if test `which tcc 2>/dev/null` ; then + cc="tcc" + host_cc="tcc" + fi + fi +fi + ar="ar" make="make" size="size" @@ -36,21 +54,6 @@ case "$cpu" in i386|i486|i586|i686|i86pc|BePC) cpu="x86" ;; - x86_64) - cpu="x86_64" - ;; - arm64) - cpu="arm64" - ;; - armv4l) - cpu="armv4l" - ;; - alpha) - cpu="alpha" - ;; - *) - cpu="unknown" - ;; esac unlockio="no" @@ -107,6 +110,13 @@ case $targetos in plugins="no" ptsname="no" ;; + NetBSD) + extralibs="-lm" + unlockio="no" + doc="no" + plugins="no" + dllibs= + ;; OpenBSD) extralibs="-lm" make="gmake" @@ -114,6 +124,8 @@ case $targetos in plugins="no" ptsname="no" dllibs= + cc="cc" + host_cc="cc" ;; FreeBSD) extralibs="-lm" @@ -123,6 +135,10 @@ case $targetos in plugins="no" ptsname="yes" cc="clang" + host_cc="clang" + ;; + MINGW*) + mingw="yes" ;; CYGWIN*) cygwin="yes" @@ -135,6 +151,7 @@ case $targetos in extralibs="-lm" strip_args="-x -S" cc="clang" + host_cc="clang" ;; Haiku) prefix="`finddir B_COMMON_DIRECTORY`" @@ -192,7 +209,7 @@ for x11path in /usr /opt/X11 /usr/X11R6; do fi done -if test -z `which texi2html` ; then +if test -z `which texi2html 2>/dev/null` ; then doc="no" fi @@ -226,6 +243,7 @@ echo "Advanced options (experts only):" echo " --source-path=PATH path of source code [$source_path]" echo " --cross-prefix=PREFIX use PREFIX for compile tools [$cross_prefix]" echo " --cc=CC use C compiler CC [$cc]" +echo " --host-cc=CC use C host compiler CC [$host_cc]" echo " --make=MAKE use specified make [$make]" echo " --with-ffmpeglibdir=DIR set ffmpeg object files directory" echo "" @@ -254,6 +272,9 @@ for opt do --cc=*) cc=`echo $opt | cut -d '=' -f 2` ;; + --host-cc=*) + host_cc=`echo $opt | cut -d '=' -f 2` + ;; --make=*) make=`echo $opt | cut -d '=' -f 2` ;; @@ -404,6 +425,16 @@ if test "$cygwin" = "yes" ; then exe=".exe" fi +if test "$mingw" = "yes" ; then + doc="no" + x11="no" + png="no" + ffmpeg="no" + html="no" + plugins="no" + exe=".exe" +fi + if test "$x11" = "no" ; then xv="no" xshm="no" @@ -521,7 +552,8 @@ echo "Manpages dir $mandir" echo "Source path $source_path" echo "C compiler $cc" echo " version " `$cc --version | head -n 1` -echo " target_arch $cpu" +echo " cpu $cpu" +echo " targetos $targetos" echo "Tiny build $tiny" echo "X11 support $x11" if test "$x11" = "yes" ; then @@ -569,25 +601,32 @@ echo "EXE=$exe" >> $TMPMAK echo "TARGET_OS=$targetos" >> $TMPMAK echo "TARGET_ARCH=$cpu" >> $TMPMAK -if test "$cpu" = "x86" ; then - echo "TARGET_ARCH_X86=yes" >> $TMPMAK - echo "#define ARCH_X86 1" >> $TMPH -elif test "$cpu" = "x86_64" ; then - echo "TARGET_ARCH_X86_64=yes" >> $TMPMAK - echo "#define ARCH_X86_64 1" >> $TMPH -elif test "$cpu" = "arm64" ; then - echo "TARGET_ARCH_ARM64=yes" >> $TMPMAK - echo "#define ARCH_ARM64 1" >> $TMPH -elif test "$cpu" = "armv4l" ; then - echo "TARGET_ARCH_ARMV4L=yes" >> $TMPMAK - echo "#define ARCH_ARMV4L 1" >> $TMPH -elif test "$cpu" = "alpha" ; then - echo "TARGET_ARCH_ALPHA=yes" >> $TMPMAK - echo "#define ARCH_ALPHA 1" >> $TMPH -elif test "$cpu" = "powerpc" ; then - echo "TARGET_ARCH_POWERPC=yes" >> $TMPMAK - echo "#define ARCH_POWERPC 1" >> $TMPH -fi +case "$cpu" in + x86) + echo "TARGET_ARCH_X86=yes" >> $TMPMAK + echo "#define ARCH_X86 1" >> $TMPH + ;; + x86_64) + echo "TARGET_ARCH_X86_64=yes" >> $TMPMAK + echo "#define ARCH_X86_64 1" >> $TMPH + ;; + arm64) + echo "TARGET_ARCH_ARM64=yes" >> $TMPMAK + echo "#define ARCH_ARM64 1" >> $TMPH + ;; + armv4l) + echo "TARGET_ARCH_ARMV4L=yes" >> $TMPMAK + echo "#define ARCH_ARMV4L 1" >> $TMPH + ;; + alpha) + echo "TARGET_ARCH_ALPHA=yes" >> $TMPMAK + echo "#define ARCH_ALPHA 1" >> $TMPH + ;; + powerpc) + echo "TARGET_ARCH_POWERPC=yes" >> $TMPMAK + echo "#define ARCH_POWERPC 1" >> $TMPH + ;; +esac if test "$has_typeof" = "yes" ; then echo "CONFIG_HAS_TYPEOF=yes" >> $TMPMAK echo "#define CONFIG_HAS_TYPEOF 1" >> $TMPH diff --git a/cutils.h b/cutils.h index a305ca0..d4472a5 100644 --- a/cutils.h +++ b/cutils.h @@ -402,17 +402,22 @@ static inline void put_u8(uint8_t *tab, uint8_t val) *tab = val; } +#ifndef bswap16 static inline uint16_t bswap16(uint16_t x) { return (x >> 8) | (x << 8); } +#endif +#ifndef bswap32 static inline uint32_t bswap32(uint32_t v) { return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) | ((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24); } +#endif +#ifndef bswap64 static inline uint64_t bswap64(uint64_t v) { return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) | @@ -424,6 +429,7 @@ static inline uint64_t bswap64(uint64_t v) ((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) | ((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8)); } +#endif /* XXX: should take an extra argument to pass slack information to the caller */ typedef void *DynBufReallocFunc(void *opaque, void *ptr, size_t size); diff --git a/qe-manual.c b/qe-manual.c index 3876417..9714d61 100644 --- a/qe-manual.c +++ b/qe-manual.c @@ -2,7 +2,7 @@ * QEmacs, tiny but powerful multimode editor * * Copyright (c) 2000-2002 Fabrice Bellard. - * Copyright (c) 2000-2023 Charlie Gordon. + * Copyright (c) 2000-2024 Charlie Gordon. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -33,22 +33,116 @@ */ /*@ INTRO=0 - introduction + # Introduction + + Welcome to QEmacs! A small but powerful UNIX editor with many features + that even big editors lack. + + ## Quick Description + + QEmacs is a small text editor targeted at embedded systems or debugging. + Although it is very small, it has some very interesting features that + even big editors lack: + + - Full screen editor with an Emacs look and feel with all common Emacs + features: multi-buffer, multi-window, command mode, universal argument, + keyboard macros, config file with C-like syntax, minibuffer with + completion and history. + + - Can edit huge files (hundreds of megabytes) without delay, using a + highly optimized internal representation and memory mapping for large + files. + + - Full Unicode support, including multi charset handling + (8859-x, UTF8, SJIS, EUC-JP, ...) and bidirectional editing respecting + the Unicode bidi algorithm. Arabic and Indic scripts handling (in + progress). Automatic end of line detection. + + - C mode: coloring with immediate update, auto-indent, automatic tags. + + - Shell mode: full color VT100 terminal emulation so your shell works + exactly as you expect. Compile mode with colorized error messages, + automatic error message parser jumps to next/previous error, works + with grep too. The shell buffer is a fully functional terminal: you + can run qemacs, vim or even emacs recursively! + + - Input methods for most languages, including Chinese (input methods + descriptions come from the Yudit editor). + + - Binary and hexadecimal in place editing mode with insertion and + block commands. Unicode hexa editing of UTF-8 files also supported. + Can patch binary files, preserving every byte outside the modified + areas. + + - Works on any VT100 terminal without termcap. UTF-8 VT100 support + included with double width glyphs. + + - X11 support. Supports multiple proportional fonts at the same time + (like XEmacs). X Input methods supported. Xft extension supported for + anti-aliased font display. + + - Bitmap images are displayed on graphics displays and as ASCII colored text + on text terminals, which is handy when browsing files over an ssh connection. + (QEmacs uses the public domain [`stb_image`](https://github.com/nothings/stb/blob/master/stb_image.h) + package for image parsing. + */ + /*@ CONCEPTS=1 - concepts + # Concepts */ + /*@ BUFFERS=2 - buffers + # Buffers */ -/*@ WINDOWS-3 - windows + +/*@ WINDOWS=3 - windows + # Windows */ + /*@ MODES=4 - modes + # Modes */ + /*@ CMD=5 - commands + # Commands */ + /*@ IMPL=6 - implementation + # Implementation */ + /*@ STRUCT=7 - structures + # Structures */ + /*@ API=8 - functions + # C functions */ -/*@ CREDITS=9 - credits + +/*@ EPILOG=9 - epilog + ## Building QEmacs + + * Get the source code from github or an archive. + * Launch the custom configuration script `./configure`. You can list the +available options by typing `./configure --help`. + * Type `make` to compile qemacs and its associated tools. + * Type `make install` as root to install it in ** /usr/local **. + + ## Authors + + QEmacs was started in 2000. The initial version was developped by + Fabrice Bellard and Charlie Gordon, who since then, has been maintaining + and extending it. + + ## Licensing + + QEmacs is released under the MIT license. + (read the accompanying [LICENCE](LICENCE) file). + + ## Contributing to QEmacs + + The QEmacs project is hosted on [github](https://github.com/qemacs/qemacs/) + Please file an issue for any questions or feature requests. Patch requests are welcome. + */ diff --git a/qe-manual.md b/qe-manual.md index 13d4bbc..605298e 100644 --- a/qe-manual.md +++ b/qe-manual.md @@ -1,4 +1,90 @@ +# Introduction + +Welcome to QEmacs! A small but powerful UNIX editor with many features +that even big editors lack. + +## Quick Description + +QEmacs is a small text editor targeted at embedded systems or debugging. +Although it is very small, it has some very interesting features that +even big editors lack: + +- Full screen editor with an Emacs look and feel with all common Emacs +features: multi-buffer, multi-window, command mode, universal argument, +keyboard macros, config file with C-like syntax, minibuffer with +completion and history. + +- Can edit huge files (hundreds of megabytes) without delay, using a +highly optimized internal representation and memory mapping for large +files. + +- Full Unicode support, including multi charset handling +(8859-x, UTF8, SJIS, EUC-JP, ...) and bidirectional editing respecting +the Unicode bidi algorithm. Arabic and Indic scripts handling (in +progress). Automatic end of line detection. + +- C mode: coloring with immediate update, auto-indent, automatic tags. + +- Shell mode: full color VT100 terminal emulation so your shell works +exactly as you expect. Compile mode with colorized error messages, +automatic error message parser jumps to next/previous error, works +with grep too. The shell buffer is a fully functional terminal: you +can run qemacs, vim or even emacs recursively! + +- Input methods for most languages, including Chinese (input methods +descriptions come from the Yudit editor). + +- Binary and hexadecimal in place editing mode with insertion and +block commands. Unicode hexa editing of UTF-8 files also supported. +Can patch binary files, preserving every byte outside the modified +areas. + +- Works on any VT100 terminal without termcap. UTF-8 VT100 support +included with double width glyphs. + +- X11 support. Supports multiple proportional fonts at the same time +(like XEmacs). X Input methods supported. Xft extension supported for +anti-aliased font display. + +- Bitmap images are displayed on graphics displays and as ASCII colored text +on text terminals, which is handy when browsing files over an ssh connection. +(QEmacs uses the public domain [`stb_image`](https://github.com/nothings/stb/blob/master/stb_image.h) +package for image parsing. + +# Concepts + +# Buffers + +# Windows + +# Modes + +# Commands + +### `isearch-repeat-backward()` +Search for the next match backward. +Retrieve the last search string if search string is empty. + +### `isearch-repeat-forward()` +Search for the next match forward. +Retrieve the last search string if search string is empty. + +### `isearch-yank-char()` +Extract the current character from the buffer and append it +to the search string. + +### `isearch-yank-kill()` +Append the contents of the last kill to the search string. + +### `isearch-yank-line()` +Extract the current line from the buffer and append it to the +search string. + +### `isearch-yank-word-or-char()` +Extract the current character or word from the buffer and append it +to the search string. + ### `overwrite-mode(argval)` Toggle overwrite mode. @@ -14,20 +100,20 @@ Backspace erases the previous character and sets point to it. C-q still inserts characters in overwrite mode as a convenient way to insert characters when necessary. -### `query-replace(string FROM-STRING, string TO-STRING, - int DELIMITED=argval, int START=point, int END=end)` +### `query-replace(string FROM-STRING, string TO-STRING, int DELIMITED=argval, int START=point, int END=end)` Replace some occurrences of FROM-STRING with TO-STRING. As each match is found, the user must type a character saying what to do with it. For directions, type '?' at that time. -Matching is independent of case if `case-fold-search` is non-zero and -FROM-STRING has no uppercase letters. Replacement transfers the case -pattern of the old text to the new text, if `case-replace` and -`case-fold-search` are non-zero and FROM-STRING has no uppercase -letters. (Transferring the case pattern means that if the old text -matched is all caps, or capitalized, then its replacement is upcased -or capitalized.) +FROM-STRING is analyzed for search flag names with determine how +matches are found. Supported flags are [UniHex], [Hex], [Folding], +[Exact], [Regex] and [Word]. + +If case matching is either Folding or Smart, replacement transfers +the case pattern of the old text to the new text. For example +if the old text matched is all caps, or capitalized, then its +replacement is upcased or capitalized. Third arg DELIMITED (prefix arg if interactive), if non-zero, means replace only matches surrounded by word boundaries. @@ -40,8 +126,7 @@ operate from point to the end of the buffer. To customize possible responses, change the "bindings" in `query-replace-mode`. -### `replace-string(string FROM-STRING, string TO-STRING, - int DELIMITED=argval, int START=point, int END=end)` +### `replace-string(string FROM-STRING, string TO-STRING, int DELIMITED=argval, int START=point, int END=end)` Replace occurrences of FROM-STRING with TO-STRING. Preserve case in each match if `case-replace' and `case-fold-search' @@ -79,6 +164,12 @@ If the RENAMEFILE argument is not null and starts with 'y', an attempt is made to rename the old visited file to the new name FILENAME. +# Implementation + +# Structures + +# C functions + ### `void qe_free(T **pp);` Free the allocated memory pointed to by a pointer whose address is passed. @@ -200,6 +291,8 @@ from modifying the existing pointer, which is unchanged if reallocation fails. This approach is not strictly conforming, it assumes all pointers have the same size and representation, which is mandated by POSIX. +We use memcpy to avoid compiler optimisation issues with the +syntax `*(void **)pp = p;` that violates the strict aliasing rule. ### `char *qe_strdup(const char *str);` @@ -210,12 +303,47 @@ Allocate a copy of a string. Return a pointer to allocated memory, aligned on the maximum alignment size. +### `int eb_search(EditBuffer *b, int dir, int flags, int start_offset, int end_offset, const char32_t *buf, int len, CSSAbortFunc *abort_func, void *abort_opaque, int *found_offset, int *found_end);` + +Search a buffer for contents. Return true if contents was found. + +* argument `b` a valid EditBuffer pointer + +* argument `dir` search direction: -1 for backward, 1 for forward + +* argument `flags` a combination of SEARCH_FLAG_xxx values + +* argument `start_offset` the starting offset in buffer + +* argument `end_offset` the maximum offset in buffer + +* argument `buf` a valid pointer to an array of `char32_t` + +* argument `len` the length of the array `buf` + +* argument `abort_func` a function pointer to test for abort request + +* argument `abort_opaque` an opaque argument for `abort_func` + +* argument `found_offset` a valid pointer to store the match + starting offset + +* argument `found_end` a valid pointer to store the match + ending offset + +Return non zero if the search was successful. Match starting and +ending offsets are stored to `start_offset` and `end_offset`. +Return `0` if search failed or `len` is zero. +Return `-1` if search was aborted. + ### `int append_slash(char *buf, int buf_size);` Append a trailing slash to a path if none there already. Return the updated path length. +Note: truncation cannot be detected reliably + ### `void canonicalize_path(char *buf, int buf_size, const char *path);` Normalize a path, removing redundant `.`, `..` and `/` parts. @@ -294,7 +422,7 @@ Note: call this function for a modifiable string. ### `size_t get_basename_offset(const char *path);` -Get the filename portion of a path. +Get the offset of the filename component of a path. Return the offset to the first character of the filename part of the path pointed to by string argument `path`. @@ -302,7 +430,7 @@ the path pointed to by string argument `path`. Extract the directory portion of a path. This leaves out the trailing slash if any. The complete path is -obtained by catenating `dirname` + `"/"` + `basename`. +obtained by concatenating `dirname` + `"/"` + `basename`. If the original path doesn't contain a directory name, `"."` is copied to `dest`. @@ -311,7 +439,7 @@ Return a pointer to the destination array. Note: truncation cannot be detected reliably. Note: the trailing slash is not removed if the directory is the -root directory: this make the behavior somewhat inconsistent, +root directory: this makes the behavior somewhat inconsistent, requiring more tests when reconstructing the full path. ### `const char *get_extension(const char *filename);` @@ -345,6 +473,25 @@ If there is no extension, return a pointer to the null terminator and the end of path. Leading dots are skipped, they are not considered part of an extension. +### `int get_str(const char **pp, char *buf, int buf_size, const char *stop);` + +Get a token from a string, stop on a set of characters and white-space. +Skip spaces before and after the token. Return the token length. + + +* argument `pp` the address of a valid pointer to the current position +in the source string + +* argument `buf` a pointer to a destination array. + +* argument `buf_size` the length of the destination array. + +* argument `stop` a valid string pointer containing separator characters. + +Return the length of the token stored into buf. + +Note: token truncation cannot be easily detected. + ### `int is_directory(const char *path);` Check if the string pointed to by `path` is the name of a @@ -390,6 +537,8 @@ array pointed to by `buf` of length `buf_size` bytes. Return a pointer to the destination array. +Note: truncation cannot be detected reliably + ### `int match_extension(const char *filename, const char *extlist);` Return `true` iff the filename extension appears in `|` separated @@ -405,6 +554,20 @@ Return `true` iff the command name invoked by the `#!` line pointed to by `p` matches one of the commands in `|` separated list pointed to by `list`. * both `#!/bin/perl` and `#!/bin/env perl` styles match list `"perl"` +### `int match_strings(const char *s1, const char *s2, int len);` + +Find the length of the common prefix, only count complete UTF-8 +sequences. + +* argument `s1` a valid string pointer + +* argument `s2` a valid string pointer + +* argument `len` the maximum number of bytes to compare. This count +is assumed to only include complete UTF-8 sequences. + +Return the length of the common prefix, between `0` and `len`. + ### `int memfind(const char *list, const char *s, int len);` Find a string fragment in a list of words separated by `|`. @@ -442,6 +605,9 @@ Return a pointer to the destination array. Note: truncation cannot be detected reliably. +Note: `strncat` has different semantics and does not check +for potential overflow of the destination array. + ### `char *pstrcpy(char *buf, int size, const char *str);` Copy the string pointed by `str` to the destination array `buf`, @@ -450,7 +616,7 @@ of length `size` bytes, truncating excess bytes. * argument `buf` destination array, must be a valid pointer. -* argument `size` length of destination array. +* argument `size` length of destination array in bytes. * argument `str` pointer to a source string, must be a valid pointer. @@ -458,8 +624,10 @@ Return a pointer to the destination array. Note: truncation cannot be detected reliably. -Note: this function does what `strncpy` should have done to be -useful. **NEVER use `strncpy`**. +Note: this function does what many programmers wrongly expect +`strncpy` to do. `strncpy` has different semantics and does not +null terminate the destination array in case of excess bytes. +**NEVER use `strncpy`**. ### `char *pstrncat(char *buf, int size, const char *s, int slen);` @@ -542,7 +710,7 @@ Compare 2 strings using special rules: ### `const char *qe_stristr(const char *s1, const char *s2);` -Find a string in another string, ignoring case. +Find an ASCII string in another ASCII string, ignoring case. * argument `s1` a valid pointer to the string in which to search for matches. @@ -568,9 +736,10 @@ value `def` otherwise. ### `void qe_strtolower(char *buf, int size, const char *str);` -Convert a string to lowercase using `qe_tolower` for each byte. +Convert an ASCII string to lowercase using `qe_tolower7` for +each byte. -* argument `buf` a valid pointer to a destination array. +* argument `buf` a valid pointer to a destination char array. * argument `size` the length of the destination array in bytes, @@ -589,19 +758,21 @@ Return the updated path length. Split the path pointed to by `pathname` into a directory part and a filename part. -Note: `dirname` will receive an empty string if `pathname` constains +Note: `dirname` will receive an empty string if `pathname` contains just a filename. ### `int strend(const char *str, const char *val, const char **ptr);` -Check if `val` is a suffix of `str`. In this case, a -pointer to the first character of the suffix in `str` is stored -into `ptr` provided `ptr` is not a null pointer. +Test if `val` is a suffix of `str`. + +if `val` is a suffix of `str`, a pointer to the first character +of the suffix in `str` is stored into `ptr` provided `ptr` is +not a null pointer. * argument `str` input string, must be a valid pointer. -* argument `val` suffix to test, must be a valid pointer. +* argument `val` suffix string, must be a valid pointer. * argument `ptr` updated to the suffix in `str` if there is a match. @@ -625,7 +796,7 @@ Leading dots are skipped, they are not considered part of an extension. ### `int stristart(const char *str, const char *val, const char **ptr);` -Test if `val` is a prefix of `str` (case independent). +Test if `val` is a prefix of `str` (case independent for ASCII). If there is a match, a pointer to the next character after the match in `str` is stored into `ptr` provided `ptr` is not null. @@ -638,6 +809,21 @@ to point after the prefix in `str` in there is a match. Return `true` if there is a match, `false` otherwise. +### `int strmatch_pat(const char *str, const char *pat, int start);` + +Check if the pattern `pat` matches `str` or a prefix of `str`. +Patterns use only `*` as a wildcard, to match any sequence of +characters. + +* argument `str` a valid string pointer. + +* argument `pat` a valid string pointer for the pattern to test. + +* argument `start` a non zero integer if the function should return +`1` for a partial match at the start of `str. + +Return `1` if there is a match, `0` otherwise. + ### `int strmatchword(const char *str, const char *val, const char **ptr);` Check if `val` is a word prefix of `str`. In this case, return @@ -671,9 +857,11 @@ Return a pointer to the first character of the match if found, ### `int strstart(const char *str, const char *val, const char **ptr);` -Check if `val` is a prefix of `str`. In this case, a -pointer to the first character after the prefix in `str` is -stored into `ptr` provided `ptr` is not a null pointer. +Test if `val` is a prefix of `str`. + +If `val` is a prefix of `str`, a pointer to the first character +after the prefix in `str` is stored into `ptr` provided `ptr` +is not a null pointer. If `val` is not a prefix of `str`, return `0` and leave `*ptr` unchanged. @@ -681,7 +869,7 @@ unchanged. * argument `str` input string, must be a valid pointer. -* argument `val` prefix to test, must be a valid pointer. +* argument `val` prefix string, must be a valid pointer. * argument `ptr` updated with a pointer past the prefix if found. @@ -709,8 +897,8 @@ Call this function with a constant string and the address of a `const char *`. ### `int strxcmp(const char *str1, const char *str2);` -Compare strings case independently, also ignoring spaces, dashes -and underscores. +Compare strings case independently (for ASCII), also ignoring +spaces, dashes and underscores. * argument `str1` a valid string pointer for the left operand. @@ -722,7 +910,7 @@ of `str1 <=> str2` ### `int strxfind(const char *list, const char *s);` Find a string in a list of words separated by `|`, ignoring case -and skipping `-` , `_` and spaces. +for ASCII and skipping `-` , `_` and spaces. An initial or trailing `|` do not match the empty string, but `||` does. * argument `list` a string of words separated by `|` characters. @@ -731,12 +919,14 @@ An initial or trailing `|` do not match the empty string, but `||` does. Return 1 if there is a match, 0 otherwise. +Note: this function only handles case insensitive matching for ASCII. + ### `int strxstart(const char *str, const char *val, const char **ptr);` -Test if `val` is a prefix of `str` (case independent and ignoring -`-`, `_` and spaces). If there is a match, a pointer to the next -character after the match in `str` is stored into `ptr`, provided -`ptr` is not null. +Test if `val` is a prefix of `str` (case independent for ASCII +and ignoring `-`, `_` and spaces). If there is a match, a pointer +to the next character after the match in `str` is stored into `ptr`, +provided `ptr` is not null. * argument `str` valid string pointer, @@ -746,3 +936,219 @@ character after the match in `str` is stored into `ptr`, provided to point after the prefix in `str` in there is a match. Return `true` if there is a match, `false` otherwise. + +### `int umemcmp(const char32_t *s1, const char32_t *s2, size_t count);` + +Compare two blocks of code points and return an integer indicative of +their relative order. + +* argument `s1` a valid wide string pointer. + +* argument `s2` a valid wide string pointer. + +* argument `count` the maximum number of code points to compare. + +Return `0` if the strings compare equal, a negative value if `s1` is +lexicographically before `s2` and a positive number otherwise. + +### `int ustr_get_identifier(char *buf, int buf_size, char32_t c, const char32_t *str, int i, int n);` + +Extract an ASCII identifier from a wide string into a char array. + +* argument `buf` a valid pointer to a destination array. + +* argument `buf_size` the length of the destination array. + +* argument `c` the first code point to copy. + +* argument `str` a valid wide string pointer. + +* argument `i` the offset of the first code point to copy. + +* argument `n` the offset to the end of the wide string. + +Return the length of the identifier present in the source string. + +Note: the return value can be larger than the destination array length. +In this case, the destination array contains a truncated string, null +terminated unless buf_size is <= 0. + +### `int ustr_get_identifier_lc(char *buf, int buf_size, char32_t c, const char32_t *str, int i, int n);` + +Extract an ASCII identifier from a wide string into a char array and +convert it to lowercase. + +* argument `buf` a valid pointer to a destination array. + +* argument `buf_size` the length of the destination array. + +* argument `c` the first code point to copy. + +* argument `str` a valid wide string pointer. + +* argument `i` the offset of the first code point to copy. + +* argument `n` the offset to the end of the wide string. + +Return the length of the identifier present in the source string. + +Note: the return value can be larger than the destination array length. +In this case, the destination array contains a truncated string, null +terminated unless buf_size is <= 0. + +### `int ustristart(const char32_t *str0, const char *val, int *lenp);` + +Test if `val` is a prefix of `str0`. Comparison is perform ignoring case. + +If `val` is a prefix of `str`, the length of the prefix is stored into +`*lenp`, provided `lenp` is not a null pointer, and return `1`. + +If `val` is not a prefix of `str`, return `0` and leave `*lenp` +unchanged. + + +* argument `str0` input string, must be a valid pointer to a null terminated code point array. + +* argument `val` prefix string, must be a valid string pointer. + +* argument `lenp` updated with the length of the prefix if found. + +Return `true` if there is a match, `false` otherwise. + +Note: val is assumed to be contain ASCII only. + +### `const char32_t *ustristr(const char32_t *str, const char *val);` + +Find a string of characters inside a string of code points ignoring case. + +* argument `str` a valid wide string pointer in which to search for matches. + +* argument `val` a valid string pointer to a subtring to search for. + +Return a pointer to the first code point of the match if found, +`NULL` otherwise. + +Note: val is assumed to be contain ASCII only. + +### `int ustrstart(const char32_t *str0, const char *val, int *lenp);` + +Test if `val` is a prefix of `str0`. + +If `val` is a prefix of `str`, the length of the prefix is stored into +`*lenp`, provided `lenp` is not a null pointer, and return `1`. + +If `val` is not a prefix of `str`, return `0` and leave `*lenp` +unchanged. + + +* argument `str0` input string, must be a valid pointer to a null terminated code point array. + +* argument `val` prefix string, must be a valid string pointer. + +* argument `lenp` updated with the length of the prefix if found. + +Return `true` if there is a match, `false` otherwise. + +### `const char32_t *ustrstr(const char32_t *str, const char *val);` + +Find a string of characters inside a string of code points. + +* argument `str` a valid wide string pointer in which to search for matches. + +* argument `val` a valid string pointer to a subtring to search for. + +Return a pointer to the first code point of the match if found, +`NULL` otherwise. + +### `char32_t utf8_decode(const char **pp);` + +Return the UTF-8 encoded code point at `*pp` and increment `*pp` +to point to the next code point. +Lax decoding is performed: +- stray trailing bytes 0x80..0xBF return a single byte +- overlong encodings, surrogates and special codes are accepted +- 32-bit codes are produced by 0xFE and 0xFF lead bytes if followed +by 5 trailing bytes + +### `char32_t utf8_decode_strict(const char **pp);` + +Return the UTF-8 encoded code point at `*pp` and increment `*pp` +to point to the next code point. +Strict decoding is performed, any encoding error returns INVALID_CHAR: +- invalid lead bytes 0x80..0xC1, 0xF8..0xFF +- overlong encodings +- low and high surrogate codes +- special codes 0xfffe and 0xffff +- code points beyond CHARCODE_MAX + +### `int utf8_get_word(char *buf, int buf_size, char32_t c, const char32_t *str, int i, int n);` + +Extract a word from a wide string into a char array. +Non ASCII code points are UTF-8 encoded. + +* argument `buf` a valid pointer to a destination array. + +* argument `buf_size` the length of the destination array. + +* argument `c` the first code point to copy. + +* argument `str` a valid wide string pointer. + +* argument `i` the offset of the first code point to copy. + +* argument `n` the offset to the end of the wide string. + +Return the length of the identifier present in the source string. + +Note: the return value can be larger than the destination array length. +In this case, the destination array contains a truncated string, null +terminated unless buf_size is <= 0. + +### `int utf8_prefix_len(const char *str1, const char *str2);` + +Return the length in bytes of an intial common prefix of `str1` and `str2`. + + +* argument `str1` must be a valid UTF-8 string pointer. + +* argument `str2` must be a valid UTF-8 string pointer. + +### `int utf8_strimatch_pat(const char *str, const char *pat, int start);` + +Check if the pattern `pat` matches `str` or a prefix of `str`, +using a case insensitive comparison. Patterns use only `*` as +a wildcard, to match any sequence of characters. +Accents are also ignored by this function. + +* argument `str` a valid string pointer. + +* argument `pat` a valid string pointer for the pattern to test. + +* argument `start` a non zero integer if the function should return +`1` for a partial match at the start of `str. + +Return `1` if there is a match, `0` otherwise. + +## Building QEmacs + +* Get the source code from github or an archive. +* Launch the custom configuration script `./configure`. You can list the +available options by typing `./configure --help`. +* Type `make` to compile qemacs and its associated tools. +* Type `make install` as root to install it in ** /usr/local **. + +## Authors + +QEmacs was started in 2000. The initial version was developped by +Fabrice Bellard and Charlie Gordon, who since then, has been maintaining +and extending it. + +## Licensing + +QEmacs is released under the MIT license. +(read the accompanying [LICENCE](LICENCE) file). + +## Contributing to QEmacs + +The QEmacs project is hosted on [github](https://github.com/qemacs/qemacs/) +Please file an issue for any questions or feature requests. Patch requests are welcome. diff --git a/search.c b/search.c index ff48786..18d8e72 100644 --- a/search.c +++ b/search.c @@ -1229,8 +1229,7 @@ void do_query_replace(EditState *s, const char *search_str, const char *replace_str, int argval) { /*@CMD query-replace - ### `query-replace(string FROM-STRING, string TO-STRING, - int DELIMITED=argval, int START=point, int END=end)` + ### `query-replace(string FROM-STRING, string TO-STRING, int DELIMITED=argval, int START=point, int END=end)` Replace some occurrences of FROM-STRING with TO-STRING. As each match is found, the user must type a character saying @@ -1267,8 +1266,7 @@ void do_replace_string(EditState *s, const char *search_str, const char *replace_str, int argval) { /*@CMD replace-string - ### `replace-string(string FROM-STRING, string TO-STRING, - int DELIMITED=argval, int START=point, int END=end)` + ### `replace-string(string FROM-STRING, string TO-STRING, int DELIMITED=argval, int START=point, int END=end)` Replace occurrences of FROM-STRING with TO-STRING. Preserve case in each match if `case-replace' and `case-fold-search' diff --git a/tools/scandoc.c b/tools/scandoc.c index 1cabeb2..a37b533 100644 --- a/tools/scandoc.c +++ b/tools/scandoc.c @@ -1,10 +1,26 @@ -/**************************************************************** -; -; SCANDOC.C (C) Copyright Charlie Gordon 2022 -; -; Extract documentation from C source files -; -;****************************************************************/ +/* + * SCANDOC, Extract documentation from C source files + * + * Copyright (c) 2022-2024 Charlie Gordon. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ /* A simple utility to preprocess C source code to extract documentation comments. These comments start with an @ just after @@ -24,6 +40,7 @@ #include #define NAME "scandoc" +#define VERSION "2024-05-20" static int verbose; @@ -334,7 +351,7 @@ static void add_doc(const char *filename, /* macro definition */ char *rep = sec + abbr_len + 1; /* store the expansion for the macro name */ - ap = add_abbrev(sec, abbr_len, rep, -1); + ap = add_abbrev(sec, abbr_len, rep, strlen(rep)); /* remove the macro name and equal sign from the section line */ xprintf(&sec, "%s", rep); } else @@ -347,6 +364,7 @@ static void add_doc(const char *filename, if (!strcmp(ap->abbr, "API") && proto_arg) { int pos, epos, elen; char *proto; + const char *p; /* skip storage class from prototype */ strstart(proto_arg, "static ", &proto_arg); @@ -378,7 +396,7 @@ static void add_doc(const char *filename, /* should implement more elaborate text reformating */ /* handle function macros */ - const char *p = text; + p = text; while ((p = strchr(p, '@')) != NULL) { int offset = p - text; if (strstart(p, "@argument", &p) || strstart(p, "@param", &p)) { @@ -434,6 +452,8 @@ static int scandoc(const char *filename, FILE *fp) { p = buf + skipwhite(buf); if (in_proto != 1) { char *p1 = buf; + int p1_len; + if (!strncmp(p, "/*@", 3)) { if (in_comment == 2) { /* flush pending doc comment */ @@ -447,7 +467,7 @@ static int scandoc(const char *filename, FILE *fp) { in_comment = 1; p1 = p; } - int p1_len = strlen(p1); + p1_len = strlen(p1); if (in_comment == 1) { char *e = strstr(p1, "*/"); if (e) { @@ -492,15 +512,25 @@ int main(int argc, char *argv[]) { char *filename = NULL; char *outname = NULL; int i; + int args_done = 0; for (i = 1; i < argc; i++) { char *arg = argv[i]; - if (*arg == '-') { + if (!args_done && *arg == '-') { if (arg[1] == '-') { - if (!strcmp(arg, "--help")) + if (!strcmp(arg, "--")) { + args_done = 1; + } else + if (!strcmp(arg, "--help")) { goto usage; - fprintf(stderr, "%s: bad option: -%s\n", NAME, arg); - return 1; + } else + if (!strcmp(arg, "--version")) { + printf("%s version %s\n", NAME, VERSION); + return 1; + } else { + fprintf(stderr, "%s: bad option: -%s\n", NAME, arg); + return 1; + } } while (*++arg) { switch (*arg) {