Interpreter Project: Functions

This chapter was interesting. In some ways simple and in other ways challenging. I have a better understanding of how other languages implement function calls and I was able to solve a problem specific to my implementation (more on that below).

You can find my work at this point here.

Silly Comma

Back in chapter on parsing expressions one of the challenges was to add support for the c style comma operator. It isn’t much of an operator really, it just lets you perform multiple declarations on the same line like so:

int x = 0, y = 1;

Two initializations for the price of one. Well, not really, you only save three characters. I do actually use that operator from time to time, so I added it.

Parsing that comma operator caused me a bit of grief while adding functions this week. Can you guess where?

Argument lists. Yep, function arguments are separated by a comma. When the parser detects a function call it then checks to see if there are any arguments. If it finds any they are evaluated (because an argument could be an expression) and then bundled into the Ringo::Call AST Node.

It is really the ‘argument can be an expression’ part that caused the problem. The code looks like this:

def finish_call(callee)
  arguments = []
  if !check?(:rparen)
    loop do
      ...
      # Parse the expression and add it to the list of args.
      arguments << expression
      break unless match?(:comma)
    end
  end
  ...
end

In my codebase the expression method looks like this:

def expression
  comma
end

There was an ambiguity in parsing. When it saw something like:

fun say_hello(first_name, last_name)
{
  print “Hello “ + first_name + “ “ + last_name + “.”
}

say_hello(“Wade”, “Wilson”)

The first part, declaring the function, worked fine. Declarations go through a different section of the parser. When you call the function is where the problem arises.

My parser would get to the arguments, ”Wade”, “Wilson”, see the comma, and wrap it all up into a Ringo::Binary expression, because that is how the comma operator works.

The fix was simple once I figured out what was happening. I don’t know if it is the best fix, but it works for this simple project. Instead of parsing a full expression when dealing with a function argument list I changed it to start the parse after the comma in the precedence.

def finish_call(callee)
  arguments = []
  if !check?(:rparen)
    loop do
      ...
      # Start parsing after comma, at assignment.
      arguments << assignment
      break unless match?(:comma)
    end
  end
  ...
end

Moving Forward

The next section is called Resolving and Binding, so I guess it has something to do with variables. I have not read ahead at all, so I'm not entirely sure what will be added to the interpreter. Perhaps a refinement to the variable scoping rules? I'll find out and report back.

Only three chapters to go for this version of the interpreter. Whatever the next chapter is, then Classes and finally Inheritance. I'm looking forward to having this part of the project complete. I'm already planning out bits of my own language that I will probably start working on soon.