A functional AWK dialect with first-class functions, arrays and lexical scope.
Not all AWK features are supported, but should be enough of them to be dangerous.
function make_adder(k) {
return (x) => x + k # lambdas (braces optional for single expressions)
}
function make_range(n) {
for (i = 1; i <= n; i++) { # locals don't leak!
arr[length(arr)] = i
}
return arr # returning arrays!
}
END {
src = make_range(3)
add5 = make_adder(5)
result = src |> map(add5) # pipelines, fns as values, map/filter/reduce
print(result) # printing arrays
}
# --> [6, 7, 8]Warning
I am ashamed surprised to say the implementation of this interpreter is 100%
written by a LLM. I've only steered it via the test suite, and it is passing
it (alongside compatibility with GAWK, where tested).
Unfortunately this means that I am not familiar with the codebase. Do not rely on this in production. I'm only planning on using this on Advent of Code myself.
- Arrays as First-Class Values - create, pass, and return arrays
- Functions as First-Class Values - pass functions as arguments
- Anonymous Functions - arrow syntax
(x) => { x * 2 } - Functional Pipeline Operator - compose operations with
|> - Higher-Order Functions - map, filter, and custom combinators
- Lexical Scope - local-by-default, no action at a distance
- Explicit Globals - declare globals with
globalkeyword - Array Destructuring - pluck data from arrays
Arrays can be created, passed to functions, and returned from functions.
Regular arrays:
numbers = [1, 2, 3, 4, 5]
sum(numbers)Nested arrays:
matrix = [[1, 2], [3, 4], [5, 6]]
for (i in matrix) {
row = matrix[i]
print row[0], row[1]
}Associative arrays:
scores = ["alice" => 95, "bob" => 87, "carol" => 92]
print scores["alice"]Top-level functions can be used as values:
function double(x) { return x * 2 }
function apply(func, value) { return func(value) }
BEGIN {
result = apply(double, 21) # Returns 42
print result
}Create inline functions with arrow syntax:
Full syntax:
add = (a, b) => {
c = a + b
return c
}
print add(10, 32) # Prints 42Shorthand for single expressions (braces optional):
# Both of these are equivalent:
square = (x) => x * x
square = (x) => { x * x } # braces still work, implicit return
triple = (x) => x * 3
numbers = [1, 2, 3, 4, 5]
map(square, numbers) # [1, 4, 9, 16, 25]Note: For single expressions, braces are optional. If braces are used with a single expression statement, it will be implicitly returned. For multi-statement lambdas, braces are required.
Chain operations elegantly with |>. The left side becomes the rightmost argument of the function on the right:
# These are equivalent:
doubled = nums |> filter((n) => n % 2 == 0) |> map((n) => n * 2)
doubled = map((n) => n * 2, filter((n) => n % 2 == 0, nums))
# More examples (braces optional for single expressions):
result = [1, 2, 3, 4, 5]
|> filter((x) => x % 2 == 0) # [2, 4]
|> map((x) => x * x) # [4, 16]
|> reduce((acc, x) => acc + x, 0) # 20
# Braces still work and are required for multi-statement lambdas:
result = [1, 2, 3]
|> map((x) => {
doubled = x * 2
return doubled
})Combine arrays and functions for powerful data processing:
# map, filter, reduce, sum are built-in (but you could have written them youself)
# split, match return an array instead of taking one as an "out-parameter"
BEGIN {
# Works with regular arrays
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
doubled = nums
|> filter((n) => n % 2 == 0)
|> map((n) => n * 2)
print doubled # [4, 8, 12, 16, 20]
# Works with associative arrays too
scores = ["alice" => 95, "bob" => 67, "carol" => 88]
passing = scores |> filter((s) => s >= 70)
print passing # [95, 88]
}Variables are local by default. No spooky action at a distance.
function outer(x) {
y = x + 10 # Local to outer()
inner = (z) => {
w = z + 5 # Local to inner()
return w
}
return inner(y)
}
BEGIN {
result = outer(20) # Returns 35
# y and w don't exist here
}Arrays and scalars are passed to functions by-value (the function cannot change their contents, it can only return a new array). On the other hand global values are mutable.
Globals must be declared in the BEGIN block:
BEGIN {
global total, count, max_value
}
{
total = total + $1
count = count + 1
if ($1 > max_value) {
max_value = $1
}
}
END {
print "Average:", total / count
print "Maximum:", max_value
}Arrays can be destructured when receiving function call results or assigning from arrays:
Simple destructuring:
[_, x, y] = match(/-x([0-9]+)-y([0-9]+)$/, $1)
[a, b, c] = "a-b-c" |> split("-")Nested destructuring:
my_nested = [[1, 2], [3, 4]]
[[x, y], [z, w]] = my_nested