Implementing a Programming Language in Swift — Part 8: Functions
NOTE: This is the eighth part in a tutorial series on “Writing a Programming Language in Swift.” Be sure to check out the previous ones.
In the previous tutorials we’ve implemented a little calculator interpreter that supports variable declarations and expressions.
Next feature to add is support for functions and in this tutorial we’ll go over the gotchas for function support and discuss how we will go about its implementation.
Designing Function Declarations
Some programming language in the past have gone quite creative when it comes to function definitions. From Objective-C’s:
// instance
- (Void) myMethod: (String) argument// static
+ (Void) myStaticMethod: (String) argument
To JavaScript’s arrow functions:
(argument) => { ... }
But since C, most programming language’s are fairly traditional and follow C style definitions:
<FunctionDefinition> ::= <ReturnType> <Identifier> ( <ArgumentList> ) { <Code> }
Where <Identifier>
results in some sequence of characters (a word).
So… how should our functions look like?
Usually when implementing a language we want to find that sweet spot between creative and traditional methods. We want the programmers using our language to have something to talk about, yet find our language intuitive and readable.
But because we are designing our language for educational purposes, we simply want things as direct and traditional as possible. So a function in our language will look very similar to the traditional C style function definitions:
function someFunction(firstArgument, secondArgument) { ... }
Our language doesn’t have type checking so we will use the keyword “function” to mark the start of a function definition instead of the traditional return type and skip type specifications in the argument list. Finally, we use matching curly brackets around our functions’ code blocks.
Thoughts on Implementation
In the previous tutorial we discussed adding support for variables which turned out to be fairly trivial, four simple steps:
- We added a
struct
for variable declaration - We added a parse method in our Parser for creating an instance of the struct
- We added a global in-memory storage for variables and their values (a simple dictionary)
- We implemented the interpretation process of the struct which simply stored the interpreted value of the variable declaration’s expression and stored it in our global variable dictionary using the variable name as key.
All this we could do in less than 50 lines of code. Adding a function is a similar process but with two major differences when it comes to in-memory storage (3).
- We will need our dictionary to map to a
Node
value instead of the currentFloat
, so the node can be lazily evaluated. - Our memory storage will have to keep scope in mind when handling variables.
Scope Handling
When it comes to scope handling, there are two approaches worth mentioning. We can either have lexical scoping or dynamic scoping.
Lexical Scope
This is what most programming languages use. With lexical scope resolution of variables depends on their location in the source code.
Dynamic Scope
With dynamic scope, resolution of variables depends on their usage at runtime:
var x = 1function getX() { return x}function some() { var x = 3 return getX()}// With dynamic scoping this should output 3.
// But with lexical scoping 1.
some()
There is Always a Shortcut
We could choose to go with either lexical scoping or dynamic scoping, but to keep things simple in the beginning we will take the third option of only supporting global variables and simply raise a runtime error if the user (the programmer) tries to declare a variable with an existing identifier.
Stay tuned for next week were we’ll finally get are hands dirty and write some Swift!
I hope you guys found this tutorial interesting and as always, don’t be shy to give feedback e.g. by clapping or writing a response.
P.S. Remember that you can follow me here on Medium for notifications on future tutorials.