1*d5e0a182SSimon J. Gerraty# $NetBSD: cond-short.mk,v 1.23 2023/11/19 22:32:44 rillig Exp $ 23841c287SSimon J. Gerraty# 33841c287SSimon J. Gerraty# Demonstrates that in conditions, the right-hand side of an && or || 43841c287SSimon J. Gerraty# is only evaluated if it can actually influence the result. 5e2eeea75SSimon J. Gerraty# This is called 'short-circuit evaluation' and is the usual evaluation 6e2eeea75SSimon J. Gerraty# mode in most programming languages. A notable exception is Ada, which 7e2eeea75SSimon J. Gerraty# distinguishes between the operators 'And', 'And Then', 'Or', 'Or Else'. 83841c287SSimon J. Gerraty# 906b9b3e0SSimon J. Gerraty# Before 2020-06-28, the right-hand side of an && or || operator was always 1006b9b3e0SSimon J. Gerraty# evaluated, which was wrong. In cond.c 1.69 and var.c 1.197 on 2015-10-11, 1106b9b3e0SSimon J. Gerraty# Var_Parse got a new parameter named 'wantit'. Since then it would have been 12*d5e0a182SSimon J. Gerraty# possible to skip evaluation of irrelevant expressions and only 1306b9b3e0SSimon J. Gerraty# parse them. They were still evaluated though, the only difference to 14*d5e0a182SSimon J. Gerraty# relevant expressions was that in the irrelevant 1512904384SSimon J. Gerraty# expressions, undefined variables were allowed. This allowed for conditions 1612904384SSimon J. Gerraty# like 'defined(VAR) && ${VAR:S,from,to,} != ""', which no longer produced an 179f45a3c8SSimon J. Gerraty# error message 'Malformed conditional', but the irrelevant expression was 189f45a3c8SSimon J. Gerraty# still evaluated. 1912904384SSimon J. Gerraty# 2012904384SSimon J. Gerraty# Since the initial commit on 1993-03-21, the manual page has been saying that 2112904384SSimon J. Gerraty# make 'will only evaluate a conditional as far as is necessary to determine', 2212904384SSimon J. Gerraty# but that was wrong. The code in cond.c 1.1 from 1993-03-21 looks good since 2312904384SSimon J. Gerraty# it calls Var_Parse(condExpr, VAR_CMD, doEval,&varSpecLen,&doFree), but the 249f45a3c8SSimon J. Gerraty# definition of Var_Parse did not call the third parameter 'doEval', as would 2512904384SSimon J. Gerraty# be expected, but instead 'err', accompanied by the comment 'TRUE if 2612904384SSimon J. Gerraty# undefined variables are an error'. This subtle difference between 'do not 2712904384SSimon J. Gerraty# evaluate at all' and 'allow undefined variables' led to the unexpected 2812904384SSimon J. Gerraty# evaluation. 29b0c40a00SSimon J. Gerraty# 30b0c40a00SSimon J. Gerraty# See also: 31b0c40a00SSimon J. Gerraty# var-eval-short.mk, for short-circuited variable modifiers 323841c287SSimon J. Gerraty 33b0c40a00SSimon J. Gerraty# The && operator: 343841c287SSimon J. Gerraty 353841c287SSimon J. Gerraty.if 0 && ${echo "unexpected and" 1>&2 :L:sh} 363841c287SSimon J. Gerraty.endif 373841c287SSimon J. Gerraty 383841c287SSimon J. Gerraty.if 1 && ${echo "expected and" 1>&2 :L:sh} 393841c287SSimon J. Gerraty.endif 403841c287SSimon J. Gerraty 413841c287SSimon J. Gerraty.if 0 && exists(nonexistent${echo "unexpected and exists" 1>&2 :L:sh}) 423841c287SSimon J. Gerraty.endif 433841c287SSimon J. Gerraty 443841c287SSimon J. Gerraty.if 1 && exists(nonexistent${echo "expected and exists" 1>&2 :L:sh}) 453841c287SSimon J. Gerraty.endif 463841c287SSimon J. Gerraty 473841c287SSimon J. Gerraty.if 0 && empty(${echo "unexpected and empty" 1>&2 :L:sh}) 483841c287SSimon J. Gerraty.endif 493841c287SSimon J. Gerraty 503841c287SSimon J. Gerraty.if 1 && empty(${echo "expected and empty" 1>&2 :L:sh}) 513841c287SSimon J. Gerraty.endif 523841c287SSimon J. Gerraty 533841c287SSimon J. Gerraty# "VAR U11" is not evaluated; it was evaluated before 2020-07-02. 543841c287SSimon J. Gerraty# The whole !empty condition is only parsed and then discarded. 553841c287SSimon J. GerratyVAR= ${VAR${:U11${echo "unexpected VAR U11" 1>&2 :L:sh}}} 563841c287SSimon J. GerratyVAR13= ${VAR${:U12${echo "unexpected VAR13" 1>&2 :L:sh}}} 573841c287SSimon J. Gerraty.if 0 && !empty(VAR${:U13${echo "unexpected U13 condition" 1>&2 :L:sh}}) 583841c287SSimon J. Gerraty.endif 593841c287SSimon J. Gerraty 603841c287SSimon J. GerratyVAR= ${VAR${:U21${echo "unexpected VAR U21" 1>&2 :L:sh}}} 613841c287SSimon J. GerratyVAR23= ${VAR${:U22${echo "expected VAR23" 1>&2 :L:sh}}} 623841c287SSimon J. Gerraty.if 1 && !empty(VAR${:U23${echo "expected U23 condition" 1>&2 :L:sh}}) 633841c287SSimon J. Gerraty.endif 643841c287SSimon J. GerratyVAR= # empty again, for the following tests 653841c287SSimon J. Gerraty 663841c287SSimon J. Gerraty# The :M modifier is only parsed, not evaluated. 673841c287SSimon J. Gerraty# Before 2020-07-02, it was wrongly evaluated. 683841c287SSimon J. Gerraty.if 0 && !empty(VAR:M${:U${echo "unexpected M pattern" 1>&2 :L:sh}}) 693841c287SSimon J. Gerraty.endif 703841c287SSimon J. Gerraty 713841c287SSimon J. Gerraty.if 1 && !empty(VAR:M${:U${echo "expected M pattern" 1>&2 :L:sh}}) 723841c287SSimon J. Gerraty.endif 733841c287SSimon J. Gerraty 743841c287SSimon J. Gerraty.if 0 && !empty(VAR:S,from,${:U${echo "unexpected S modifier" 1>&2 :L:sh}},) 753841c287SSimon J. Gerraty.endif 763841c287SSimon J. Gerraty 773841c287SSimon J. Gerraty.if 0 && !empty(VAR:C,from,${:U${echo "unexpected C modifier" 1>&2 :L:sh}},) 783841c287SSimon J. Gerraty.endif 793841c287SSimon J. Gerraty 803841c287SSimon J. Gerraty.if 0 && !empty("" == "" :? ${:U${echo "unexpected ? modifier" 1>&2 :L:sh}} :) 813841c287SSimon J. Gerraty.endif 823841c287SSimon J. Gerraty 833841c287SSimon J. Gerraty.if 0 && !empty(VAR:old=${:U${echo "unexpected = modifier" 1>&2 :L:sh}}) 843841c287SSimon J. Gerraty.endif 853841c287SSimon J. Gerraty 863841c287SSimon J. Gerraty.if 0 && !empty(1 2 3:L:@var@${:U${echo "unexpected @ modifier" 1>&2 :L:sh}}@) 873841c287SSimon J. Gerraty.endif 883841c287SSimon J. Gerraty 893841c287SSimon J. Gerraty.if 0 && !empty(:U${:!echo "unexpected exclam modifier" 1>&2 !}) 903841c287SSimon J. Gerraty.endif 913841c287SSimon J. Gerraty 922c3632d1SSimon J. Gerraty# Irrelevant assignment modifiers are skipped as well. 932c3632d1SSimon J. Gerraty.if 0 && ${1 2 3:L:@i@${FIRST::?=$i}@} 942c3632d1SSimon J. Gerraty.endif 952c3632d1SSimon J. Gerraty.if 0 && ${1 2 3:L:@i@${LAST::=$i}@} 962c3632d1SSimon J. Gerraty.endif 972c3632d1SSimon J. Gerraty.if 0 && ${1 2 3:L:@i@${APPENDED::+=$i}@} 982c3632d1SSimon J. Gerraty.endif 992c3632d1SSimon J. Gerraty.if 0 && ${echo.1 echo.2 echo.3:L:@i@${RAN::!=${i:C,.*,&; & 1>\&2,:S,., ,g}}@} 1002c3632d1SSimon J. Gerraty.endif 1012c3632d1SSimon J. Gerraty.if defined(FIRST) || defined(LAST) || defined(APPENDED) || defined(RAN) 1022c3632d1SSimon J. Gerraty. warning first=${FIRST} last=${LAST} appended=${APPENDED} ran=${RAN} 1032c3632d1SSimon J. Gerraty.endif 1042c3632d1SSimon J. Gerraty 105b0c40a00SSimon J. Gerraty# The || operator: 1063841c287SSimon J. Gerraty 1073841c287SSimon J. Gerraty.if 1 || ${echo "unexpected or" 1>&2 :L:sh} 1083841c287SSimon J. Gerraty.endif 1093841c287SSimon J. Gerraty 1103841c287SSimon J. Gerraty.if 0 || ${echo "expected or" 1>&2 :L:sh} 1113841c287SSimon J. Gerraty.endif 1123841c287SSimon J. Gerraty 1133841c287SSimon J. Gerraty.if 1 || exists(nonexistent${echo "unexpected or exists" 1>&2 :L:sh}) 1143841c287SSimon J. Gerraty.endif 1153841c287SSimon J. Gerraty 1163841c287SSimon J. Gerraty.if 0 || exists(nonexistent${echo "expected or exists" 1>&2 :L:sh}) 1173841c287SSimon J. Gerraty.endif 1183841c287SSimon J. Gerraty 1193841c287SSimon J. Gerraty.if 1 || empty(${echo "unexpected or empty" 1>&2 :L:sh}) 1203841c287SSimon J. Gerraty.endif 1213841c287SSimon J. Gerraty 1223841c287SSimon J. Gerraty.if 0 || empty(${echo "expected or empty" 1>&2 :L:sh}) 1233841c287SSimon J. Gerraty.endif 1243841c287SSimon J. Gerraty 1259f45a3c8SSimon J. Gerraty# Unreachable nested conditions are skipped completely as well. These skipped 1269f45a3c8SSimon J. Gerraty# lines may even contain syntax errors. This allows to skip syntactically 1279f45a3c8SSimon J. Gerraty# incompatible new features in older versions of make. 1283841c287SSimon J. Gerraty 1293841c287SSimon J. Gerraty.if 0 1303841c287SSimon J. Gerraty. if ${echo "unexpected nested and" 1>&2 :L:sh} 1313841c287SSimon J. Gerraty. endif 1323841c287SSimon J. Gerraty.endif 1333841c287SSimon J. Gerraty 1343841c287SSimon J. Gerraty.if 1 1353841c287SSimon J. Gerraty.elif ${echo "unexpected nested or" 1>&2 :L:sh} 1363841c287SSimon J. Gerraty.endif 1373841c287SSimon J. Gerraty 1383841c287SSimon J. Gerraty 1398c973ee2SSimon J. GerratyNUMBER= 42 1408c973ee2SSimon J. GerratyINDIR_NUMBER= ${NUMBER} 1418c973ee2SSimon J. GerratyINDIR_UNDEF= ${UNDEF} 1423841c287SSimon J. Gerraty 1438c973ee2SSimon J. Gerraty.if defined(NUMBER) && ${NUMBER} > 0 1443841c287SSimon J. Gerraty.else 1458c973ee2SSimon J. Gerraty. error 1463841c287SSimon J. Gerraty.endif 1472c3632d1SSimon J. Gerraty 1488c973ee2SSimon J. Gerraty# Starting with var.c 1.226 from from 2020-07-02, the following condition 1498c973ee2SSimon J. Gerraty# triggered a warning: "String comparison operator should be either == or !=". 1508c973ee2SSimon J. Gerraty# 1518c973ee2SSimon J. Gerraty# The left-hand side of the '&&' evaluated to false, which should have made 1528c973ee2SSimon J. Gerraty# the right-hand side irrelevant. 1538c973ee2SSimon J. Gerraty# 1548c973ee2SSimon J. Gerraty# On the right-hand side of the '&&', the expression ${INDIR_UNDEF} was 1558c973ee2SSimon J. Gerraty# defined and had the value '${UNDEF}', but the nested variable UNDEF was 1568c973ee2SSimon J. Gerraty# undefined. The right hand side "${INDIR_UNDEF}" still needed to be parsed, 1578c973ee2SSimon J. Gerraty# and in parse-only mode, the "value" of the parsed expression was the 1588c973ee2SSimon J. Gerraty# uninterpreted variable value, in this case '${UNDEF}'. And even though the 1598c973ee2SSimon J. Gerraty# right hand side of the '&&' should have been irrelevant, the two sides of 1608c973ee2SSimon J. Gerraty# the comparison were still parsed and evaluated. Comparing these two values 1618c973ee2SSimon J. Gerraty# numerically was not possible since the string '${UNDEF}' is not a number, 1628c973ee2SSimon J. Gerraty# so the comparison fell back to string comparison, which then complained 1638c973ee2SSimon J. Gerraty# about the '>' operator. 16406b9b3e0SSimon J. Gerraty# 16506b9b3e0SSimon J. Gerraty# This was fixed in cond.c 1.79 from 2020-07-09 by not evaluating irrelevant 16606b9b3e0SSimon J. Gerraty# comparisons. Instead, they are only parsed and then discarded. 16706b9b3e0SSimon J. Gerraty# 16806b9b3e0SSimon J. Gerraty# At that time, there was not enough debug logging to see the details in the 16906b9b3e0SSimon J. Gerraty# -dA log. To actually see it, add debug logging at the beginning and end of 17006b9b3e0SSimon J. Gerraty# Var_Parse. 1718c973ee2SSimon J. Gerraty.if defined(UNDEF) && ${INDIR_UNDEF} < ${NUMBER} 1728c973ee2SSimon J. Gerraty. error 1733841c287SSimon J. Gerraty.endif 1748c973ee2SSimon J. Gerraty# Adding a ':U' modifier to the irrelevant expression didn't help, as that 1758c973ee2SSimon J. Gerraty# expression was only parsed, not evaluated. The resulting literal string 1768c973ee2SSimon J. Gerraty# '${INDIR_UNDEF:U2}' was not numeric either, for the same reason as above. 1778c973ee2SSimon J. Gerraty.if defined(UNDEF) && ${INDIR_UNDEF:U2} < ${NUMBER} 1788c973ee2SSimon J. Gerraty. error 1793841c287SSimon J. Gerraty.endif 1802c3632d1SSimon J. Gerraty 181*d5e0a182SSimon J. Gerraty 182*d5e0a182SSimon J. Gerraty# Since cond.c 1.76 from 2020.06.28 and before var.c 1.225 from 2020.07.01, 183*d5e0a182SSimon J. Gerraty# the following snippet resulted in the error message 'Variable VAR is 184*d5e0a182SSimon J. Gerraty# recursive'. The condition '0' evaluated to false, which made the right-hand 185*d5e0a182SSimon J. Gerraty# side of the '&&' irrelevant. Back then, irrelevant condition parts were 186*d5e0a182SSimon J. Gerraty# still evaluated, but in "irrelevant mode", which allowed undefined variables 187*d5e0a182SSimon J. Gerraty# to occur in expressions. In this mode, the variable name 'VAR' was 188*d5e0a182SSimon J. Gerraty# unnecessarily evaluated, resulting in the expression '${VAR${:U1}}'. In 189*d5e0a182SSimon J. Gerraty# this expression, the variable name was 'VAR${:U1}', and of this variable 190*d5e0a182SSimon J. Gerraty# name, only the fixed part 'VAR' was evaluated, without the part '${:U1}'. 191*d5e0a182SSimon J. Gerraty# This partial evaluation led to the wrong error message about 'VAR' being 192*d5e0a182SSimon J. Gerraty# recursive. 193*d5e0a182SSimon J. GerratyVAR= ${VAR${:U1}} 194*d5e0a182SSimon J. Gerraty.if 0 && !empty(VAR) 195*d5e0a182SSimon J. Gerraty.endif 196*d5e0a182SSimon J. Gerraty 197*d5e0a182SSimon J. Gerraty 1988c973ee2SSimon J. Gerraty# Enclosing the expression in double quotes changes how that expression is 1998c973ee2SSimon J. Gerraty# evaluated. In irrelevant expressions that are enclosed in double quotes, 2008c973ee2SSimon J. Gerraty# expressions based on undefined variables are allowed and evaluate to an 2018c973ee2SSimon J. Gerraty# empty string. 20206b9b3e0SSimon J. Gerraty# 2038c973ee2SSimon J. Gerraty# The manual page stated from at least 1993 on that irrelevant conditions were 2048c973ee2SSimon J. Gerraty# not evaluated, but that was wrong. These conditions were evaluated, the 2058c973ee2SSimon J. Gerraty# only difference was that undefined variables in them didn't trigger an 2068c973ee2SSimon J. Gerraty# error. Since numeric conditions are quite rare, this subtle difference 2078c973ee2SSimon J. Gerraty# didn't catch much attention, as most other conditions such as pattern 2088c973ee2SSimon J. Gerraty# matches or equality comparisons worked fine and never produced error 2098c973ee2SSimon J. Gerraty# messages. 2108c973ee2SSimon J. Gerraty.if defined(UNDEF) && "${INDIR_UNDEF}" < ${NUMBER} 2118c973ee2SSimon J. Gerraty. error 2128c973ee2SSimon J. Gerraty.endif 2138c973ee2SSimon J. Gerraty 2148c973ee2SSimon J. Gerraty# Since the condition is relevant, the indirect undefined variable is 2158c973ee2SSimon J. Gerraty# evaluated as usual, resolving nested undefined expressions to an empty 2168c973ee2SSimon J. Gerraty# string. 21706b9b3e0SSimon J. Gerraty# 2188c973ee2SSimon J. Gerraty# Comparing an empty string numerically is not possible, however, make has an 2198c973ee2SSimon J. Gerraty# ugly hack in TryParseNumber that treats an empty string as a valid numerical 2208c973ee2SSimon J. Gerraty# value, thus hiding bugs in the makefile. 2218c973ee2SSimon J. Gerraty.if ${INDIR_UNDEF} < ${NUMBER} 2228c973ee2SSimon J. Gerraty# only due to the ugly hack 2233841c287SSimon J. Gerraty.else 2248c973ee2SSimon J. Gerraty. error 2253841c287SSimon J. Gerraty.endif 2262c3632d1SSimon J. Gerraty 2278c973ee2SSimon J. Gerraty# Due to the quotes around the left-hand side of the '<', the operand is 2288c973ee2SSimon J. Gerraty# marked as a string, thus preventing a numerical comparison. 2298c973ee2SSimon J. Gerraty# 2308c973ee2SSimon J. Gerraty# expect+1: Comparison with '<' requires both operands '' and '42' to be numeric 2318c973ee2SSimon J. Gerraty.if "${INDIR_UNDEF}" < ${NUMBER} 2328c973ee2SSimon J. Gerraty. info yes 2333841c287SSimon J. Gerraty.else 2348c973ee2SSimon J. Gerraty. info no 2353841c287SSimon J. Gerraty.endif 2362c3632d1SSimon J. Gerraty 2378c973ee2SSimon J. Gerraty# The right-hand side of '||' is irrelevant and thus not evaluated. 2388c973ee2SSimon J. Gerraty.if 1 || ${INDIR_NUMBER} < ${NUMBER} 2393841c287SSimon J. Gerraty.else 2408c973ee2SSimon J. Gerraty. error 2413841c287SSimon J. Gerraty.endif 2428c973ee2SSimon J. Gerraty 2438c973ee2SSimon J. Gerraty# The right-hand side of '||' is relevant and thus evaluated normally. 2448c973ee2SSimon J. Gerraty.if 0 || ${INDIR_NUMBER} < ${NUMBER} 2458c973ee2SSimon J. Gerraty. error 2468c973ee2SSimon J. Gerraty.endif 2478c973ee2SSimon J. Gerraty 2488c973ee2SSimon J. Gerraty# The right-hand side of '||' evaluates to an empty string, as the variable 2498c973ee2SSimon J. Gerraty# 'INDIR_UNDEF' is defined, therefore the modifier ':U2' has no effect. 2508c973ee2SSimon J. Gerraty# Comparing an empty string numerically is not possible, however, make has an 2518c973ee2SSimon J. Gerraty# ugly hack in TryParseNumber that treats an empty string as a valid numerical 2528c973ee2SSimon J. Gerraty# value, thus hiding bugs in the makefile. 2538c973ee2SSimon J. Gerraty.if 0 || ${INDIR_UNDEF:U2} < ${NUMBER} 2548c973ee2SSimon J. Gerraty# only due to the ugly hack 2558c973ee2SSimon J. Gerraty.else 2568c973ee2SSimon J. Gerraty. error 2578c973ee2SSimon J. Gerraty.endif 2588c973ee2SSimon J. Gerraty 2593841c287SSimon J. Gerraty 26006b9b3e0SSimon J. Gerraty# The right-hand side of the '&&' is irrelevant since the left-hand side 26106b9b3e0SSimon J. Gerraty# already evaluates to false. Before cond.c 1.79 from 2020-07-09, it was 26206b9b3e0SSimon J. Gerraty# expanded nevertheless, although with a small modification: undefined 26306b9b3e0SSimon J. Gerraty# variables may be used in these expressions without generating an error. 264e2eeea75SSimon J. Gerraty.if defined(UNDEF) && ${UNDEF} != "undefined" 265e2eeea75SSimon J. Gerraty. error 266e2eeea75SSimon J. Gerraty.endif 267e2eeea75SSimon J. Gerraty 26812904384SSimon J. Gerraty 26912904384SSimon J. Gerraty# Ensure that irrelevant conditions do not influence the result of the whole 27012904384SSimon J. Gerraty# condition. As of cond.c 1.302 from 2021-12-11, an irrelevant function call 2718c973ee2SSimon J. Gerraty# evaluated to true (see CondParser_FuncCall and CondParser_FuncCallEmpty), an 2728c973ee2SSimon J. Gerraty# irrelevant comparison evaluated to false (see CondParser_Comparison). 27312904384SSimon J. Gerraty# 27412904384SSimon J. Gerraty# An irrelevant true bubbles up to the outermost CondParser_And, where it is 27512904384SSimon J. Gerraty# ignored. An irrelevant false bubbles up to the outermost CondParser_Or, 27612904384SSimon J. Gerraty# where it is ignored. 27712904384SSimon J. Gerraty# 27812904384SSimon J. Gerraty# If the condition parser should ever be restructured, the bubbling up of the 27912904384SSimon J. Gerraty# irrelevant evaluation results might show up accidentally. Prevent this. 28012904384SSimon J. GerratyDEF= defined 28112904384SSimon J. Gerraty.undef UNDEF 28212904384SSimon J. Gerraty 28312904384SSimon J. Gerraty.if 0 && defined(DEF) 28412904384SSimon J. Gerraty. error 28512904384SSimon J. Gerraty.endif 28612904384SSimon J. Gerraty 28712904384SSimon J. Gerraty.if 1 && defined(DEF) 28812904384SSimon J. Gerraty.else 28912904384SSimon J. Gerraty. error 29012904384SSimon J. Gerraty.endif 29112904384SSimon J. Gerraty 29212904384SSimon J. Gerraty.if 0 && defined(UNDEF) 29312904384SSimon J. Gerraty. error 29412904384SSimon J. Gerraty.endif 29512904384SSimon J. Gerraty 29612904384SSimon J. Gerraty.if 1 && defined(UNDEF) 29712904384SSimon J. Gerraty. error 29812904384SSimon J. Gerraty.endif 29912904384SSimon J. Gerraty 30012904384SSimon J. Gerraty.if 0 || defined(DEF) 30112904384SSimon J. Gerraty.else 30212904384SSimon J. Gerraty. error 30312904384SSimon J. Gerraty.endif 30412904384SSimon J. Gerraty 30512904384SSimon J. Gerraty.if 1 || defined(DEF) 30612904384SSimon J. Gerraty.else 30712904384SSimon J. Gerraty. error 30812904384SSimon J. Gerraty.endif 30912904384SSimon J. Gerraty 31012904384SSimon J. Gerraty.if 0 || defined(UNDEF) 31112904384SSimon J. Gerraty. error 31212904384SSimon J. Gerraty.endif 31312904384SSimon J. Gerraty 31412904384SSimon J. Gerraty.if 1 || defined(UNDEF) 31512904384SSimon J. Gerraty.else 31612904384SSimon J. Gerraty. error 31712904384SSimon J. Gerraty.endif 31812904384SSimon J. Gerraty 31912904384SSimon J. Gerraty 3203841c287SSimon J. Gerratyall: 321