aboutsummaryrefslogtreecommitdiff
path: root/unit-tests/cond-op.mk
blob: 170355f6c0ad916b4df0f73c1771c34d6f6845ba (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# $NetBSD: cond-op.mk,v 1.10 2020/11/15 14:58:14 rillig Exp $
#
# Tests for operators like &&, ||, ! in .if conditions.
#
# See also:
#	cond-op-and.mk
#	cond-op-not.mk
#	cond-op-or.mk
#	cond-op-parentheses.mk

# In make, && binds more tightly than ||, like in C.
# If make had the same precedence for both && and ||, like in the shell,
# the result would be different.
# If || were to bind more tightly than &&, the result would be different
# as well.
.if !(1 || 1 && 0)
.  error
.endif

# If make were to interpret the && and || operators like the shell, the
# previous condition would be interpreted as:
.if (1 || 1) && 0
.  error
.endif

# The precedence of the ! operator is different from C though. It has a
# lower precedence than the comparison operators.  Negating a condition
# does not need parentheses.
#
# This kind of condition looks so unfamiliar that it doesn't occur in
# practice.
.if !"word" == "word"
.  error
.endif

# This is how the above condition is actually interpreted.
.if !("word" == "word")
.  error
.endif

# TODO: Demonstrate that the precedence of the ! and == operators actually
# makes a difference.  There is a simple example for sure, I just cannot
# wrap my head around it right now.  See the truth table generator below
# for an example that doesn't require much thought.

# This condition is malformed because the '!' on the right-hand side must not
# appear unquoted.  If any, it must be enclosed in quotes.
# In any case, it is not interpreted as a negation of an unquoted string.
# See CondParser_String.
.if "!word" == !word
.  error
.endif

# Surprisingly, the ampersand and pipe are allowed in bare strings.
# That's another opportunity for writing confusing code.
# See CondParser_String, which only has '!' in the list of stop characters.
.if "a&&b||c" != a&&b||c
.  error
.endif

# As soon as the parser sees the '$', it knows that the condition will
# be malformed.  Therefore there is no point in evaluating it.
#
# As of 2020-09-11, that part of the condition is evaluated nevertheless,
# since CondParser_Expr just requests the next token, without restricting
# the token to the expected tokens.  If the parser were to restrict the
# valid follow tokens for the token "0" to those that can actually produce
# a correct condition (which in this case would be comparison operators,
# TOK_AND, TOK_OR or TOK_RPAREN), the variable expression would not have
# to be evaluated.
#
# This would add a good deal of complexity to the code though, for almost
# no benefit, especially since most expressions and conditions are side
# effect free.
.if 0 ${ERR::=evaluated}
.  error
.endif
.if ${ERR:Uundefined} == evaluated
.  info After detecting a parse error, the rest is evaluated.
.endif

# Just in case that parsing should ever stop on the first error.
.info Parsing continues until here.

# Demonstration that '&&' has higher precedence than '||'.
.info A B C   =>   (A || B) && C   A || B && C   A || (B && C)
.for a in 0 1
.  for b in 0 1
.    for c in 0 1
.      for r1 in ${ ($a || $b) && $c :?1:0}
.        for r2 in ${ $a || $b && $c :?1:0}
.          for r3 in ${ $a || ($b && $c) :?1:0}
.            info $a $b $c   =>   ${r1}               ${r2}             ${r3}
.          endfor
.        endfor
.      endfor
.    endfor
.  endfor
.endfor

all:
	@:;