May 17, 2014

Boolean check in GNU make

I had found my way the other day to flip a boolean value in GNU make. One thing then was a given that the value being flipped was in fact a boolean. That is, a 0 or 1, and nothing else. After all, my solution there will work only if the value was boolean. So I'll have to verify that it's indeed the case.

As with the problem earlier, there are no make builtin that can do this alone. Shell, again, is an option, but I was inclined to get this done using bultins alone. So I decided take another stab at it:

define assert_boolean
$(and $(patsubst 0,,$($(1))),$(patsubst 1,,$($(1))),$(error $(1) must be boolean))
endef

my_boolean    := 0
not_a_boolean := foo

$(call assert_boolean,my_boolean)       # All is well
$(call assert_boolean,not_a_boolean)    # Flags error

It's a little involved, but I'll try to break it down.

The snippet above is written in canned sequence of commands using define directive. The define directive, although has semblance to a macro definition in C, actually declares a recursively expanding variable in make. This is the same kind of variable that you declare by assignment using the = (not :=) operator. The only practical and convenient distinction when defining using define is that the value of variable can span multiple lines, until it's terminated with endef. This type of variable has a property that it only stores its value verbatim and does not evaluate it in place. Evaluation of the variable only happens when it's referred, and in the referred context. That means value of the variable is taken in as as mere list of characters provided on the right hand side, and nothing, the $() dereferences in particular, on the right hand side gets evaluated at the point of declaration.

Then there's the call builtin. Simply put, what the call builtin does is to evaluate a variable, but with a twist: It lets us treat the variable as if it's a function (or a macro for that matter), to which we can pass arguments. Arguments passed to the variable are referred to by their position - like $(1), $(2) etc. Once called this way, all the call builtin does is do a parameter substitution in the variable's value and evaluates it. I.e. the $() indirections get evaluated. So $(call foo) is exactly same as $(foo), except that the former lets to pass arguments if we chose to.

The meat of the snippet has two patsubsts, each to substitute a single 0 or 1 in the given variable with an empty string. The and builtin does a logical AND of its arguments. So if the variable is a boolean, either of the patsubst will turn inputs to and false (empty), and the error won't fire. If, on the other hand, if it's not a boolean, neither of the patsubst will make an empty string, making all inputs to and true (non-empty), causing the error to complain.

Finally, we assert that the value stored in a variable is a boolean by calling assert_boolean variable (or function), and passing it the name of the variable whose value we want to assert, say, my_boolean. Once the call is made, all the $(1)s in assert_boolean are replaced by the string my_boolean. The patsubst builtins are in turn passed the value of my_boolean because of the $() indirection. And in the end, the error builtin will tell us if the value is not a boolean. Woof!

So, there. I have it!

No comments:

Post a Comment