#IZ6 True being evaluated to True when I want just true

I’m stuck on the first evaluating Stage #IZ6.

The evaluator does evaluate using the whole tokens thing. However, somewhere in the process of being passed from the parser to the evaluator, true gets converted to True implictly through what I assume is Python evaluation. I just can’t get a handle on where exactly.

Here are my logs:

I added a debug print (this is inside the evaluator):

def evaluate(self):
        print(f"Evaluating Literal: {self.expression.value} (type: {type(self.expression.value)})")

        return self.evaluate_expr(self.expression)

and received stdout which verifies that Python is converting itself:

Evaluating Literal: True (type: <class 'bool'>)
True

And here’s a snippet of my code:

include relevant code here (please make sure to keep the backticks around this!)

The parser itself correctly displays true, so it must be somewhere in the passing to the evaluator that Python coils around it and changes it to a Python Bool

Here’s a snippet of my evaluator being called in main():

elif command == "evaluate":
        tokens, lex_errors = scanner.scan_tokens()
        
        if lex_errors:
            sys.exit(65)
        
        parser = Parser(tokens)
        expression = parser.parse()
        
        if parser.had_error or not expression:
            sys.exit(65)
        
        evaluator = Evaluate(expression)
        result = evaluator.evaluate()
        print(result)
        sys.exit(0)

Additional testing:

Added in some debugging output for evaluating !true:

<__main__.Unary object at 0x10d4ecc90>
Evaluating Unary: TokenType.BANG ! None true
<__main__.Literal object at 0x10d4eccd0>
Evaluating Literal: True (type: <class 'bool'>)
False

And I’m just going to give my entire Evaluate Class:

class Evaluate:

    def __init__(self, expression):
        self.expression = expression

    def evaluate(self):
        return self.evaluate_expr(self.expression)

    def evaluate_expr(self, expr):
        # DEBUGGING PRINTING IS IN
        if isinstance(expr, Binary):
            print(repr(expr))
            print(f"Evaluating Binary: {expr.left} {expr.operator} {expr.right}")
            return self.evaluate_binary(expr)
        elif isinstance(expr, Grouping):
            print(repr(expr))
            print(repr(expr.expression))
            print(f"Evaluating Grouping: {expr.expression}")
            return self.evaluate_expr(expr.expression)
        elif isinstance(expr, Literal):
            print(repr(expr))
            print(f"Evaluating Literal: {expr.value} (type: {type(expr.value)})")
            return expr.value
        elif isinstance(expr, Unary):
            print(repr(expr))
            print(f"Evaluating Unary: {expr.operator} {expr.right}")
            return self.evaluate_unary(expr)
        else:
            raise Exception(f"Unexpected expression type: {type(expr)}")

    def evaluate_binary(self, expr):
        left = self.evaluate_expr(expr.left)
        right = self.evaluate_expr(expr.right)
        operator = expr.operator.type

        if operator == TokenType.PLUS:
            return left + right
        elif operator == TokenType.MINUS:
            return left - right
        elif operator == TokenType.STAR:
            return left * right
        elif operator == TokenType.SLASH:
            return left / right
        elif operator == TokenType.GREATER:
            return left > right
        elif operator == TokenType.GREATER_EQUAL:
            return left >= right
        elif operator == TokenType.LESS:
            return left < right
        elif operator == TokenType.LESS_EQUAL:
            return left <= right
        elif operator == TokenType.EQUAL_EQUAL:
            return left == right
        elif operator == TokenType.BANG_EQUAL:
            return left != right
        else:
            raise Exception(f"Unexpected operator: {operator}")

    def evaluate_unary(self, expr):
        right = self.evaluate_expr(expr.right)
        operator = expr.operator.type

        if operator == TokenType.MINUS:
            return -right
        elif operator == TokenType.BANG:
            return not right
        else:
            raise Exception(f"Unexpected operator: {operator}")

I think the issue here is that not right evaluates to not "true", which is False (i.e. the python False).

You need to consider 2 things here:

  1. Lox’s truthiness is different from Python’s truthiness, so there must be some function to bridge the gap between the two.
  2. Python uses True and False while Lox uses true and false, so after interpreting the boolean expression in Python land, you need another function to convert the result to a Lox bool.

Are you following the book or are you trying to implement this by yourself?

2 Likes

I am trying to follow the book but of course the examples aren’t in python and I don’t know the syntax for that. How do I evaluate to a lox bool? I know the linter and parser correctly give it in terms of tokens as a lox bool, but in evaluation does this just mean outputting the literal “true”?

Also, I see in the book something about truthyness, do we need to add in evaluation of all identifiers to truth values in this stage?

1 Like

Following your advice and after using hints from the book, I added type checking to all inputs, handled everything as python types, then at output, “stringified” into lox types:

class Evaluate:
    def __init__(self, expression):
        self.expression = expression

    def isTruthy(self, value):
        # Lox truthiness rules
        if value is None:
            return False
        if isinstance(value, bool):
            return value
        # In Lox, all values except false and nil are truthy
        return True

    def evaluate(self):
        result = self.evaluate_expr(self.expression)
        return result

    def stringify(self, value):
        # Convert Python values to Lox string representation
        if value is None:
            return "nil"
        
        if isinstance(value, bool):
            return str(value).lower()
        
        if isinstance(value, float):
            # Remove decimal point for whole numbers
            text = str(value)
            if text.endswith(".0"):
                text = text[0:len(text)-2]
            return text
        
        # Regular string or other type
        return str(value)

    def evaluate_expr(self, expr):
        if isinstance(expr, Binary):
            return self.evaluate_binary(expr)
        elif isinstance(expr, Grouping):
            return self.evaluate_expr(expr.expression)
        elif isinstance(expr, Literal):
            # Return actual value, not string representation
            return expr.value
        elif isinstance(expr, Unary):
            return self.evaluate_unary(expr)
        else:
            raise Exception(f"Unexpected expression type: {type(expr)}")

    def evaluate_binary(self, expr):
        left = self.evaluate_expr(expr.left)
        right = self.evaluate_expr(expr.right)
        operator = expr.operator.type

        # Arithmetic operators
        if operator == TokenType.PLUS:
            # Handle string concatenation
            if isinstance(left, str) and isinstance(right, str):
                return left + right
            
            if isinstance(left, (int, float)) and isinstance(right, (int, float)):
                return left + right
                
            # In Lox, you can only add numbers or concatenate strings
            raise RuntimeError("Operands must be two numbers or two strings.")
            
        elif operator == TokenType.MINUS:
            self.checkNumberOperands(operator, left, right)
            return left - right
            
        elif operator == TokenType.STAR:
            self.checkNumberOperands(operator, left, right)
            return left * right
            
        elif operator == TokenType.SLASH:
            self.checkNumberOperands(operator, left, right)
            if right == 0:
                raise RuntimeError("Division by zero.")
            return left / right

        # Comparison operators
        elif operator == TokenType.GREATER:
            self.checkNumberOperands(operator, left, right)
            return left > right
            
        elif operator == TokenType.GREATER_EQUAL:
            self.checkNumberOperands(operator, left, right)
            return left >= right
            
        elif operator == TokenType.LESS:
            self.checkNumberOperands(operator, left, right)
            return left < right
            
        elif operator == TokenType.LESS_EQUAL:
            self.checkNumberOperands(operator, left, right)
            return left <= right

        # Equality operators
        elif operator == TokenType.EQUAL_EQUAL:
            return self.isEqual(left, right)
            
        elif operator == TokenType.BANG_EQUAL:
            return not self.isEqual(left, right)
            
        else:
            raise Exception(f"Unexpected operator: {operator}")

    def evaluate_unary(self, expr):
        right = self.evaluate_expr(expr.right)
        operator = expr.operator.type

        if operator == TokenType.MINUS:
            self.checkNumberOperand(operator, right)
            return -float(right)
            
        elif operator == TokenType.BANG:
            return not self.isTruthy(right)
            
        else:
            raise Exception(f"Unexpected operator: {operator}")

    def isEqual(self, a, b):
        # Handle nil equality
        if a is None and b is None:
            return True
            
        if a is None:
            return False
            
        # In Lox, equality uses standard equality semantics
        return a == b

    def checkNumberOperand(self, operator, operand):
        # Ensure operand is a number
        if isinstance(operand, (int, float)):
            return
        raise RuntimeError(f"Operand must be a number for {operator}.")

    def checkNumberOperands(self, operator, left, right):
        # Ensure both operands are numbers
        if isinstance(left, (int, float)) and isinstance(right, (int, float)):
            return
        raise RuntimeError(f"Operands must be numbers for {operator}.")

with evaluation run as:

tokens, lex_errors = scanner.scan_tokens()
        if lex_errors:
            sys.exit(65)
        parser = Parser(tokens)
        expression = parser.parse()
        if parser.had_error or not expression:
            sys.exit(65)
        evaluator = Evaluate(expression)
        result = evaluator.evaluate()
        print(evaluator.stringify(result))  # Use stringify here
        sys.exit(0)
2 Likes

Nice! Glad you could figure it out. That’s a fine approach given that you’re not using the visitor pattern from the book judging by what I can see. isTruthy and stringify are necessary here :+1:

One thing to note is that your evaluate_expr method will grow quite large in time, but that’s not the end of the world.

1 Like

I think I’m going to do a rehaul and try and implement the visitor stuff, otherwise I assume the book is not going to be much help for the statements and functions later on (which is where I assume it gets nitty gritty)

You don’t need to use the Visitor pattern - others have done it differently .

But if you wanna have a smooth sail on the first part of the book (and the challenge) I’d say to go ahead and implement it. The AST generator presented in the book is not that hard to copy to python.

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.