Programming Savvy and Arithmetic Expressions
Programming Savvy and Arithmetic Expressions
P
rogrammingS
avvyA
ndA
rithmeticExpressions
Dynamiclinkageisapowerfulprogrammingtechniqueinitsownright.Rather
thanwritingafewfunctions,eachwithabigswitchtohandlemanyspecialcases,
wecanwritemanysmallfunctions,oneforeachcase,andarrangefortheproper
functiontobecalledbydynamiclinkage.Thisoftensimplifiesaroutinejobandit
usuallyresultsincodethatcanbeextendedeasily.
Asanexamplewewillwriteasmallprogramtoreadandevaluatearithmetic
expressionsconsistingoffloatingpointnumbers,parenthesesandtheusualoperatorsfor
addition,subtraction,andsoon.Normallywewouldusethecompilergeneratortoolslexand
yacctobuildthatpartoftheprogramwhichrecognizesanarithmeticexpression.Thisbookis
notaboutcompilerbuilding,however,sojustthis
oncewewillwritethiscodeourselves.
1.1TheMainLoop
Themainloopoftheprogramreadsalinefromstandardinput,initializesthingsso
thatnumbersandoperatorscanbeextractedandwhitespaceisignored,callsupa
functiontorecognizeacorrectarithmeticexpressionandsomehowstoreit,and
finallyprocesseswhateverwasstored.Ifthingsgowrong,wesimplyreadthe
nextinputline.Hereisthemainloop:
#include<setjmp.h>
staticenumtokenstoken;/*currentinputsymbol*/
staticjmp_bufonError;
intmain(void)
{volatileinterrors=0;
charbuf[BUFSIZ];
if(setjmp(onError))
++errors;
while(gets(buf))
if(scan(buf))
{void*e=sum();
if(token)
error("trashaftersum");
process(e);
delete(e);
}
returnerrors>0;
}
voiderror(constchar*fmt,...)
{va_listap;
va_start(ap,fmt);
vfprintf(stderr,fmt,ap),putc(’\n’,stderr);
va_end(ap);
longjmp(onError,1);
2
}
Theerrorrecoverypointisdefinedwithsetjmp().Iferror()iscalledsomewherein
theprogram,longjmp()continuesexecutionwithanotherreturnfromsetjmp().In
thiscase,theresultisthevaluepassedtolongjmp(),theerroriscounted,andthe
nextinputlineisread.Theexitcodeoftheprogramreportsifanyerrorswere
Encountered.
1.2TheScanner
Inthemainloop,onceaninputlinehasbeenreadintobuf[],itispassedtoscan(),
whichforeachcallplacesthenextinputsymbolintothevariabletoken.Attheend
ofalinetokeniszero:
#include<ctype.h>
#include<errno.h>
#include<stdlib.h>
#include"parse.h"/*definesNUMBER*/
staticdoublenumber;/*ifNUMBER:numericalvalue*/
staticenumtokensscan(constchar*buf)
/*returntoken=nextinputsymbol*/
{staticconstchar*bp;
if(buf)
bp=buf;/*newinputline*/
while(isspace(*bp))
++bp;
if(isdigit(*bp)||*bp==’.’)
{errno=0;
token=NUMBER,number=strtod(bp,(char**)&bp);
if(errno==ERANGE)
error("badvalue:%s",strerror(errno));
}
else
token=*bp?*bp++:0;
returntoken;
}
Wecallscan()withtheaddressofaninputlineorwithanullpointertocontinue
workonthepresentline.Whitespaceisignoredandforaleadingdigitordecimal
pointweextractafloatingpointnumberwiththeANSI-Cfunctionstrtod().Any
othercharacterisreturnedasis,andwedonotadvancepastanullbyteattheend
oftheinputbuffer.
1.3TheRecognizer
Theresultofscan()isstoredintheglobalvariabletoken—thissimplifiesthe
recognizer.Ifwehavedetectedanumber,wereturntheuniquevalueNUMBER
andwemaketheactualvalueavailableintheglobalvariablenumber.
Atthetoplevelexpressionsarerecognizedbythefunctionsum()whichinternally
callsonscan()andreturnsarepresentationthatcanbemanipulatedbyprocess()
3
andreclaimedbydelete().
Ifwedonotuseyaccwerecognizeexpressionsbythemethodofrecursive
descentwheregrammaticalrulesaretranslatedintoequivalentCfunctions.For
example:asumisaproduct,followedbyzeroormoregroups,eachconsistingof
anadditionoperatorandanotherproduct.Agrammaticalrulelike
sum:product{+|—product}...
istranslatedintoaCfunctionlike
voidsum(void)
{
product();
for(;;)
{switch(token){
case’+’:
case’—’:
scan(0),product();continue;
}
return;
}
}
ThereisaCfunctionforeachgrammaticalrulesothatrulescancalleachother.
Alternativesaretranslatedintoswitchorifstatements,iterationsinthegrammar
produceloopsinC.Theonlyproblemisthatwemustavoidinfiniterecursion.
tokenalwayscontainsthenextinputsymbol.Ifwerecognizeit,wemustcall
scan(0)toadvanceintheinputandstoreanewsymbolintoken.
1.4TheProcessor
Howdoweprocessanexpression?Ifweonlywanttoperformsimplearithmetic
onnumericalvalues,wecanextendtherecognitionfunctionsandcomputethe
resultassoonaswerecognizetheoperatorsandtheoperands:sum()would
expectadoubleresultfromeachcalltoproduct(),performadditionorsubtraction
assoonaspossible,andreturntheresult,againasadoublefunctionvalue.
Ifwewanttobuildasystemthatcanhandlemorecomplicatedexpressionswe
needtostoreexpressionsforlaterprocessing.Inthiscase,wecannotonlyperform
arithmetic,butwecanpermitdecisionsandconditionallyevaluateonlypartof
anexpression,andwecanusestoredexpressionsasuserfunctionswithinother
expressions.Allweneedisareasonablygeneralwaytorepresentanexpression.
Theconventionaltechniqueistouseabinarytreeandstoretokenineachnode:
structNode{
enumtokenstoken;
structNode*left,*right;
};
Thisisnotveryflexible,however.Weneedtointroduceauniontocreateanode
inwhichwecanstoreanumericalvalueandwewastespaceinnodesrepresenting
unaryoperators.Additionally,process()anddelete()willcontainswitchstatementswhich
growwitheverynewtokenwhichweinvent.
4
1.5InformationHiding
Applyingwhatwehavelearnedthusfar,wedonotrevealthestructureofanodeat
all.Instead,weplacesomedeclarationsinaheaderfilevalue.h:
constvoid*Add;
...
void*new(constvoid*type,...);
voidprocess(constvoid*tree);
voiddelete(void*tree);
Nowwecancodesum()asfollows:
#include"value.h"
staticvoid*sum(void)
{void*result=product();
constvoid*type;
for(;;)
{switch(token){
case’+’:
type=Add;
break;
case’—’:
type=Sub;
break;
default:
returnresult;
}
scan(0);
result=new(type,result,product());
}
}
product()hasthesamearchitectureassum()andcallsonafunctionfactor()to
recognizenumbers,signs,andasumenclosedinparentheses:
staticvoid*sum(void);
staticvoid*factor(void)
{void*result;
switch(token){
case’+’:
scan(0);
returnfactor();
1.6DynamicLinkage
case’—’:
scan(0);
returnnew(Minus,factor());
default:
error("badfactor:’%c’0x%x",token,token);
caseNUMBER:
result=new(Value,number);
break;
case’(’:
5
scan(0);
result=sum();
if(token!=’)’)
error("expecting)");
}
scan(0);
returnresult;
}
Especiallyinfactor()weneedtobeverycarefultomaintainthescannerinvariant:
tokenmustalwayscontainthenextinputsymbol.Assoonastokenisconsumed
weneedtocallscan(0).
1.6DynamicLinkage
Therecognizeriscomplete.value.hcompletelyhidestheevaluatorforarithmeticexpressions
andatthesametimespecifieswhatwehavetoimplement.
new()takesadescriptionsuchasAddandsuitableargumentssuchaspointersto
theoperandsoftheadditionandreturnsapointerrepresentingthesum:
structType{
void*(*new)(va_listap);
double(*exec)(constvoid*tree);
void(*delete)(void*tree);
};
void*new(constvoid*type,...)
{va_listap;
void*result;
assert(type&&((structType*)type)—>new);
va_start(ap,type);
result=((structType*)type)—>new(ap);
*(conststructType**)result=type;
va_end(ap);
returnresult;
}
Weusedynamiclinkageandpassthecalltoanode-specificroutinewhich,inthe
caseofAdd,hastocreatethenodeandenterthetwopointers:
structBin{
constvoid*type;
void*left,*right;
};
staticvoid*mkBin(va_listap)
{structBin*node=malloc(sizeof(structBin));
assert(node);
node—>left=va_arg(ap,void*);
node—>right=va_arg(ap,void*);
returnnode;
}
6
NotethatonlymkBin()knowswhatnodeitcreates.Allwerequireisthatthevariousnodes
startwithapointerforthedynamiclinkage.Thispointerisenteredby
new()sothatdelete()canreachitsnode-specificfunction:
voiddelete(void*tree)
{
assert(tree&&*(structType**)tree
&&(*(structType**)tree)—>delete);
(*(structType**)tree)—>delete(tree);
}
staticvoidfreeBin(void*tree)
{
delete(((structBin*)tree)—>left);
delete(((structBin*)tree)—>right);
free(tree);
}
Dynamiclinkageelegantlyavoidscomplicatednodes..new()createsprecisely
therightnodeforeachtypedescription:binaryoperatorshavetwodescendants,
unaryoperatorshaveone,andavaluenodeonlycontainsthevalue.delete()isa
verysimplefunctionbecauseeachnodehandlesitsowndestruction:binaryoperatorsdelete
twosubtreesandfreetheirownnode,unaryoperatorsdeleteonlyone
subtree,andavaluenodewillonlyfreeitself.Variablesorconstantscaneven
remainbehind—theysimplywoulddonothinginresponsetodelete().
1.7APostfixWriter
Sofarwehavenotreallydecidedwhatprocess()isgoingtodo.Ifwewanttoemit
apostfixversionoftheexpression,wewouldaddacharacterstringtothestruct
Typetoshowtheactualoperatorandprocess()wouldarrangeforasingleoutput
lineindentedbyatab:
voidprocess(constvoid*tree)
{
putchar(’\t’);
exec(tree);
putchar(’\n’);
}
exec()handlesthedynamiclinkage:
staticvoidexec(constvoid*tree)
{
assert(tree&&*(structType**)tree
&&(*(structType**)tree)—>exec);
(*(structType**)tree)—>exec(tree);
}
andeverybinaryoperatorisemittedwiththefollowingfunction:
staticvoiddoBin(constvoid*tree)
{
exec(((structBin*)tree)—>left);
exec(((structBin*)tree)—>right);
7
printf("%s",(*(structType**)tree)—>name);
}
Thetypedescriptionstieeverythingtogether:
staticstructType_Add={"+",mkBin,doBin,freeBin};
staticstructType_Sub={"—",mkBin,doBin,freeBin};
constvoid*Add=&_Add;
constvoid*Sub=&_Sub;
Itshouldbeeasytoguesshowanumericalvalueisimplemented.Itisrepresented
asastructurewithadoubleinformationfield:
structVal{
constvoid*type;
doublevalue;
};
staticvoid*mkVal(va_listap)
{structVal*node=malloc(sizeof(structVal));
assert(node);
node—>value=va_arg(ap,double);
returnnode;
}
Processingconsistsofprintingthevalue:
staticvoiddoVal(constvoid*tree)
{
printf("%g",((structVal*)tree)—>value);
}
Wearedone—thereisnosubtreetodelete,sowecanusethelibraryfunction
free()directlytodeletethevaluenode:
staticstructType_Value={"",mkVal,doVal,free};
constvoid*Value=&_Value;
AunaryoperatorsuchasMinusisleftasanexercise.
1.8Arithmetic
Ifwewanttodoarithmetic,welettheexecutefunctionsreturndoublevaluesto
beprintedinprocess():
staticdoubleexec(constvoid*tree)
{
return(*(structType**)tree)—>exec(tree);
}
voidprocess(constvoid*tree)
{
printf("\t%g\n",exec(tree));
}
Foreachtypeofnodeweneedoneexecutionfunctionwhichcomputesandreturns
thevalueforthenode.Herearetwoexamples:
staticdoubledoVal(constvoid*tree)
{
return((structVal*)tree)—>value;
8
}
staticdoubledoAdd(constvoid*tree)
{
returnexec(((structBin*)tree)—>left)+
exec(((structBin*)tree)—>right);
}
staticstructType_Add={mkBin,doAdd,freeBin};
staticstructType_Value={mkVal,doVal,free};
constvoid*Add=&_Add;
constvoid*Value=&_Value;
1.9InfixOutput
Perhapsthehighlightofprocessingarithmeticexpressionsistoprintthemwitha
minimalnumberofparentheses.Thisisusuallyabittricky,dependingonwhois
responsibleforemittingtheparentheses.Inadditiontotheoperatornameusedfor
postfixoutputweaddtwonumberstothestructType:
structType{
constchar*name;/*node’sname*/
charrank,rpar;
void*(*new)(va_listap);
void(*exec)(constvoid*tree,intrank,intpar);
void(*delete)(void*tree);
};
.rankistheprecedenceoftheoperator,startingwith1foraddition..rparissetfor
operatorssuchassubtraction,whichrequiretheirrightoperandtobeenclosedin
parenthesesifitusesanoperatorofequalprecedence.Asanexampleconsider
$infix
1+(2—3)
1+2—3
1—(2—3)
1—(2—3)
Thisdemonstratesthatwehavethefollowinginitialization:
staticstructType_Add={"+",1,0,mkBin,doBin,freeBin};
staticstructType_Sub={"—",1,1,mkBin,doBin,freeBin};
Thetrickypartisforabinarynodetodecideifitmustsurrounditselfwith
parentheses.Abinarynodesuchasanadditionisgiventheprecedenceofits
superiorandaflagindicatingwhetherparenthesesareneededinthecaseofequal
precedence.doBin()decidesifitwilluseparentheses:
staticvoiddoBin(constvoid*tree,intrank,intpar)
{conststructType*type=*(structType**)tree;
par=type—>rank<rank
||(par&&type—>rank==rank);
if(par)putchar(’(’);
Ifournodehaslessprecedencethanitssuperior,orifweareaskedtoputup
9
parenthesesonequalprecedence,weprintparentheses.Inanycase,ifour
descriptionhas.rparset,werequireonlyofourrightoperandthatitputupextra
parentheses:
exec(((structBin*)tree)—>left,type—>rank,0);
printf("%s",type—>name);
exec(((structBin*)tree)—>right,
type—>rank,type—>rpar);
if(par)putchar(’)’);
}
Theremainingprintingroutinesaresignificantlysimplertowrite.
REFERENCES
[ANSI]AmericanNationalStandardforInformationSystems—Programming
LanguageCX3.159-1989.
[AWK88]A.V.Aho,B.W.KernighanundP.J.WeinbergerTheawkProgramming
LanguageAddison-Wesley1988,ISBN0-201-07981-X.
AUTHOR
GuruprasadDavangave
SoftwareEngineer
ComputerScienceandEngineering.[2021]