Encountering Error for test case Booleans & Nil #sc2

I’m stuck on Stage #Booleans & Nil #sc2.

Here are my logs:

remote: [tester::#PQ5] [test-3] [test.lox] var greeting = "Hello"
remote: [tester::#PQ5] [test-3] [test.lox] if (greeting == "Hello") {
remote: [tester::#PQ5] [test-3] [test.lox]     return true
remote: [tester::#PQ5] [test-3] [test.lox] } else {
remote: [tester::#PQ5] [test-3] [test.lox]     return false
remote: [tester::#PQ5] [test-3] [test.lox] }
remote: [tester::#PQ5] [test-3] $ ./your_program.sh tokenize test.lox
remote: [your_program] VAR var null
remote: [your_program] IDENTIFIER greeting null
remote: [your_program] EQUAL = null
remote: [your_program] STRING "Hello" Hello
remote: [your_program] IF if null
remote: [your_program] LEFT_PAREN ( null
remote: [your_program] IDENTIFIER greeting null
remote: [your_program] EQUAL_EQUAL == null
remote: [your_program] STRING "Hello" Hello
remote: [your_program] RIGHT_PAREN ) null
remote: [your_program] LEFT_BRACE { null
remote: [your_program] RETURN return null
remote: [your_program] TRUE true null
remote: [your_program] RIGHT_BRACE } null
remote: [your_program] ELSE else null
remote: [your_program] LEFT_BRACE { null
remote: [your_program] RETURN return null
remote: [your_program] FALSE false null
remote: [your_program] RIGHT_BRACE } null
remote: [your_program] EOF  null
remote: [tester::#PQ5] [test-3] ✓ 20 line(s) match on stdout
remote: [tester::#PQ5] [test-3] ✓ Received exit code 0.
remote: [tester::#PQ5] [test-4] Running test case: 4
remote: [tester::#PQ5] [test-4] Writing contents to ./test.lox:
remote: [tester::#PQ5] [test-4] [test.lox] var result = (a + b) > 7 or "Success" != "Failure" or x >= 5
remote: [tester::#PQ5] [test-4] [test.lox] while (result) {
remote: [tester::#PQ5] [test-4] [test.lox]     var counter = 0
remote: [tester::#PQ5] [test-4] [test.lox]     counter = counter + 1
remote: [tester::#PQ5] [test-4] [test.lox]     if (counter == 10) {
remote: [tester::#PQ5] [test-4] [test.lox]         return nil
remote: [tester::#PQ5] [test-4] [test.lox]     }
remote: [tester::#PQ5] [test-4] [test.lox] }
remote: [tester::#PQ5] [test-4] $ ./your_program.sh tokenize test.lox
remote: [your_program] VAR var null
remote: [your_program] IDENTIFIER result null
remote: [your_program] EQUAL = null
remote: [your_program] LEFT_PAREN ( null
remote: [your_program] IDENTIFIER a null
remote: [your_program] PLUS + null
remote: [your_program] IDENTIFIER b null
remote: [your_program] GREATER > null
remote: [your_program] NUMBER 7 null
remote: [your_program] OR or null
remote: [your_program] STRING "Success" Success
remote: [your_program] BANG_EQUAL != null
remote: [your_program] STRING "Failure" Failure
remote: [your_program] OR or null
remote: [your_program] IDENTIFIER x null
remote: [your_program] GREATER_EQUAL >= null
remote: [your_program] NUMBER 5 null
remote: [your_program] WHILE while null
remote: [your_program] LEFT_PAREN ( null
remote: [your_program] IDENTIFIER result null
remote: [your_program] LEFT_BRACE { null
remote: [your_program] VAR var null
remote: [your_program] IDENTIFIER counter null
remote: [your_program] EQUAL = null
remote: [your_program] NUMBER 0 null
remote: [your_program] IDENTIFIER counter null
remote: [your_program] EQUAL = null
remote: [your_program] IDENTIFIER counter null
remote: [your_program] PLUS + null
remote: [your_program] NUMBER 1 null
remote: [your_program] IF if null
remote: [your_program] LEFT_PAREN ( null
remote: [your_program] IDENTIFIER counter null
remote: [your_program] EQUAL_EQUAL == null
remote: [your_program] NUMBER 10 null
remote: [your_program] LEFT_BRACE { null
remote: [your_program] RETURN return null
remote: [your_program] NIL nil null
remote: [your_program] RIGHT_BRACE } null
remote: [your_program] RIGHT_BRACE } null
remote: [your_program] EOF  null
remote: [tester::#PQ5] [test-4] ✓ VAR var null
remote: [tester::#PQ5] [test-4] ✓ IDENTIFIER result null
remote: [tester::#PQ5] [test-4] ✓ EQUAL = null
remote: [tester::#PQ5] [test-4] ✓ LEFT_PAREN ( null
remote: [tester::#PQ5] [test-4] ✓ IDENTIFIER a null
remote: [tester::#PQ5] [test-4] ✓ PLUS + null
remote: [tester::#PQ5] [test-4] ✓ IDENTIFIER b null
remote: [tester::#PQ5] [test-4] 𐄂 GREATER > null
remote: [tester::#PQ5] [test-4] Expected line #8 on stdout to be "RIGHT_PAREN ) null", got "GREATER > null"
remote: [tester::#PQ5] [test-4] Test failed

And here’s a snippet of my code:

import sys

def tokenize_file(file_contents):
    # Reserved words in the language (keywords)
    reserved_words = {
        "and", "class", "else", "false", "for", "fun", "if", "nil", "or", "print", 
        "return", "super", "this", "true", "var", "while"
    }

    # Mapping of single-character symbols to token types
    token_map = {
        "(": "LEFT_PAREN",
        ")": "RIGHT_PAREN",
        "{": "LEFT_BRACE",
        "}": "RIGHT_BRACE",
        ",": "COMMA",
        ".": "DOT",
        "-": "MINUS",
        "+": "PLUS",
        ";": "SEMICOLON",
        "*": "STAR"
    }

    tokens = []  # List to hold all the tokens
    error = False  # Flag to indicate errors during tokenization
    i = 0  # Pointer to iterate over the file contents

    while i < len(file_contents):  # Iterate through the file contents
        c = file_contents[i]  # Current character in the file

        # Skip whitespace characters (space, carriage return, tab, newline)
        if c in " \r\t\n":
            i += 1  # Move to the next character
            continue

        # Handle single-character symbols (e.g., parentheses, operators)
        elif c in "(){},.-+;*":
            token = {"type": token_map[c], "value": c}
            tokens.append(token)  # Add the token to the list

        # Handle operators and other special symbols (e.g., ==, !=)
        elif c == "=":
            if i + 1 < len(file_contents) and file_contents[i + 1] == "=":
                token = {"type": "EQUAL_EQUAL", "value": "=="}
                i += 1  # Skip the second '=' character
            else:
                token = {"type": "EQUAL", "value": "="}
            tokens.append(token)  # Add the token to the list

        # Handle reserved words (keywords) and identifiers
        # Add logic for reserved words, identifiers, strings, numbers, etc.

        # Move to the next character
        # Handle less-than or less-than-equal (<=)
        elif c == "<":
            if i + 1 < len(file_contents) and file_contents[i + 1] == "=":
                token = {"type": "LESS_EQUAL", "value": "<="}
                i += 1  # Skip the '=' character
            else:
                token = {"type": "LESS", "value": "<"}
            tokens.append(token)  # Add the token to the list

        # Handle greater-than or greater-than-equal (>=)
        elif c == ">":
            if i + 1 < len(file_contents) and file_contents[i + 1] == "=":
                token = {"type": "GREATER_EQUAL", "value": ">="}
                i += 1  # Skip the '=' character
            else:
                token = {"type": "GREATER", "value": ">"}
            tokens.append(token)  # Add the token to the list

        # Handle not-equal (!=) or not (!) 
        elif c == "!":
            if i + 1 < len(file_contents) and file_contents[i + 1] == "=":
                token = {"type": "BANG_EQUAL", "value": "!="}
                i += 1  # Skip the '=' character
            else:
                token = {"type": "BANG", "value": "!"}
            tokens.append(token)  # Add the token to the list

        # Handle comments (//) – Skip the rest of the line after '//'
        elif c == "/":
            if i + 1 < len(file_contents) and file_contents[i + 1] == "/":
                while i < len(file_contents) and file_contents[i] != "\n":
                    i += 1  # Skip characters until the end of the line
            else:
                token = {"type": "SLASH", "value": "/"}
                tokens.append(token)  # Add the token to the list

        # Handle string literals (enclosed in " or ')
        elif c in "\"'":
            quote_type = c  # Keep track of whether it's a single or double quote
            i += 1  # Move to the next character after the opening quote
            string_value = ""  # Initialize an empty string for the literal value
            while i < len(file_contents) and file_contents[i] != quote_type:
                string_value += file_contents[i]  # Append characters inside the string literal
                i += 1
            if i == len(file_contents):  # Error: Unterminated string literal
                error = True
                line_no = file_contents.count("\n", 0, i) + 1
                print(f"[line {line_no}] Error: Unterminated string.", file=sys.stderr)
            else:
                token = {"type": "STRING", "value": string_value}  # Create a token for the string
                tokens.append(token)  # Add the token to the list
        
        # Handle numbers (integers and floats)
        elif c.isdigit():
            start_idx = i  # Mark the starting index of the number
            while i < len(file_contents) and (file_contents[i].isdigit() or file_contents[i] == "."):
                i += 1  # Move until the end of the number
            lexeme = file_contents[start_idx:i]  # Extract the lexeme (the number string)
            # Check for invalid number literals (multiple decimal points or non-numeric characters)
            if lexeme.count(".") > 1 or not lexeme.replace(".", "", 1).isdigit():
                error = True
                line_no = file_contents.count("\n", 0, i) + 1
                print(f"[line {line_no}] Error: Invalid number literal.", file=sys.stderr)
            else:
                literal_value = float(lexeme)  # Convert the lexeme to a float
                token = {"type": "NUMBER", "value": lexeme}  # Create a token for the number
                tokens.append(token)  # Add the token to the list

        # Handle identifiers (variable names, function names, etc.)
        elif c.isalpha() or c == "_":
            start_idx = i  # Mark the starting index of the identifier
            while i < len(file_contents) and (file_contents[i].isalnum() or file_contents[i] == "_"):
                i += 1  # Move until the end of the identifier
            lexeme = file_contents[start_idx:i]  # Extract the lexeme (the identifier)
            if lexeme in reserved_words:  # Check if it's a reserved word (keyword)
                token = {"type": lexeme.upper(), "value": lexeme}  # Reserved word token
                tokens.append(token)  # Add the token to the list
            else:
                token = {"type": "IDENTIFIER", "value": lexeme}  # Regular identifier token
                tokens.append(token)  # Add the token to the list

        # Handle unexpected characters (errors)
        else:
            error = True
            line_no = file_contents.count("\n", 0, i) + 1
            print(f"[line {line_no}] Error: Unexpected character: {c}", file=sys.stderr)


        i += 1  # Move to the next character

    # Add an EOF (End of File) token
    tokens.append({"type": "EOF", "value": "null"})

    # If there was an error, exit with code 65
    if error:
        exit(65)
    else:
        return tokens  # Return the list of tokens

def print_tokens(tokens):
    for token in tokens:
        if token['type'] == 'EOF':
            print(f"{token['type']}  {token['value']}")
        elif token['type'] == 'STRING':
            print(f'{token['type']} "{token['value']}" {token['value']}')
        else:
            print(f"{token['type']} {token['value']} null")
 
def literals(value):
    if value == None:
        return 'nil'
    return str(value).lower()

def unary(operator,right):
    return {'operator':operator, 'right':right}

def binary(left, operator, right):
    return {'left':left,'operator':operator,'right':right}

def grouping(expression):
    return {'expression':expression}

class Parser:
    def __init__(self, tokens):
        self.tokens = tokens
        self.current = 0  # Points to the current token

    def parse(self):
        return self.expression()  # Start parsing from the highest level rule

    def expression(self):
        return self.equality()

    def equality(self):
        expr = self.comparison()
        while self.match("BANG_EQUAL", "EQUAL_EQUAL"):
            operator = self.previous()
            right = self.comparison()
            expr = binary(expr, operator["value"], right["value"])
        return expr

    def comparison(self):
        expr = self.term()
        while self.match("GREATER", "GREATER_EQUAL", "LESS", "LESS_EQUAL"):
            operator = self.previous()
            right = self.term()
            expr = binary(expr, operator["value"], right["value"])
        return expr

    def term(self):
        expr = self.factor()
        while self.match("MINUS", "PLUS"):
            operator = self.previous()
            right = self.factor()
            expr = binary(expr, operator["value"], right["value"])
        return expr

    def factor(self):
        expr = self.unary()
        while self.match("SLASH", "STAR"):
            operator = self.previous()
            right = self.unary()
            expr = binary(expr, operator["value"], right["value"])
        return expr

    def unary(self):
        if self.match("BANG", "MINUS"):
            operator = self.previous()
            right = self.unary()
            return unary(operator["value"], right["value"])
        return self.primary()

    def primary(self):
        if self.match("FALSE"):
            return literals(False)
        if self.match("TRUE"):
            return literals(True)
        if self.match("NIL"):
            return literals(None)
        if self.match("NUMBER", "STRING"):
            return self.previous()["value"]
        if self.match("LEFT_PAREN"):
            expr = self.expression()
            self.consume("RIGHT_PAREN", "Expect ')' after expression.")
            return grouping(expr)
        if self.match("IDENTIFIER"):
            return {"type": "IDENTIFIER", "value": self.previous()["value"]}
        self.error("Expect expression.")

    def print_ast(self, expr):
        if isinstance(expr, dict):  # If it's a binary operation
            return f"({expr['operator']} {self.print_ast(expr['left'])} {self.print_ast(expr['right'])})"
        elif isinstance(expr, str):  # If it's a literal
            return expr
        return ""

    # Utility Methods
    def match(self, *types):
        for token_type in types:
            if self.check(token_type):
                self.advance()
                return True
        return False

    def check(self, token_type):
        if self.is_at_end():
            return False
        return self.peek()["type"] == token_type

    def advance(self):
        if not self.is_at_end():
            self.current += 1
        return self.previous()

    def is_at_end(self):
        return self.peek()["type"] == "EOF"

    def peek(self):
        return self.tokens[self.current]

    def previous(self):
        return self.tokens[self.current - 1]

    def consume(self, token_type, message):
        if self.check(token_type):
            return self.advance()
        self.error(message)

    def error(self, message):
        token = self.peek()
        line = token.get("line", "unknown")
        raise SyntaxError(f"[line {line}] Error: {message}")

def main():
    if len(sys.argv) < 3:
        print("Usage: ./your_program.sh <command> <filename>", file=sys.stderr)
        exit(1)

    command = sys.argv[1]
    filename = sys.argv[2]

    try:
        with open(filename) as file:
            file_contents = file.read()
    except FileNotFoundError:
        print(f"Error: File '{filename}' not found.", file=sys.stderr)
        exit(1)

    if command == "tokenize":
        tokens = tokenize_file(file_contents)
        print_tokens(tokens)

    elif command == "parse":
        tokens = tokenize_file(file_contents)
        parser = Parser(tokens)
        try:
            expression = parser.parse()  # Generate the AST
            print(parser.print_ast(expression))  # Print the AST representation
        except SyntaxError as e:
            print(e, file=sys.stderr)
            exit(65)
    else:
        print(f"Unknown command: {command}", file=sys.stderr)
        exit(1)
  
if __name__ == "__main__":
    main()



Hi @ksv-py, I tried running your code against the previous stages, but it’s actually no longer passing the stage #EA6 (Scanning: Lexical errors).

Suggestions:

  1. Use our CLI to test against previous stages by running:
codecrafters test --previous
  1. Focus on fixing the early stages first, as later stages depend on them.

Closing this thread due to inactivity. If you still need assistance, feel free to reopen or start a new discussion!

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