WalkLang v1 Syntax Guide
This guide is the readable syntax companion to docs/SPEC.md. SPEC.md is the contract when the two disagree.
---
Files
WalkLang files use .walk.
main.walk
calc.walk
tests.walk
Comments use # outside strings.
# full-line comment
var: x = 5 # inline comment
---
Blocks
Blocks are indentation-based.
if: true
out: 'inside'
out: 'outside'
Tabs are invalid. walk fmt emits 4 spaces for each block level.
---
Commands
Command statements use keyword:.
var: x = 1
const: limit = 10
out: x
Long expressions can use a block after a command.
out:
+:
1
2
3
Draft effect calls use do:. The expression after do: must be a draft effect function, not an ordinary value expression.
imp: io
do: io.write('Loading')
do: io.write_line('done')
do: io.error_line('warning')
---
Names
Names use letters, digits, and _, and cannot be reserved words.
var: user_name = 'Walker'
var: score2 = 100
Invalid:
var: if = 1
var: 2score = 100
---
Literals
var: i = 10
var: f = 3.14
var: yes = true
var: no = false
var: name = 'Walker'
var: optional_name string? = null
Strings are single-quoted.
var: msg = 'don\'t stop'
Put an expression inside {} when a string should include a value.
imp: string
var: secretWord = 'paddle'
var: secretWordLength = string.len(secretWord)
out: 'the secret word is {secretWordLength} characters long'
Interpolation accepts display values: int, float, bool, string, and nullable string. Use doubled braces for literal braces.
out: '{{literal}}'
Strings can be indexed by zero-based byte position. The result is a one-character string.
out: 'walk'[1]
Use the string module for common string helpers.
imp: string
out: string.contains('walk', 'al')
out: string.concat('walk', 'lang')
---
Variables And Constants
var: creates a mutable binding.
var: count = 0
count = + count 1
const: creates an immutable binding.
const: max = 10
Type annotations go between the name and =.
var: count int = 0
var: price float = 9
var: name string? = null
---
Output
Use out: to print a scalar value.
out: 'hello'
out: + 1 2
out: true
Arrays and function values cannot be printed directly.
The draft io module gives explicit effect calls for output without a newline and for stderr. Import it and call those functions with do:.
imp: io
do: io.write('prefix')
do: io.write_line(' line')
do: io.error_line('error')
---
Input
Use in: to read one required line from stdin.
var: name = in:
in: may include a prompt. The prompt is a string expression written to stdout without a newline, then stdout is flushed before reading.
var: prompt = 'Name? '
var: name = in: prompt
in: strips the final line ending, preserves all other whitespace, returns '' for an empty line, accepts final input without a trailing newline, and runtime-stops if stdin reaches EOF before any text is read.
in: is an expression, so this is valid:
out: in: 'Say something: '
Typed input is separate from in:. Read text first, then parse explicitly when parsing helpers exist.
---
Prefix Math
Math is prefix only.
+ a b
- a b
* a b
/ a b
^ a b
+ and * accept 2 or more operands.
var: total = + a b c
var: product = * a b c
-, /, and ^ accept exactly 2 operands.
var: diff = - a b
var: ratio = / 5 2
var: square = ^ x 2
Negative numeric literals are valid.
var: x = -4
Use subtraction from zero to negate a name.
var: y = - 0 x
---
Grouping
Use parentheses when an expression must be one operand.
var: x = * (+ a b) (- c d)
There is no infix precedence.
---
Operator Blocks
Long prefix expressions may use operator: blocks.
var: total =
+:
subtotal
tax
fee
---
Comparisons And Boolean Logic
> a b
< a b
>= a b
<= a b
== a b
!= a b
and a b
or a b
not a
Example:
if: and (> age 18) (< age 65)
out: 'working age'
---
If / Else
if: condition
statement
else:
statement
The condition must be bool.
---
Loops
while: loops while a bool condition is true.
var: count = 0
while: < count 3
out: count
count = + count 1
repeat: loops an int count.
repeat: 3
out: 'again'
for: iterates arrays.
var: nums = [1, 2, 3]
for: n in nums
out: n
break: and continue: are valid only inside loops.
---
Functions
Parameters may have explicit types.
func: add(a int, b int) int
return: + a b
Obvious local helper functions can omit parameter and return types when the body proves the types clearly.
func: power_four(n)
return: ^ n 4
This infers n int and an int return. Use an annotation when the obvious type is not the intended type:
func: half(n float)
return: / n 2
Ambiguous parameters need annotations. Types are not inferred from later call sites.
func: identity(value) # add a type for value
return: value
Omitting the return type on a function with no value returns makes the function void.
func: say(message string)
out: message
Non-void functions must return on all paths.
---
Function Values
Named functions can be passed to typed function parameters.
func: inc(x int) int
return: + x 1
func: apply(f func(int) int, x int) int
return: f(x)
out: apply(inc, 4)
Anonymous functions and closures are not v1.1 syntax.
---
Arrays
Arrays use brackets and commas.
var: nums = [1, 2, 3]
var: names = ['a', 'b', 'c']
Arrays must be homogeneous. Empty arrays need an explicit array annotation.
nums[1] = 99
out: nums[0]
var: guessed array[string] = []
Stable native element types are int, float, bool, and string.
Use the array module for common helpers. array.push returns a new array; assign it back when you want to keep the appended value.
imp: array
var: guessed array[string] = []
guessed = array.push(guessed, 'w')
out: array.contains(guessed, 'w')
Use random.choice to pick an item from a non-empty stable native array.
imp: random
var: words = ['dog', 'cat']
out: random.choice(words)
---
Null
Use nullable string annotations when assigning null.
var: email string? = null
email = 'a@b.com'
if: != email null
out: email
---
Imports And Exports
Use imp: to import built-in modules, sibling user modules, or v3 package modules.
imp: math
out: math.sqrt(9)
User module example:
# calc.walk
func: square(x int) int
return: * x x
exp: square
# main.walk
imp: calc
out: calc.square(5)
Only names listed with exp: are public through the namespace.
Package module example:
imp: geometry.core
out: geometry.core.double(3)
Draft process and IO helpers are available in the current compiler, but they are not part of the stable v1.9 syntax contract yet.
imp: process
out: process.arg_count()
out: process.cwd()
do: process.exit(0)
Draft recoverable text input and parse helpers return result structs:
imp: io
imp: parse
var: line = io.read_line()
var: age = parse.int(line.value)
if: age.ok
out: age.value
---
Tests
Use test: and assert: with walk test.
func: add(a int, b int) int
return: + a b
test: 'add works'
assert: == add(2, 3) 5
assert: requires a bool expression.
testing.assert(bool) can wrap that bool expression when you want a namespaced stdlib assertion helper.
imp: testing
test: 'wrapped assertion works'
assert: testing.assert(true)
---
Reserved Words
var const out if else while for repeat break continue
func return imp exp true false null and or not in test assert
---
Complete Example
imp: math
func: distance(x1 float, y1 float, x2 float, y2 float) float
return:
math.sqrt(
+:
^ (- x2 x1) 2
^ (- y2 y1) 2
)
var: d = distance(0, 0, 3, 4)
if: == d 5
out: 'distance is 5'
else:
out: 'distance is not 5'