My blog post: Creating the Golfcart Programming Language
Golfcart is a minimal programming language inspired by Ink, JavaScript, and Python – implemented in Go. It's a toy programming language that I built to use for Advent of Code 2021. Another motivation was to learn how to write an interpreter from scratch.
// Here's the classic interview question FizzBuzz
for i = 1; i < 101; i = i + 1 {
log(if i % 3 == 0 and i % 5 == 0 {
"FizzBuzz"
} else if i % 3 == 0 {
"Fizz"
} else if i % 5 == 0 {
"Buzz"
} else {
str(i)
})
}
Golfcart is a dynamic strongly typed language with support for bools, strings, numbers (float64), lists, dicts, and nil (null). There is full support for closures and functions can alter any variable in a higher scope.
counter = () => {
n = 0
() => {
n = n + 1
n
}
}
my_counter = counter()
my_counter() // 1
assert(my_counter(), 2)
For Golfcart, I began with a desire to design a small programming language that didn't use semi-colons or automatic semicolon insertion. So, no statements, and everything should be an expression that evaluates to a value. For example:
if/else if/else
evaluates to the successful branch- A variable declaration evaluates to the value
- Setting a dict value evaluates to the value
- A for loop evaluates to the number of times the condition expression succeeded
assert(
// This runs five times
for i = 0; i < 5; i = i + 1 {}, 5
)
A Golfcart program is a series of expressions. Line breaks are optional and there are no semi-colons. The final expression is sent to stdout.
a = 1 b = 2 assert(a + b, 3) // A successful assert() evaluates to nil
There are seven types. A type-check can be performed with type()
.
// Bools
true or false
true and true
// Numbers
1
1.1 + 1.1 // 2.2
// Strings
"multi-line
string"
"1" + "2" // "12"
// Lists
[1, 2]
nums = [3, 4]
nums.append(5) // [3, 4, 5]
[0] + [1] // [0, 1]
// Dicts
{a: 1} // Accessed by `.a` or `["a"]` like JavaScript
{b: n => n + 1} // Values can be any type
keys({a: 1}) // ["a"]
// Functions
_ => nil // All user-defined functions are anonymous, assignable by variable
n => n + 1
sum = (x, y) => x + y
// Nil
nil
nil == nil // true
The Fibonacci sequence.
// Naive
t = time()
fib = n => if n == 0 {
0
} else if n == 1 {
1
} else {
fib(n - 1) + fib(n - 2)
}
fib(20)
log("fib: " + str(time() - t))
// With memoization
t = time()
cache = {"0": 0, "1": 1}
fib_memo = n => if cache[n] != nil {
cache[n]
} else {
cache[n] = fib_memo(n - 1) + fib_memo(n - 2)
}
fib_memo(20)
log("fib_memo: " + str(time() - t))
For more detailed examples, see:
(All the above are used as part of Golfcart's test suite)
Let's talk about stack frames in Golfcart. A stack frame is a map of variables in scope. It's a recursive structure, every stack frame has a parent apart from the global frame. All functions are anonymous and create closures. Any variable referenced in a higher scope can be altered. Examples explain this better than words.
a = 1
a_function = () => a = 2 // Closure created
a_function() // When called, `a` is changed
a // 2
if true {
// `b` is not defined in a higher scope
// So, `b` is declared only within this scope
b = 3
}
b // Error: cannot find value for key 'b'
c = nil
if true {
// This assignment recursively looks in higher scopes for `c`
// it's found and that value is altered
c = 4
}
c // 4
Pass a Golfcart program as the first command-line argument
$ ./golfcart-linux program.golf
Run the binary with no command-line arguments to open the REPL.
$ ./golfcart-linux
.-::":-.
.'''..''..'.
/..''..''..''\
;'..''..''..''.;
;'..''..''..'..;
\..''..''..''/
'.''..''...'
'-..::-' Golfcart v0.1
λ
Use -ebnf
to print the Extended Backus–Naur form grammar to stdout and quit.
Use -version
to print the version to stdout and quit.
Create releases.
./build.sh
Run all tests (they also run via GitHub Action on commit).
go test ./...
More than welcome! Raise an issue with a bug report/feature proposal and let's chat.
MIT.