Tutorial: Calculator¶
In this tutorial, we will write a calculator. It supports
Positive integers.
Negative integers.
Addition operator +.
Subtraction operator -.
Multiplication operator *.
Division operator /.
Parenthesis ().
Detect invalid zero division.
The calculator can take arbitrary valid string and yield a result.
Step 1: Create Grammar¶
Let’s create a new file “calc.h” and
create the grammar using P4_LoadGrammar()
.
P4_Grammar* P4_CreateCalcGrammar() {
return P4_LoadGrammar(
"statement = term eol;\n"
"@nonterminal\n"
"term = factor | term (add / minus) factor;\n"
"@nonterminal\n"
"factor = unary | factor (mul / div) unary;\n"
"@nonterminal\n"
"unary = primary / (add / minus)? unary;\n"
"@lifted\n"
"primary = integer / \"(\" term \")\";\n"
"@squashed @tight\n"
"integer = [0-9]+;\n"
"add = \"+\";\n"
"minus = \"-\";\n"
"mul = \"*\";\n"
"div = \"/\";\n"
"@spaced @lifted\n"
"whitespace = \" \" / \"\\t\";\n"
"eol = \"\\n\" / !.;\n"
);
}
To extend a little bit,
Rule statement is the entry.
Rule term can parse inputs like a + b - c + d … or a.
Rule factor can parse inputs like a * b / c * d … or a.
Rule unary can parse inputs like +a, ++a, -a, a, etc.
Rule primary can parse inputs like (a), etc.
Rule integer are some squashed digits. It avoids creating nodes for each digit.
Rule whitespace allows arbitrary number of whitespace or tab in between expressions.
Step 2: Eval Ast¶
Next, let’s evaluate the tree. We traverse the AST and calculate the result.
P4_Error P4_CalcEval(P4_Node* node, long* result) {
P4_Error err = P4_Ok;
P4_Node* tmp = NULL;
char sign = '+';
long val = 0;
char* intstr = NULL;
if (strcmp(node->rule_name, "statement") == 0) {
return P4_CalcEval(node->head, result);
} else if (strcmp(node->rule_name, "term") == 0) {
if ((err = P4_CalcEval(node->head, &val)) != P4_Ok)
return err;
*result = val;
if ((err = P4_CalcEval(node->tail, &val)) != P4_Ok)
return err;
if (strcmp(node->head->next->rule_name, "add") == 0) {
*result += val;
} else if (strcmp(node->head->next->rule_name, "minus") == 0) {
*result -= val;
} else {
return P4_ValueError;
}
return P4_Ok;
} else if (strcmp(node->rule_name, "factor") == 0) {
if ((err = P4_CalcEval(node->head, &val)) != P4_Ok)
return err;
*result = val;
if ((err = P4_CalcEval(node->tail, &val)) != P4_Ok)
return err;
if (strcmp(node->head->next->rule_name, "mul") == 0) {
*result *= val;
} else if (strcmp(node->head->next->rule_name, "div") == 0) {
if (val == 0) {
return P4_ValueError;
}
*result /= val;
} else {
return P4_ValueError;
}
return P4_Ok;
} else if (strcmp(node->rule_name, "unary") == 0) {
if (node->head == node->tail)
return P4_CalcEval(node->head, result);
else {
long val = 0;
if ((err = P4_CalcEval(node->tail, &val)) != P4_Ok)
return err;
if (strcmp(node->head->rule_name, "add") == 0)
*result = val;
else if (strcmp(node->head->rule_name, "minus") == 0)
*result = -val;
else
return P4_ValueError;
return P4_Ok;
}
} else if (strcmp(node->rule_name, "integer") == 0) {
intstr = P4_CopyNodeString(node);
*result = atol(intstr);
free(intstr);
return P4_Ok;
} else {
return P4_ValueError;
}
}
Step 3: Parse¶
Let’s create a new file “calc.c” and parse inputs reading from stdin.
The main function does the below things:
Create the grammar.
Create the source.
Parse the source using the grammar.
Eval the source ast.
Print the evaluated result.
#include "calc.h"
int main() {
P4_Grammar* grammar = P4_CreateCalcGrammar();
P4_Source* source = NULL;
P4_Error error = P4_Ok;
long result = 0;
char line[256];
printf("Type statement to continue. Type ^C to quit.\n");
while (fgets(line, sizeof(line), stdin)) {
source = P4_CreateSource(line, "statement");
if ((error = P4_Parse(grammar, source)) != P4_Ok) {
printf("error: parse: %s\n", P4_GetErrorMessage(source));
} else if ((error = P4_CalcEval(P4_GetSourceAst(source), &result)) != P4_Ok){
printf("error: eval: %d\n", error);
} else {
printf("[Out] %ld\n\n", result);
}
P4_DeleteSource(source);
}
P4_DeleteGrammar(grammar);
}
Run:
$ gcc -o calc calc.c peppa.c && ./calc
Type statement to continue. Type ^C to quit.
1+2*3
[Out] 7
-1 + 4/2*3 - 1
[Out] 4
5/0
error: eval: 6