WalkLang v1 Specification

This document is the stable WalkLang language contract for the v1 line. v1.9 keeps the v1.8 compatibility policy and adds string interpolation for display text.

Core rule:

If it is not in SPEC.md, it is not stable WalkLang.
If the compiler disagrees with SPEC.md, either the compiler or the spec must change.

---

1. Source Files

WalkLang source files are UTF-8 text files ending in .walk.

main.walk

A source file contains zero or more statements. Blank lines are ignored. # starts a comment outside strings.

# comment
var: x = 1 # inline comment
out: x

---

2. Compilation Contract

The stable compiler pipeline is:

.walk source -> lexer -> parser -> AST -> type checker -> C emitter -> native executable

The walk build command writes generated C next to the executable path unless --emit-c chooses another path. Native builds use cc by default and link with -lm.

---

3. Blocks And Indentation

Indentation owns blocks.

if: true
    out: 'inside'
out: 'outside'

Rules:

  • spaces define indentation
  • tabs are invalid
  • a greater indentation level starts a child block
  • a lower indentation level closes blocks
  • formatter output uses 4 spaces per block level

---

4. Lexical Rules

Stable tokens:

names       letters, digits, and _
numbers     int and float literals
strings     single-quoted strings
symbols     ( ) [ ] , : = + - * / ^ > < ? .
comments    # outside strings

Strings support these escapes:

\' single quote
\\ backslash
\n newline
\t tab

Strings support interpolation with {expression}. Interpolation formats int, float, bool, string, and nullable string values into the surrounding string.

var: name = 'Walker'
var: length = string.len(name)
out: 'name {name} has {length} characters'

Use doubled braces for literal braces:

out: '{{name}}'

Double-quoted strings are not valid WalkLang.

---

5. Statements

Stable statements:

imp:
exp:
var:
const:
assignment
out:
test:
assert:
func:
return:
if:
else:
while:
repeat:
for:
break:
continue:

Draft implemented statement:

do:

do: is the current compiler's draft effect-call statement. It is documented so draft IO programs can be tested, but it is not part of the v1.9 stable compatibility contract yet.

One statement appears on each physical line. Semicolons are not part of v1.1 syntax.

---

6. Reserved Words

These words cannot be user-defined names:

var const out if else while for repeat break continue
func return imp exp true false null and or not in test assert

---

7. Types

Stable value types:

int
float
bool
string
array[T]
func(T...) R

void is used internally for functions with no return type. It is not a value type.

null is stable for nullable strings in native v1.1 programs:

var: name string? = null

Other nullable scalar forms are not part of the v1.1 stable native contract.

---

8. Type Inference And Type Lock

var: and const: infer a type from their initializer unless an explicit type annotation is present.

var: x = 1
var: y float = 1
const: name = 'Walker'

Once a name is declared, its type is locked.

var: x = 1
x = 2      # ok
x = 'two'  # type error

int values may initialize or assign to float values. Other implicit conversions are not stable.

Function parameters and return types may also be inferred from the function body when the result is local and unambiguous. Omitted function parameter types are not inferred from call sites.

---

9. Variables And Constants

var: creates a mutable binding.

var: count = 0
count = + count 1

const: creates an immutable binding.

const: limit = 10

Reassigning a const: name is a type error. Assigning through an indexed target rooted at a const: array is also a type error.

---

10. Output

out: writes one value and a trailing newline.

out: 'hello'
out: + 1 2

Stable output types:

int
float
bool
string
nullable string

Arrays, functions, and void values cannot be output.

---

11. Input

in: reads one required line from stdin and returns a string.

var: name = in:

in: may take an optional prompt expression. The prompt must be string.

var: prompt = 'Name? '
var: name = in: prompt

Prompt behavior:

write prompt to stdout
add no newline
flush stdout before reading

Input behavior:

reads from stdin only
consumes exactly one line
strips the final \n or \r\n line ending
preserves all other whitespace
returns '' for an empty line
has no language-level line length limit
returns final unterminated input as a line
runtime-stops if EOF happens before any text is read
runtime-stops on stdin read failure or allocation failure

in: is an expression and may appear anywhere an expression is valid.

out: in: 'Say something: '

Typed input is not part of in:. Read text first, then parse it explicitly when a parse API exists.

---

12. Expressions

Stable expressions:

literals
names
in:
prefix operators
grouped expressions
function calls
qualified module calls
array literals
index expressions
block expressions under commands

Index expressions work on arrays and strings.

var: nums = [1, 2, 3]
out: nums[0]
out: 'walk'[1]

String indexing returns a one-character string by zero-based byte index and runtime-stops when the index is out of range.

Grouping uses parentheses:

var: x = * (+ 1 2) (- 9 4)

---

13. Operators

Numeric operators:

+  2 or more args
*  2 or more args
-  exactly 2 args
/  exactly 2 args, returns float
^  exactly 2 args

Comparison operators:

> < >= <= == !=

Boolean operators:

and  2 or more bool args
or   2 or more bool args
not  exactly 1 bool arg

Negative numeric literals are supported:

var: x = -4

Unary negation of variables is not v1.1 syntax. Use subtraction from zero:

var: y = - 0 x

---

14. Arrays

Array literals are homogeneous. Empty arrays need an explicit array annotation.

var: nums = [1, 2, 3]
var: words = ['a', 'b']
var: guessed array[string] = []

Stable native array element types:

int
float
bool
string

Indexing is zero-based.

out: nums[0]
nums[1] = 9

Nested array emission is not a stable v1.8 native feature.

---

15. Functions

Function declarations may use typed parameters.

func: add(a int, b int) int
    return: + a b

Obvious local functions may omit parameter and return types. Whole-number arithmetic infers int; float contexts infer float; boolean contexts infer bool.

func: power_four(n)
    return: ^ n 4

If a parameter type cannot be inferred from the function body, the parameter needs an explicit annotation. Function types are not inferred from later call sites.

func: identity(value) # type error until value is annotated
    return: value

If a return type is omitted and the function has no return: value, the function returns void and should end normally.

func: say(message string)
    out: message

return: requires a value and is valid only inside a function with a compatible return type.

Non-void functions must return on all paths.

---

16. Function Values

Named functions may be passed as values.

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.

---

17. Control Flow

if: conditions must be bool.

if: > age 18
    out: 'adult'
else:
    out: 'minor'

while: conditions must be bool.

while: < count 3
    count = + count 1

repeat: counts must be int.

repeat: 3
    out: 'again'

for: iterates arrays.

for: n in nums
    out: n

break: and continue: are valid only inside loops.

---

18. Modules

Built-in modules:

math
string
array
time
random
testing

Draft built-in modules in the current compiler:

io
parse
process

Draft modules are importable for experimentation, but they are not compatibility-protected by the v1.9 stable contract.

Stable built-in functions:

math.sqrt(number) -> float
math.pow(number, number) -> float
string.len(string) -> int
string.at(string, int) -> string
string.contains(string, string) -> bool
string.concat(string, string) -> string
array.len(array[T]) -> int
array.contains(array[T], T) -> bool
array.push(array[T], T) -> array[T]
time.now() -> int
random.int(int, int) -> int
random.choice(array[T]) -> T
testing.assert(bool) -> bool

array.contains, array.push, and random.choice are stable for arrays whose elements are int, float, bool, or string.

User modules are sibling .walk files imported by bare module name.

imp: calc
out: calc.square(5)

calc resolves to calc.walk in the importing file's directory.

User module rules:

  • imported calls stay namespaced
  • only functions listed with exp: are callable from another file
  • module files may contain only imp:, func:, and exp: at top level
  • import cycles are errors

---

19. Tests

test: defines a test block for walk test.

test: 'add works'
    assert: == add(2, 3) 5

assert: requires a bool expression. Failed assertions make the generated test executable exit non-zero.

testing.assert(bool) is a stable v1.3 stdlib helper that returns its bool argument unchanged. It is intended to be paired with assert::

imp: testing

test: 'wrapped assertion works'
    assert: testing.assert(true)

Normal walk build ignores test: blocks.

---

20. Formatter

walk fmt emits stable spacing and indentation:

var:x=+ 1 2

becomes:

var: x = + 1 2

Formatter output uses 4 spaces for indentation.

---

21. Diagnostics

Compiler diagnostics use this shape:

file.walk:line:column: category: message

The command-line display may add a source snippet, caret, and focused suggestion under that stable first line:

main.walk:1:16: type error: age is int, got string

var: age int = 'old'
               ^ string cannot initialize int

Stable categories:

syntax error
type error
name error
module error
warning
internal error

Warnings do not fail by default. --warnings=error promotes warnings to errors. Stable v1.4 warnings cover shadowing an outer name and unreachable statements after return:, break, or continue.

---

22. Non-Goals

These are not stable v1 features:

classes
structs
methods
generic functions
inheritance
interfaces
traits
anonymous functions
closures
try/catch
networking
package manager
debugger
full LSP
file/json/matrix stdlib APIs
empty arrays
nested arrays in native output

---

23. Minimal Valid Program

out: 'hello'

---

24. Representative v1.1 Program

imp: math

func: add(a int, b int) int
    return: + a b

func: distance(x1 float, y1 float, x2 float, y2 float) float
    return:
        math.sqrt(
            +:
                ^ (- x2 x1) 2
                ^ (- y2 y1) 2
        )

var: nums = [1, 2, 3]

for: n in nums
    out: add(n, 10)

var: d = distance(0, 0, 3, 4)

if: == d 5
    out: 'distance is 5'
else:
    out: 'distance is not 5'