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()