diff --git a/Makefile.am b/Makefile.am index 7fd4174b..34038cb7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -36,7 +36,8 @@ EXTRA_DIST = TODO BUGS BUGFORM FAQ FAQ.I18N INSTALL \ INSTALL-WMAKER README.definable-cursor \ The-perfect-Window-Maker-patch.txt \ README COPYING.WTFPL autogen.sh \ - email-clients.txt checkpatch.pl update-changelog.pl + email-clients.txt checkpatch.pl update-changelog.pl \ + script/nested-func-to-macro.sh if USE_LCOV coverage-reset: diff --git a/configure.ac b/configure.ac index 7097e9d9..d7ccdaec 100644 --- a/configure.ac +++ b/configure.ac @@ -158,6 +158,12 @@ AS_IF([test "x$debug" = "xyes"], AX_CFLAGS_GCC_OPTION([-Wno-deprecated-declarations]) ]) + +dnl Support for Nested Functions by the compiler +dnl ============================================ +WM_PROG_CC_NESTEDFUNC + + dnl Posix thread dnl ================= AX_PTHREAD @@ -905,6 +911,9 @@ AS_IF([test "x$debug" = "xyes"], [AS_ECHO(["Debug enabled: CFLAGS = $CFLAGS"]) ]) echo +AS_IF([test "x$wm_cv_prog_cc_nestedfunc" != "xyes"], + [AC_MSG_WARN([[Your compiler does not support Nested Function, work-around enabled]])]) + dnl WM_PRINT_REDCRAP_BUG_STATUS AS_IF([test "x$enable_jpeg" = xno], [dnl diff --git a/m4/wm_prog_cc_c11.m4 b/m4/wm_prog_cc_c11.m4 index fbdbffb7..cf846998 100644 --- a/m4/wm_prog_cc_c11.m4 +++ b/m4/wm_prog_cc_c11.m4 @@ -46,3 +46,41 @@ AS_CASE([$wm_cv_prog_cc_c11], [no|native], [], [CFLAGS="$CFLAGS $wm_cv_prog_cc_c11"]) ]) + + +# WM_PROG_CC_NESTEDFUNC +# --------------------- +# +# Check if the compiler support declaring Nested Functions (that means +# declaring a function inside another function). +# +# If the compiler does not support them, then the Automake conditional +# USE_NESTED_FUNC will be set to false, in which case the Makefile will +# use the script 'scripts/nested-func-to-macro.sh' to generate a modified +# source with the nested function transformed into a Preprocessor Macro. +AC_DEFUN_ONCE([WM_PROG_CC_NESTEDFUNC], +[AC_CACHE_CHECK([if compiler supports nested functions], [wm_cv_prog_cc_nestedfunc], + [AC_COMPILE_IFELSE( + [AC_LANG_SOURCE([[ +int main(int narg, char **argv) +{ + int local_variable; + + int nested_function(int argument) + { + /* Checking we have access to upper level's scope, otherwise it is of no use */ + return local_variable + argument; + } + + /* To avoid a warning for unused parameter, that may falsely fail */ + (void) argv; + + /* Initialise using the parameter to main so the compiler won't be tempted to optimise too much */ + local_variable = narg + 1; + + return nested_function(2); +}]]) ], + [wm_cv_prog_cc_nestedfunc=yes], + [wm_cv_prog_cc_nestedfunc=no]) ]) +AM_CONDITIONAL([USE_NESTED_FUNC], [test "x$wm_cv_prog_cc_nestedfunc" != "xno"])dnl +]) diff --git a/script/nested-func-to-macro.sh b/script/nested-func-to-macro.sh new file mode 100755 index 00000000..416400fd --- /dev/null +++ b/script/nested-func-to-macro.sh @@ -0,0 +1,216 @@ +#!/bin/sh +########################################################################### +# +# Window Maker window manager +# +# Copyright (c) 2014 Christophe CURIS +# Copyright (c) 2014 Window Maker Team +# +# 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +########################################################################### +# +# nested-func-to-macro.sh: +# from a C source file specified with "-i", convert the functions +# specified with "-f" into preprocessor macros ("#define") and save the +# result as the file specified with "-o" +# +# The goal is to process nested functions (functions defined inside other +# functions), because some compilers do not support that, despite the +# advantages against macros: +# - there is no side effect on arguments, like what macros does; +# - the compiler can check the type used for arguments; +# - the compiler can decide wether it is best to inline them or not; +# - they are better handled by text editors, mainly for indentation but +# also because there is no more '\' at end of lines; +# - generaly, error message from the compiler are a lot clearer. +# +# As opposed to simple static functions, there is a strong benefit because +# they can access the local variables of the function in which they are +# defined without needing extra arguments that may get complicated. +# +# These added values are important for developpement because they help keep +# code simple (and so maintainable and with lower bug risk). +# +# Because this script convert them to macros (only if 'configure' detected +# that the compiler does not support nested functions, see the macro +# WM_PROG_CC_NESTEDFUNC), there are a few constraints when writing such +# nested functions in the code: +# +# - you cannot use the function's address (example: callback), but that +# would be a bad idea anyway (in best case there's a penalty issue, in +# worst case it can crash the program); +# +# - you should be careful on what you're doing with the arguments of the +# function, otherwise the macro's side effects will re-appear; +# +# - you may need some extra '{}' when calling the function in an +# if/for/while/... construct, because of the difference between a function +# call and the macro that will be replaced (the macro will contain its own +# pair of '{}' which will be followed by the ';' you use for the function +# call; +# +# - the prototype of the function must be on a single line, it must not +# spread across multiple lines or replace will fail; +# +# - you should follow the project's coding style, as hacky stuff may +# make the script generate crap. And you don't want that to happen. +# +########################################################################### +# +# Please note that this script is writen in sh+awk on purpose: this script +# is gonna be run on the machine of the person who is trying to compile +# WindowMaker, and as such we cannot be sure to find any scripting language +# in a known version and that works (python/ruby/tcl/perl/php/you-name-it). +# +# So for portability, we stick to the same sh+awk constraint as Autotools +# to limit the problem, see for example: +# http://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.69/html_node/Portable-Shell.html +# +########################################################################### + +# Report an error on stderr and exit with status 1 to tell make could not work +arg_error() { + echo "$0: $@" >&2 + exit 1 +} + +# print help and exit with success status +print_help() { + echo "$0: convert nested functions into macros in C source" + echo "Usage: $0 [options...] input_file" + echo "valid options are:" + echo " -f name : add 'name' to the list of function to process" + echo " -o file : set output file" + exit 0 +} + +# Extract command line arguments +while [ $# -gt 0 ]; do + case $1 in + -f) + shift + echo "$1" | grep -q '^[A-Z_a-z][A-Z_a-z0-9]*$' || arg_error "function name \"$1\" is not valid" + function_list="$function_list $1" + ;; + + -h|-help|--help) print_help ;; + -o) shift ; output_file="$1" ;; + -*) arg_error "unknow option '$1'" ;; + + *) + [ "x$input_file" = "x" ] || arg_error "only 1 input file can be specified, not \"$input_file\" and \"$1\"" + input_file="$1" + ;; + esac + shift +done + +# Check consistency of command-line +[ "x$input_file" = "x" ] && arg_error "no source file given" +[ "x$function_list" = "x" ] && arg_error "no function name were given, nothing to do" +[ "x$output_file" = "x" ] && arg_error "no output file name specified" + +[ -r "$input_file" ] || arg_error "source file \"$input_file\" is not readable" + +# Declare a function that takes care of converting the function code into a +# macro definition. All the code is considered part of the C function's body +# until we have matched the right number of {} pairs +awk_function_handler=' +function replace_definition(func_name) +{ + # Isolate the list of arguments from the rest of the line + # This code could be updated to handle arg list over multiple lines, but + # it would add unnecessary complexity because a function that big should + # probably be global static + arg_start = index($0, "("); + arg_end = index($0, ")"); + argsubstr = substr($0, arg_start + 1, arg_end - arg_start - 1); + remain = substr($0, arg_end); + + $0 = "#define " func_name "(" + + # Remove the types from the list of arguments + split(argsubstr, arglist, /,/); + separator = ""; + for (i = 1; i <= length(arglist); i++) { + argname = substr(arglist[i], match(arglist[i], /[A-Z_a-z][A-Z_a-z0-9]*$/)); + $0 = $0 separator argname; + separator = ", "; + } + delete arglist; + $0 = $0 remain; + + # Count the number of pairs of {} and take next line until we get our matching count + is_first_line = 1; + nb_pair = 0; + while (1) { + # Count the opening braces + split($0, dummy, /\{/); + nb_pair = nb_pair + (length(dummy) - 1); + delete dummy; + + # Count the closing braces + split($0, dummy, /\}/); + nb_pair = nb_pair - (length(dummy) - 1); + delete dummy; + + # If we found the end of the function, stop now + if (nb_pair <= 0 && !is_first_line) { + # Note that we count on awk that is always executing the match-all + # pattern to print the current line in the $0 pattern + break; + } + + # Otherwise, print current line with the macro continuation mark and grab + # next line to process it + $0 = $0 " \\"; + print; + getline; + is_first_line = 0; + } + + # We mark the current macro as defined so it can be undefined at the end + func_defined[func_name] = 1; +}' + +# Build the list of awk pattern matching for each function: +# The regular expression matches function definition by the name of the function +# that must be preceeded by at least one keyword (likely the return type), but +# nothing like a math operator or other esoteric sign +for function in $function_list ; do + awk_function_handler="$awk_function_handler +/^[\\t ]*([A-Za-z][A-Za-z_0-9]*[\\t ])+${function}[\\t ]*\\(/ { + replace_definition(\"${function}\"); +}" +done + +# Finishing, undefine the macro at the most appropriate place we can easily +# guess +awk_function_handler="$awk_function_handler +/^\\}/ { + # If we are at the end of a function definition, undefine all macros that + # have been defined so far + for (func_name in func_defined) { + print \"#undef \" func_name; + delete func_defined[func_name]; + } +} +# Print all other lines as-is +{ print } +" + +# Find the specified functions and transform them into macros +awk "$awk_function_handler" < "$input_file" > "$output_file" diff --git a/src/Makefile.am b/src/Makefile.am index 41a7e83e..6c20e63a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -49,7 +49,6 @@ wmaker_SOURCES = \ main.h \ menu.c \ menu.h \ - misc.c \ misc.h \ monitor.c \ monitor.h \ @@ -123,6 +122,20 @@ if WM_OSDEP_GENERIC wmaker_SOURCES += osdep_stub.c endif +if USE_NESTED_FUNC +wmaker_SOURCES += misc.c +else +nodist_wmaker_SOURCES = misc.hack_nf.c + +CLEANFILES = $(nodist_wmaker_SOURCES) + +misc.hack_nf.c: misc.c $(top_srcdir)/script/nested-func-to-macro.sh + $(top_srcdir)/script/nested-func-to-macro.sh \ + $(srcdir)/misc.c -o $(builddir)/misc.hack_nf.c \ + -f "append_string" -f "append_modifier" +endif + + AM_CFLAGS = AM_CPPFLAGS = \ diff --git a/src/misc.c b/src/misc.c index 51eb1ee6..3b9299d4 100644 --- a/src/misc.c +++ b/src/misc.c @@ -763,8 +763,10 @@ char *GetShortcutKey(WShortKey key) char buffer[256]; char *wr; - void append_string(const char *string) + void append_string(const char *text) { + const char *string = text; + while (*string) { if (wr >= buffer + sizeof(buffer) - 1) break; @@ -774,10 +776,11 @@ char *GetShortcutKey(WShortKey key) void append_modifier(int modifier_index, const char *fallback_name) { - if (wPreferences.modifier_labels[modifier_index]) + if (wPreferences.modifier_labels[modifier_index]) { append_string(wPreferences.modifier_labels[modifier_index]); - else + } else { append_string(fallback_name); + } } key_name = XKeysymToString(XkbKeycodeToKeysym(dpy, key.keycode, 0, 0));