Exercise 3: Chained Assignment, x = y = 10 — Possible Solution ==================================================================== THE PROGRAM ------------------------------ var x = 1; var y = 2; x = y = 10; print x; print y; # As an AST: ExpressionStmt(Assign('x', Assign('y', Literal(10.0)))) # -- the VALUE of the outer Assign('x', ...) is itself an Assign node RESULT ------------------------------ ['10', '10'] WHY THIS WORKS AS AN ANSWER ------------------------------ def visit_assign(self, node): self.visit(node.value) # <-- this line is the whole answer slot = self.resolve_local(node.name) if slot is not None: ... else: idx = self.chunk.add_constant(node.name) self.chunk.write(OP_SET_GLOBAL, node.line) self.chunk.write(idx, node.line) self.visit(node.value) doesn't know or care whether node.value is a Literal, a Binary expression, or another Assign -- it just calls .accept() on whatever expression object is there, exactly like every other visit_* method in this compiler. When node.value happens to BE an Assign node (compiling y = 10), that inner call runs visit_assign() again, recursively, which itself compiles 10, emits OP_SET_GLOBAL for y, and -- critically -- LEAVES the value 10 on the stack afterward, because OP_SET_GLOBAL peeks rather than pops. That leftover value on the stack is exactly what the OUTER visit_assign call then treats as "node.value, already compiled" and uses for its own OP_SET_GLOBAL targeting x. No special case for chained assignment exists anywhere in this compiler, and none was needed. It falls out for free from two already-established design decisions working together: visit_assign treating its own value as "just another expression to compile" (the same uniform recursive-visitor discipline used everywhere else in this compiler), and OP_SET_GLOBAL/OP_SET_LOCAL both being designed to leave their assigned value on the stack rather than consuming it -- specifically so that assignment can be nested inside another expression, which is exactly what happened here.