First, we do not have functions. We only have subroutines that read or write existing memory space. The language resembles assembly language, but is actually high-level. A subroutine may define local memory (equivalent to the stack frame), but all of that is lost when the subroutine completes.
# Approximately convert fahrenheit to celsius
subroutine fahren-to-celsius {
read f32 fahren
write f32 celsius
# temp is not necessary here, but used for illustrative purposes
local f32 temp
# copy the fahren value into temp. Not that '\n' and ';' are interchangable
copy {to temp; from fahren}
# Perform the calculations
subtract {n temp; by 32}
mult {n temp; by 0.5555555555}
copy {to celsius; from temp} # Our final action
}
There is no input or output, but rather existing locations in memory that we can read and write.
The caller is responsible for creating those memory locations and using paths in memory to call subroutines
struct weather {
f32 fahrenheit
f32 celsius
f32 wind-speed
f32 latitude
f32 longitude
}
subroutine do-weather-stuff {
# The 'weather' variable will be deallocated when the subroutine finishes
local weather {
fahrenheit 64.4
wind-speed 12.2
}
# Every subroutine call has unordered named arguments mapping the name from the subroutine to some memory address
fahren-to-celsius {
fahren weather/fahrenheit
celsius weather/celsius
}
print-struct { struct weather }
}
The default scope for any memory is the length of the subroutine. You can also define a custom scope to deallocate sooner.
subroutine do-weather-stuff {
# The 'weather' variable will be deallocated when the subroutine finishes
local weather {
fahrenheit 64.4
wind-speed 12.2
} scope {
# Every subroutine call has unordered named arguments mapping the name from the subroutine to some memory address
fahren-to-celsius {
fahren weather/fahrenheit
celsius weather/celsius
}
print-struct { struct weather }
}
# Now we can do other actions, and weather no longer exists in memory.
}