munificent / craftinginterpreters

Repository for the book "Crafting Interpreters"
http://www.craftinginterpreters.com/
Other
8.5k stars 1.01k forks source link

Best way to convert infix operator to function call? #1022

Closed gvwilson closed 8 months ago

gvwilson commented 2 years ago

I've added a prefix # operator to CLOX that converts its argument to a string, so that (for example) #123 produces "123". To implement it, I have:

  1. Added native functions type, has, and str_ that return the name of an object's Lox-level type, check if an object has an attribute, and convert an object to a string (returning instance for instances).
  2. Added a Lox standard library (borrowing the idea from Wren, I compile a small bit of Lox code as a prefix to the user's program).
  3. Translated # expr into a call to str(expr), where str (without the trailing underscore) is part of the standard library:
fun str(obj) {
  if ((type(obj) == "instance") and has(obj, "str")) {
    return obj.str();
  } else {
    return str_(obj);
  }
}

This implementation allows classes to define their own str methods, like the __str__ and __repr__ methods of Python.

I'd like to define an infix version of # that concatenates the string representations of objects so that print("hello: " # 123); prints "hello: 123". Like the prefix version, infix # should call each operand's str method if there is one, or use the built-in str_ as a fallback, and then concatenate the results.

So here's the problem: by the time the compiler reaches the infix #, it has already generated code for the left operand, but function calls expect name arg1 arg2 on the stack, not arg1 name arg2. One possibility is to add a second kind of CALL instruction that rearranges the top of the stack (or expects things in a different order, e.g., arg1 arg2 name), but I'm hoping there's a more elegant solution: adding a new VM instruction to support a single use case seems clumsy. Suggestions would be very welcome - thanks in advance.