▍ NEKOVA docs
NEKOVA / Docs / Core Syntax

Functions & Tasks

NEKOVA calls functions "tasks" — with default values, varargs, keyword arguments, and type hints all supported.

Basic tasks

task add(a, b):
    return a + b

show add(2, 3)

Default parameter values

task greet(name, greeting="Hello"):
    return greeting + ", " + name

show greet("Ada")               # "Hello, Ada"
show greet("Ada", "Hey")        # "Hey, Ada"

Keyword arguments

task greet(name, greeting="Hello"):
    return greeting + ", " + name

show greet(name="Ada", greeting="Hey")
show greet("Ada", greeting="Yo")   # positional + keyword mixed

Keyword arguments can fill in a later parameter while leaving an earlier one at its default:

task greet(name="World", greeting="Hello"):
    return greeting + ", " + name

show greet(greeting="Hey")   # "Hey, World"

Passing an unknown keyword, or the same parameter both positionally and by keyword, raises a clear error rather than guessing what you meant.

Variadic arguments

task total(*nums):
    let sum = 0
    for n in nums:
        sum = sum + n
    return sum

show total(1, 2, 3, 4)

Spreading arguments into a call

*rest collects extra arguments in a declaration. The mirror image, ..., spreads a list or tuple back out into separate arguments at a call:

task total(*nums):
    let sum = 0
    for n in nums:
        sum = sum + n
    return sum

let values = [1, 2, 3, 4]
show total(...values)
show total(10, ...values)

The thing being spread must be a list or tuple. It combines freely with ordinary positional arguments, as in total(10, ...values).

This is what lets a wrapper forward whatever it was given, so one decorator can wrap a function of any arity:

task total(*nums):
    let sum = 0
    for n in nums:
        sum = sum + n
    return sum

task logged(f):
    task wrapper(*args):
        show "calling with " + str(len(args)) + " args"
        return f(...args)
    return wrapper

let safe_total = logged(total)
show safe_total(5, 6)
show safe_total(1, 2, 3, 4)

... also spreads inside list and dictionary literals — [...a, ...b] — so it works the same way in all three places.

Type hints and typed tasks

task add(a: int, b: int) -> int:
    return a + b

Type hints on parameters and return values are checked at call time for int, float, str, bool, list, and dict.

Docstrings

A leading string in a task body is captured as documentation rather than treated as a statement:

task greet(name):
    """Says hello to someone."""
    show "Hello " + name

show doc(greet)   # "Says hello to someone."

Recursion

task factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)

NEKOVA tracks its own call depth (500 by default) independently of Python's stack, so an unbounded recursive call reports the exact depth reached rather than a vague "infinite recursion" guess.

Closures

Tasks capture variables from their enclosing scope by reading them, the same as any nested scope:

task make_adder(x):
    task add(y):
        return x + y
    return add

let add5 = make_adder(5)
show add5(3)    # 8
show add5(10)   # 15

Each call to make_adder creates a fresh x, so add5 and a second make_adder(100) don't interfere with each other.