Crafting Interpreters — Jlox(Evaluating Expressions)
In this chapter, we are evaluating through expressions to return a value, in earlier chapters we created an AST, where we arranged tokens in their order of precedence. The way they should be executed, and here we are executing them.
Object *evaluate(Expr *expression){
switch (expression->type)
{
case EXPR_CONDITION:
Object *condition_literal = evaluate(expression->ast.Conditon.condition);
if(condition_literal->value.num == 1){
evaluate(expression->ast.Conditon.true_statment);
}else{
evaluate(expression->ast.Conditon.false_statment);
}
free(condition_literal);
free(expression);
break;
case EXPR_BINARY:
Object *binary_left = evaluate(expression->ast.Binary.left);
Object *binary_right = evaluate(expression->ast.Binary.right);
Object *binary_literal = malloc(sizeof(Object));
switch (expression->ast.Binary.operator->type)
{
case PLUS:
if(binary_left->type == CHAR && binary_right->type == CHAR){
size_t length = strlen(binary_left->value.str) + strlen(binary_right->value.str) + 1;
char *buffer = malloc(sizeof(char) * length);
snprintf(buffer, length, "%s%s", binary_left->value.str, binary_right->value.str);
free(binary_left->value.str);
free(binary_right->value.str);
binary_literal->type = CHAR;
binary_literal->value.str = buffer;
}
else if(binary_left->type == DOUBLE && binary_right->type == DOUBLE){
binary_literal->type = DOUBLE;
binary_literal->value.real = binary_left->value.real + binary_right->value.real;
}
else{
runtimeError(expression->ast.Binary.operator, "operands must be two strings or two numbers");
}
break;
case STAR:
checkNumberOperands(expression->ast.Binary.operator, binary_left, binary_right);
binary_literal->type = DOUBLE;
binary_literal->value.real = binary_left->value.real * binary_right->value.real;
break;
case SLASH:
checkNumberOperands(expression->ast.Binary.operator, binary_left, binary_right);
if((int)binary_right->value.real == 0){
runtimeError(expression->ast.Binary.operator, "cannot divide by zero.");
}
binary_literal->type = DOUBLE;
binary_literal->value.real = binary_left->value.real / binary_right->value.real;
break;
case MINUS:
checkNumberOperands(expression->ast.Binary.operator, binary_left, binary_right);
binary_literal->type = DOUBLE;
binary_literal->value.real = binary_left->value.real - binary_right->value.real;
break;
case LESS_EQUAL:
checkNumberOperands(expression->ast.Binary.operator, binary_left, binary_right);
binary_literal->type = INT;
binary_literal->value.num = binary_left->value.real <= binary_right->value.real;
break;
case LESS:
checkNumberOperands(expression->ast.Binary.operator, binary_left, binary_right);
binary_literal->type = INT;
binary_literal->value.num = binary_left->value.real < binary_right->value.real;
break;
case GREATER_EQUAL:
checkNumberOperands(expression->ast.Binary.operator, binary_left, binary_right);
binary_literal->type = INT;
binary_literal->value.num = binary_left->value.real >= binary_right->value.real;
break;
case GREATER:
checkNumberOperands(expression->ast.Binary.operator, binary_left, binary_right);
binary_literal->type = INT;
binary_literal->value.num = binary_left->value.real > binary_right->value.real;
break;
case BANG_EQUAL:
binary_literal->type = INT;
if(binary_left->type == CHAR && binary_right->type == CHAR){
if(strcmp(binary_left->value.str, binary_right->value.str) == 0){
binary_literal->value.num = 0;
}else{
binary_literal->value.num = 1;
}
free(binary_left->value.str);
free(binary_right->value.str);
}else{
binary_literal->value.num = binary_left->value.real != binary_right->value.real;
}
break;
case EQUAL_EQUAL:
binary_literal->type = INT;
if(binary_left->type == CHAR && binary_right->type == CHAR){
if(strcmp(binary_left->value.str, binary_right->value.str) == 0){
binary_literal->value.num = 1;
}else{
binary_literal->value.num = 0;
}
free(binary_left->value.str);
free(binary_right->value.str);
}else{
binary_literal->value.num = binary_left->value.real == binary_right->value.real;
}
break;
default:
break;
}
free(binary_left);
free(binary_right);
free(expression);
return binary_literal;
case EXPR_UNARY:
Object *unary_right = evaluate(expression->ast.Unary.right);
Object *unary_literal = malloc(sizeof(Object));
switch(expression->ast.Unary.operator->type){
case MINUS:
checkNumberOperand(expression->ast.Unary.operator, unary_right);
unary_literal->type = DOUBLE;
unary_literal->value.real = -unary_right->value.real;
break;
case BANG:
binary_literal->type = INT;
if(strcmp(unary_right->value.str, "nil") == 0 || strcmp(unary_right->value.str, "false") == 0){
unary_literal->value.num = 1;
}else{
unary_literal->value.num = 0;
}
break;
default:
break;
}
free(unary_right);
free(expression);
return unary_literal;
case EXPR_GROUPING:
Object *grouping_literal = evaluate(expression->ast.Grouping.expr);
free(expression);
return grouping_literal;
case EXPR_LITERAL:
Object *literal = malloc(sizeof(Object));
char *result = malloc(expression->ast.Literal->length + 1);
strncpy(result, expression->ast.Literal->ptr,expression->ast.Literal->length);
result[expression->ast.Literal->length] = '\0';
if(expression->ast.Literal->type == NUMBER){
char *endptr;
literal->type = DOUBLE;
literal->value.real = strtod(result, &endptr);
free(result);
}
else{
literal->type = CHAR;
literal->value.str = result;
}
free(expression);
return literal;
default:
break;
}
}
The first executed is the EXPR_LITERAL where we can either have a string or double.
Then I created a struct to store this and use depending on the type called the Object.
typedef struct{
enum {INT, DOUBLE, CHAR} type;
union
{
int num;
double real;
char *str;
} value;
} Object;
We also implement a type check, so as not to get a bug in our code.
void checkNumberOperand(TokenArray *operator, Object *operand){
if(operand->type == DOUBLE) return;
runtimeError(operator, "Operand must be a number.");
}
void checkNumberOperands(TokenArray *operator, Object *left, Object *right){
if(left->type == DOUBLE && right->type == DOUBLE) return;
runtimeError(operator, "Operands must be a number.");
}
With that the parser calls the interpret function, passing the expression for it to evaluate, and in return, it gives our value. So `9 < 5 ? 7 + 9 : 2 + 5` should give us 7.
Also, I didn’t want to concatenate strings and numbers, so I didn’t do the exercise to add them, and also, I’m eliminating comparisons of different types till they prove useful.
For now, our interpreter is an arithmetic calculator, next chapter we will be adding statements and states.