I agree with your assessment of function expressions (as I wrote at length above). But I wonder what your thoughts are on a uniform return variable syntax? The purely hypothetical one I suggested would enable easier usage of existing commands, like string:
search1 := string(FIND "foo bar" "o b")
search2 := string(REPLACE "foo" "bar" "foo baz")
search3 := string(REGEX MATCH "[0-9]+" "123foo")
search4 := string(REGEX MATCHALL "[0-9]+" "123foo456")
search5 := string(REGEX REPLACE "[0-9]+" "foo" "123 bar")
manip1 := string(CONCAT foo bar baz)
manip2 := string(JOIN ", " foo bar baz)
manip3 := string(TOLOWER "LOWER")
manip4 := string(TOUPPER "upper")
manip5 := string(LENGTH "foo")
manip6 := string(SUBSTRING "foo bar" 4 3)
manip7 := string(STRIP " foo ")
manip8 := string(GENEX_STRIP "foo $<CONFIG> bar")
manip9 := string(REPEAT "CMake is cool " 8)
cmp1 := string(COMPARE LESS "Alice" "Bob")
hash1 := string(MD5 "foo")
gen1 := string(ASCII 102 111 111)
gen2 := string(HEX "foo")
gen3 := string(CONFIGURE "@PROJECT_NAME@" @ONLY)
gen4 := string(MAKE_C_IDENTIFIER "h3110 \/\/0rld")
gen5 := string(RANDOM LENGTH 8)
gen6 := string(TIMESTAMP "%Y-%m-%dT%H:%M:%SZ" UTC)
gen7 := string(UUID TYPE SHA1)
Or list:
read1 := list(LENGTH my_list)
read2 := list(GET my_list 3)
read3 := list(JOIN my_list ", ")
read4 := list(SUBLIST my_list 3 2)
search1 := list(FIND my_list "foo")
# These will need to be special-cased to give
# out-of-place results when used with this syntax
mod1 := list(APPEND my_list foo)
mod2 := list(FILTER my_list EXCLUDE REGEX "^[0-9]")
mod3 := list(INSERT my_list 2 "foo")
(mod4a, mod4b) := list(POP_BACK my_list)
(mod5a, mod5b) := list(POP_FRONT my_list)
mod6 := list(PREPEND my_list "foo")
mod7 := list(REMOVE_ITEM my_list "foo")
mod8 := list(REMOVE_AT my_list 5)
mod9 := list(REMOVE_DUPLICATES my_list)
mod10 := list(TRANSFORM my_list REPLACE "make" "CMake")
ord1 := list(REVERSE my_list)
ord2 := list(SORT my_list)
Custom functions could be written and used like:
# Outputs are mapped to the LHS of := at the call site
# and the values of those names in the function's scope
# are assigned to the corresponding names in the caller's
# scope.
function(example1 IN x y OUT z)
if ("${x}${y}" STREQUAL "")
set(z "both are empty")
else ()
set(z "x = ${x}, y = ${y}")
endif ()
endfunction()
# This would even work with cmake_parse_arguments
function(example2 OUT z)
cmake_parse_arguments(ARG_ "" "X;Y" "" ${ARGN})
set(z "X = ${ARG_X}, Y = ${ARG_Y}")
endfunction()
var1 := example1("" "")
message(STATUS "${var1}") # prints '-- both are empty'
var2 := example1("foo" "bar")
message(STATUS "${var2}") # prints '-- x = foo, y = bar'
var3 := example2(X baz Y hello)
message(STATUS "${var3}") # prints '-- X = baz, Y = hello'
I’ll add that if it needed to be a command, then any of a number of alternative syntaxes would work
set(VARS var1 TO example1(foo bar))
cmake_language(OUT var1 CALL example1 foo bar)
# etc.
The more important change is the function() modification that promotes local variables to parent scope variables.