Author’s note: For ChatGPT tips, scroll to the end. The ChatGPT transcript is here.

I love Crafting Interpreters. I first worked through the book at Recurse Center, next at Bradfield and most recently with David Beazley (repos here, here and here) [1].

The book teaches you how to implement a programming language from scratch. I worked through most of the chapters, but what I enjoy re-implementing is the ‘minimal’ set of features to to generate Fibonacci numbers. It’s a nice use case, requiring built-in operations, control flow and recursive function calls.

Since the code has gone through a couple of iterations, I was curious to see how ChatGPT could help me improve code quality further.

Prelude

Source code to output

The scanner (or lexer) converts the source code into tokens (truncated to ease viewing).

> source_code = """\\
func fibonacci(n) {
    if (n < 2) {
        return 1;
    }

    return fibonacci(n - 1) + fibonacci(n - 2);
}
fibonacci(9);"""

> tokens = scanner.scan(source_code)

> for token in tokens: print(token)
Token(token_type=<TokenType.FUNC: 'FUNC'>, value='func', line=1)
Token(token_type=<TokenType.NAME: 'NAME'>, value='fibonacci', line=1)
Token(token_type=<TokenType.PAREN_LEFT: 'PAREN_LEFT'>, value='(', line=1)
Token(token_type=<TokenType.NAME: 'NAME'>, value='n', line=1)
Token(token_type=<TokenType.PAREN_RIGHT: 'PAREN_RIGHT'>, value=')', line=1)
...
Token(token_type=<TokenType.NAME: 'NAME'>, value='fibonacci', line=8)
Token(token_type=<TokenType.PAREN_LEFT: 'PAREN_LEFT'>, value='(', line=8)
Token(token_type=<TokenType.INTEGER: 'INTEGER'>, value='9', line=8)
Token(token_type=<TokenType.PAREN_RIGHT: 'PAREN_RIGHT'>, value=')', line=8)
Token(token_type=<TokenType.SEMICOLON: 'SEMICOLON'>, value=';', line=8)
Token(token_type=<TokenType.EOF: 'EOF'>, value='EOF', line=8)

Next the parser converts the tokens into abstract syntax trees (hereafter, ASTs).

> statements = parser.parse(tokens)

> for statement in statements: print(statement)
Function(
    name=Name(text='fibonacci'),
    parameters=[Name(text='n')],
    body=Block(
			statements=[
					...
			]
		)
)
Expression(
    expression=Call(
        callee=Name(text='fibonacci'),
        arguments=[Integer(value='9')]
    )
)

Finally the interpreter ‘executes’ the ASTs to generate the desired output.

> results = interpreter.interpret(statements)

> for result in results: print(result)
None
55

Project setup

To separate ChatGPT changes from my own implementation, I copied over the latest version of minimal-lox into a new repo simple-lox. Naturally I got ChatGPT to suggest the name.