Its own words. Its own grammar. Its own machine.

Venutian has its own vocabulary, its own grammar, its own bytecode instruction set and its own virtual machine, all specified and implemented from scratch. The syntax reads close to plain English: make for a function, give for a return, when for a branch, repeat while for a loop. It is still a real, statically checkable grammar.

shape Account    owner: Text    balance: Numberbehavior Account    make deposit(self, amount)    make withdraw(self, amount)    make describe(self)
The Account shape from examples/behaviors.ven. Each mark below is one of its fields or behaviors, in reading order.
Skip the Account diagram
The Account shape, field by field Five marks, one for each field and method of the real Account shape from examples/behaviors.ven, highlighted in reading order. owner: Text balance: Number deposit(self, amount) withdraw(self, amount) describe(self)

shape Account owner: Text balance: Number behavior Account make deposit(self, amount) self.balance = self.balance + amount give self.balance make withdraw(self, amount) when amount > self.balance raise "insufficient funds" self.balance = self.balance - amount give self.balance make describe(self) give self.owner + ": " + as_text(self.balance) Each mark above is one field or one behavior of Account: Who holds the account. What it currently holds. Adds to the balance and gives the new total. Raises a fault before it ever goes negative. Reads the shape back as one line of text.

Vocabulary at a glance

The whole surface grammar, word by word.
WordWhat it does
makeDeclares a function.
giveReturns a value from the enclosing function.
when / otherwiseA conditional branch and its alternate.
repeat whileLoops while a condition holds (alongside repeat N times and repeat for x in y).
choose / is / wherePattern based selection: choose evaluates a subject once, then compares it against each is pattern in order, plain value equality, a type pattern, or Tuple, List and shape destructuring, each optionally narrowed with a where guard.
shapeDeclares a record type with named, typed fields, instantiated positionally and compared by value.
behaviorAttaches one or more make methods to an already declared shape.
attempt / rescue / raiseattempt opens a block whose faults are caught by rescue; raise signals a fault carrying any Venutian value; faults raised by user code and built in runtime faults share one mechanism.
withOpens a context manager block, backed by a behavior's operator_enter and operator_exit, so cleanup runs on every exit path.
spawn / awaitspawn runs a function call on a real, separate OS thread and returns a Task immediately; await blocks until it finishes, returning its result or re-raising its fault.
share / protectshare declares a value shared across threads with its own lock; protect holds that lock for a block, released unconditionally.
emitSuspends the current function as a generator, handing a value to whoever is stepping it.
wrapPlaced above a make, rebinds it to a decorator function, the same power as a decorator, spelled as an English word.
useImports another file's make, tool make, shape and behavior declarations by name, with as name for qualified access and show a, b for a selective import.

The twelve features that make the case

Control flow and concurrency

Threads, matching and errors that behave the way the code reads.

Real threads

Concurrency in Venutian is real OS level threading, not a simulated event loop. spawn starts a real thread running the call right now and returns a Task immediately; await blocks until it finishes. Shared state crosses threads through share and protect: real mutual exclusion, not a cooperative promise.

examples/concurrency.ven
:: 'spawn call_expr' starts a real OS thread running the call right now
:: and returns a Task immediately, without waiting. 'await task' blocks
:: until it finishes, returning its result, or re-raising its fault.

make add(a, b)
    give a + b

t = spawn add(2, 3)
print kind(t)
print await t

:: several tasks make genuine concurrent progress
make slow_double(n)
    sleep(0.05)
    give n * 2

t1 = spawn slow_double(1)
t2 = spawn slow_double(2)
t3 = spawn slow_double(3)
print await t1
print await t2
print await t3

:: a fault inside a spawned task surfaces at 'await', not silently
make risky()
    give 1 / 0

failing_task = spawn risky()
attempt
    await failing_task
rescue e
    print "rescued:", e

:: 'share'/'protect': real mutual exclusion across real threads
share counter = 0

make increment_many(count)
    i = 0
    repeat while i < count
        protect counter
            counter = counter + 1
        i = i + 1

tasks = []
repeat 8 times
    tasks = appended(tasks, spawn increment_many(500))

repeat for t in tasks
    await t

:: exactly 4000 (8 * 500) every time. 'protect' prevents lost updates
print counter
Output
Task
5
2
4
6
rescued: Division by zero.
4000

Pattern matching with guards

Matching reads like plain English: is n: Integer where n >= 90 names the pattern, its type and its guard in one line, evaluated top to bottom against a subject computed once. It handles plain values, inheritance aware type patterns and OR patterns, and it powers the grade function in the example.

examples/pattern_matching_extended/guards.ven
make grade(score)
    choose score
        is n: Integer where n >= 90
            give "A"
        is n: Integer where n >= 80
            give "B"
        is n: Integer where n >= 70
            give "C"
        is : Integer
            give "F"
        otherwise
            give "not a number"

print grade(95)
print grade(82)
print grade(71)
print grade(50)
print grade("ninety")
Output
A
B
C
F
not a number

Destructuring

The same choose / is machinery pulls Tuples, Lists and shape fields apart in one pattern, nested to any depth, is (x, y) for a pair, is (x, y, z) for a triple, with an otherwise branch for anything else.

examples/pattern_matching_extended/destructuring.ven
:: 'choose'/'is' destructuring patterns: 'is (a, b, ...)' for a Tuple
:: or List of exactly that many elements, and 'is ShapeName{field, ...}'
:: for a shape/record's fields, both binding new local variables for
:: just that one branch. See examples/pattern_matching_extended/
:: type_patterns.ven's header for why this file lives outside
:: examples/ itself.

make classify_pair(value)
    choose value
        is (x, y)
            print "a 2-element Tuple or List:", x, "and", y
        is (x, y, z)
            print "a 3-element Tuple or List:", x, y, z
        otherwise
            print "not a 2- or 3-element Tuple/List:", value

classify_pair((1, 2))
classify_pair([3, 4])
classify_pair((1, 2, 3))
classify_pair((1,))
classify_pair("nope")

shape Point
    x: Integer
    y: Integer

shape Circle inherits Point
    radius: Number

make describe_shape(value)
    choose value
        is Circle{x, y, radius}
            print "Circle at (", x, ",", y, ") radius", radius
        is Point{x, y}
            print "Point at (", x, ",", y, ")"
        otherwise
            print "not a Point or Circle"

describe_shape(Point(1, 2))
describe_shape(Circle(0, 0, 5.0))
describe_shape("not a shape")

:: A shape pattern's field list can be a subset of the real fields, or
:: even empty: 'is Point{}' is a pure type test, identical to
:: 'is : Point'.
make has_origin_field(value)
    choose value
        is Point{}
            print "it's at least a Point (fields not inspected)"
        otherwise
            print "not a Point at all"

has_origin_field(Circle(1, 1, 2.0))
has_origin_field(42)
Output
a 2-element Tuple or List: 1 and 2
a 2-element Tuple or List: 3 and 4
a 3-element Tuple or List: 1 2 3
not a 2- or 3-element Tuple/List: (1,)
not a 2- or 3-element Tuple/List: nope
Point at ( 1 , 2 )
Circle at ( 0 , 0 ) radius 5.0
not a Point or Circle
it's at least a Point (fields not inspected)
not a Point at all

Typed error handling

A rescue clause matches a fault by its shape, not by its message. Name a shape to catch that fault, or write a plain rescue to catch whatever else propagates, so a caller can tell a validation fault from an arithmetic one without parsing text.

examples/typed_rescue.ven
shape ValidationError
    message: Text

shape TimeoutError
    seconds: Integer

make risky(n)
    when n < 0
        raise ValidationError("must be positive")
    when n > 100
        raise TimeoutError(30)
    give n * 2

make try_it(n)
    attempt
        give risky(n)
    rescue err: ValidationError
        give "validation: " + err.message
    rescue err: TimeoutError
        give "timeout after " + as_text(err.seconds) + "s"
    rescue err
        give "unknown: " + as_text(err)

print try_it(5)
print try_it(-1)
print try_it(200)
Output
10
validation: must be positive
timeout after 30s

Cleanup that always runs

A with block calls the entry and exit methods of a behavior, a stronger guarantee than a plain finally: cleanup runs through an early give, a raise or the ordinary end of the block alike.

examples/context_managers.ven
:: A 'with' block guarantees a shape's 'operator_exit' hook runs when
:: the block is left: normally, through an early 'give', or because a
:: fault propagated through it. A stronger guarantee than 'finally'.

shape Resource
    name: Text

behavior Resource
    make operator_enter(self)
        print "entering " + self.name
        give self.name
    make operator_exit(self)
        print "exiting " + self.name

with Resource("database") as r
    print "using " + r

:: 'as name' is optional
with Resource("cache")
    print "warmed up"

:: operator_exit still runs even if the body raises
attempt
    with Resource("risky") as r2
        raise "something went wrong"
rescue e
    print "caught:", e

:: nested 'with' blocks unwind innermost-first
with Resource("outer") as o
    with Resource("inner") as i
        print o + "/" + i
Output
entering database
using database
exiting database
entering cache
warmed up
exiting cache
entering risky
exiting risky
caught: something went wrong
entering outer
entering inner
outer/inner
exiting inner
exiting outer

Numbers

Exact decimals, complex values and integers with a stated width.

Exact decimals

Decimal is exact base 10 arithmetic built into the language, not a library patch. decimal("0.1") + decimal("0.2") == decimal("0.3") holds exactly, closing the "0.1 + 0.2 != 0.3" problem that host doubles carry.

examples/decimal_numbers.ven
:: The native Decimal value kind, a REAL, arbitrary-precision base-10
:: number type distinct from Number (a host double, with the usual
:: binary-float rounding surprises this closes: '0.1 + 0.2 != 0.3' on
:: Number, but 'decimal("0.1") + decimal("0.2") == decimal("0.3")').
:: Built with 'decimal(x)' from Text (parsed exactly, digit-for-digit)
:: or Integer (exact); '+ - * / % // **' and unary '-' are ordinary
:: native VM operators, exactly like Integer/Number arithmetic.
::
:: Reference-runtime (Python) only: see this example's own header
:: comment in the merge report for why native/C parity was not
:: attempted for this value kind (unlike Complex, which has it).

a = decimal("0.1")
b = decimal("0.2")

print a                       :: 0.1
print b                       :: 0.2
print a + b                   :: 0.3
print (a + b) == decimal("0.3")  :: yes: exact, unlike Number
print (0.1 + 0.2) == 0.3       :: no: the classic float bug, unchanged

print kind(a)                  :: Decimal

:: A Decimal preserves significant trailing zeros, real, exact digits,
:: not a display trick.
print decimal("1.50")          :: 1.50
print decimal("1.50") == decimal("1.5")  :: yes (same value, different digits)

:: Full arithmetic, all exact base-10.
print decimal("10") / decimal("4")   :: 2.5
print decimal("10") // decimal("3")  :: 3
print decimal("10") % decimal("3")   :: 1
print decimal("2") ** 10              :: 1024
print -decimal("5.25")                :: -5.25
print absolute(decimal("-3.5"))       :: 3.5

:: A plain Integer promotes exactly (no precision to lose); a Number
:: (float) is deliberately REFUSED, mixing it in directly would
:: reintroduce the very rounding problem Decimal exists to avoid.
print decimal("3") + 1          :: 4
attempt
    print decimal("3") + 1.0
rescue e
    print "refused: " + as_text(e)

:: The explicit, honest escape hatch: 'decimal_from_number' shows a
:: float's OWN exact binary value, usually a long, ugly expansion.
:: That ugliness is the float's real value, not a bug.
print decimal_from_number(0.1)

:: Conversion, rounding, and precision control.
print decimal_to_text(a)         :: 0.1
print decimal_to_number(a)       :: 0.1
print decimal_round(decimal("3.14159"), 2)  :: 3.14
print decimal_precision()        :: 28 (the default; see decimal_set_precision)

:: Full ordering (unlike Complex), Decimal is a genuinely ordered type.
print decimal("1") < decimal("2")   :: yes
print decimal("3.0") == 3            :: yes: cross-type equality with a
                                      ::   plain Integer, matching this
                                      ::   runtime's own 5 == 5.0

:: Division by zero and unparsable Text both raise clean, catchable
:: errors.
attempt
    print decimal("1") / decimal("0")
rescue e
    print "div0: " + as_text(e)

attempt
    print decimal("not a number")
rescue e
    print "parse: " + as_text(e)

Complex numbers

Complex numbers use ordinary operators. z1 = complex(3, 4) then + - * / behave the way the mathematics says they should, with no operator overload boilerplate to write.

examples/complex_numbers.ven
:: The native Complex value kind, a REAL value kind (runtime/values.py::
:: Complex / native/venutian.h's T_COMPLEX), not the '(real, imag)' Tuple
:: convention 'stdlib/quantum.ven' and this runtime's own 'cmath_*'
:: builtins still use. Built with 'complex(real, imag)'; '+ - * /' and
:: unary '-' are ordinary native VM operators, exactly like Integer/
:: Number arithmetic: no 'behavior'/operator-overload plumbing involved.

z1 = complex(3, 4)
z2 = complex(1, -2)

print z1                    :: 3.0 + 4.0i
print z2                    :: 1.0 - 2.0i

print z1 + z2                :: 4.0 + 2.0i
print z1 - z2                :: 2.0 + 6.0i
print z1 * z2                :: 11.0 - 2.0i
print z1 / z2                :: -1.0 + 2.0i
print 0 - z1                 :: -3.0 - 4.0i (unary negate)

:: A plain Integer/Number promotes automatically on either side.
print z1 + 1                 :: 4.0 + 4.0i
print 2 * z1                 :: 6.0 + 8.0i

:: Magnitude, real/imaginary part access, conjugate.
print absolute(z1)           :: 5.0  (a real 3-4-5 triangle)
print complex_real(z1)       :: 3.0
print complex_imag(z1)       :: 4.0
print complex_conjugate(z1)  :: 3.0 - 4.0i

:: kind()/equals/hashing: real value-kind behavior, not a Tuple.
print kind(z1)               :: Complex
print z1 == complex(3, 4)    :: yes
print z1 == z2                :: no

:: A Complex with a zero imaginary part compares (and hashes) equal to
:: the plain Integer/Number it equals, matching this runtime's own
:: pre-existing Integer/Number cross-type equality (5 == 5.0), and
:: matching Python's own real 'complex' type, which has this same
:: property.
print complex(3, 0) == 3     :: yes

registry = new_map()
map_set(registry, complex(1, 0), "one")
print map_get(registry, 1)   :: one: same Map slot

:: Non-negative Integer exponents work via repeated squaring.
print z1 ** 2                :: -7.0 + 24.0i

:: Bridges to the pre-existing '(real, imag)' Tuple convention (and so
:: to 'cmath_*'), for interop with code that still uses that shape.
pair = complex_to_tuple(z1)
print pair                   :: (3.0, 4.0)
print cmath_abs(pair)        :: 5.0
print complex_from_tuple((5.0, 6.0))  :: 5.0 + 6.0i

:: Real, documented scope limits: no ordering, no '%'/'//' on Complex,
:: and '**' only for a non-negative Integer exponent, each raises a
:: clean, catchable error rather than doing something silently wrong.
attempt
    print z1 < z2
rescue e
    print "ordering: " + as_text(e)

attempt
    print z1 % z2
rescue e
    print "modulo: " + as_text(e)

attempt
    print z1 ** complex(0, 1)
rescue e
    print "fractional/complex power: " + as_text(e)
Output
3.0 + 4.0i
1.0 - 2.0i
4.0 + 2.0i
2.0 + 6.0i
11.0 - 2.0i
-1.0 + 2.0i
-3.0 - 4.0i
4.0 + 4.0i
6.0 + 8.0i
5.0
3.0
4.0
3.0 - 4.0i
Complex
yes
no
yes
one
-7.0 + 24.0i
(3.0, 4.0)
5.0
5.0 + 6.0i
ordering: Cannot order a Complex.
modulo: '%' is not defined for Complex.
fractional/complex power: '**' on Complex only supports a non-negative Integer exponent.

Sized integer literals

Venutian lexes fixed width typed integers: x = 42i32 becomes a TYPED_INTEGER token carrying the value and its width, (42, "i32"), the groundwork for systems level integers beyond one generic Integer.

Literal
x = 42i32

Structure and meaning

How data, laziness, decoration and copying are specified.

Shapes and behaviors

Data and methods are separate on purpose: shape declares named, typed fields; behavior attaches methods to that shape, including inheritance. An Account shape with an owner and a balance gets its deposit method through a behavior Account block, not a class body that mixes both.

examples/behaviors.ven
shape Account
    owner: Text
    balance: Number

behavior Account
    make deposit(self, amount)
        self.balance = self.balance + amount
        give self.balance

    make withdraw(self, amount)
        when amount > self.balance
            raise "insufficient funds"
        self.balance = self.balance - amount
        give self.balance

    make describe(self)
        give self.owner + ": " + as_text(self.balance)

a = Account("Ada", 100)
print a.describe()
a.deposit(50)
print a.describe()
a.withdraw(30)
print a.describe()

attempt
    a.withdraw(10000)
rescue error
    print "blocked: " + error

transfer = a.deposit
transfer(1)
print a.describe()
Output
Ada: 100
Ada: 150
Ada: 120
blocked: insufficient funds
Ada: 121

Lazy generators

Calling a generator function runs no code at all: it returns a Generator value that executes only up to its next emit each time it is stepped, through the two function has_next / next_value protocol, lazy, stateful iteration with no hidden machinery.

examples/generators.ven
:: A function whose body contains 'emit' becomes a generator: calling
:: it doesn't run any code at all, it returns a Generator value that
:: only executes up to its next 'emit' each time it's stepped, via
:: 'has_next'/'next_value'.

make count_to(n)
    i = 1
    repeat while i <= n
        emit i
        i = i + 1

g = count_to(5)
print kind(g)

repeat while has_next(g)
    print next_value(g)

:: calling the function again gives a fresh, independent generator
total = 0
g2 = count_to(5)
repeat while has_next(g2)
    total = total + next_value(g2)
print total

:: generators can be nested inside other functions...
make make_squares_generator(n)
    make squares(limit)
        i = 0
        repeat while i < limit
            emit i * i
            i = i + 1
    give squares

gen_factory = make_squares_generator(0)
squares_gen = gen_factory(4)
repeat while has_next(squares_gen)
    print next_value(squares_gen)

:: ...or attached to a shape as a method
shape Countdown
    start: Integer

behavior Countdown
    make ticks(self)
        n = self.start
        repeat while n > 0
            emit n
            n = n - 1

cd = Countdown(3)
ticker = cd.ticks()
repeat while has_next(ticker)
    print next_value(ticker)

:: a plain 'give' inside a generator ends it early, exactly like
:: Python's own 'return' inside a generator does
make first_even(xs)
    repeat for x in xs
        when x % 2 == 0
            emit x
            give nothing

evens = first_even([1, 3, 4, 5, 6])
print next_value(evens)
print has_next(evens)
Output
Generator
1
2
3
4
5
15
0
1
4
9
3
2
1
4
no

Decorators in plain words

wrap deco, placed directly above a make, rebinds the function to deco(original), the same decorator power a symbol based language gives you, spelled as a readable English word instead of @.

examples/decorators.ven
:: 'wrap deco' immediately before a 'make' rebinds the function's name
:: to 'deco(original_function)': Venutian's version of Python's
:: '@decorator' syntax, using a word instead of a symbol.

make logged(fn)
    make wrapped(a, b)
        print "calling with " + as_text(a) + ", " + as_text(b)
        result = fn(a, b)
        print "returned " + as_text(result)
        give result
    give wrapped

wrap logged
make add(a, b)
    give a + b

print add(2, 3)

:: stacked decorators apply nearest-to-'make' first, just like Python's
:: stacked '@decorator' order
make doubled(fn)
    make double_it(x)
        give fn(x) * 2
    give double_it

make incremented(fn)
    make increment_it(x)
        give fn(x) + 1
    give increment_it

wrap doubled
wrap incremented
make identity(x)
    give x

:: incremented applies first (5 + 1 = 6), then doubled (6 * 2 = 12)
print identity(5)

:: a nested 'make' can be decorated too, applied right where it's defined
make outer()
    make tripled(fn)
        make w(x)
            give fn(x) * 3
        give w

    wrap tripled
    make square(x)
        give x * x
    give square(4)

print outer()

:: decoration composes with generators, the decorator just wraps
:: whatever value calling the generator function produces
make traced(fn)
    make w(n)
        print "creating a generator"
        give fn(n)
    give w

wrap traced
make count_up(n)
    i = 0
    repeat while i < n
        emit i
        i = i + 1

g = count_up(3)
print next_value(g)
print next_value(g)
Output
calling with 2, 3
returned 5
5
12
48
creating a generator
0
1

Value and reference semantics

Integer, Number, Text, Truth and Nothing are value types: no operation mutates one in place, every operation produces a new value. List and shape instances have reference semantics: assigning one name to another binds both names to the same underlying value. A closure captures its defining scope by a read only snapshot taken once, at creation. None of this is left to guesswork; the language specifies it.

Recursion, drawn

Skip the spiral scene
The Fibonacci spiral, drawn from fib's own recursion A quarter circle spiral drawn through eleven squares, one per call from fib(10) down to fib(0), the base case in the largest square. fib(10) fib(9) fib(8) fib(7)
fib.ven
make fib(n)
    when n < 2
        give n
    give fib(n - 1) + fib(n - 2)

print fib(10)

Prints 55.

Call by call: fib(10), fib(9), fib(8), fib(7), fib(6), fib(5), fib(4), fib(3), fib(2), fib(1), fib(0).

The spiral below is fib(10), one square per call, down to fib(0). A recursive function is the plainest shape a language can hand a reader: a base case that stops the spiral, and a call that draws the next turn. Venutian's own native compiler proves the point on fib(30), inlining the recursive body once and keeping the base case frameless, reaching cycle for cycle parity with clang -O2 (see the performance page).

The mirror

reflect.ven
make square(n)
    give n * n

print square(6)

Prints 36.

Reflected: the same program's real venutian ast
{
  "node": "Program",
  "statements": [
    {
      "node": "FunctionDecl",
      "name": "square",
      "params": [
        {
          "node": "Param",
          "name": "n",
          "type_name": null,
          "default": null
        }
      ],
      "body": [
        {
          "node": "GiveStatement",
          "value": {
            "node": "BinaryOp",
            "op": "*",
            "left": {
              "node": "Name",
              "identifier": "n"
            },
            "right": {
              "node": "Name",
              "identifier": "n"
            }
          }
        }
      ],
      "is_tool": false,
      "source_name": "reflect.ven",
      "decorators": [],
      "is_exposed": false,
      "type_params": [],
      "effects": []
    },
    {
      "node": "PrintStatement",
      "args": [
        {
          "node": "Call",
          "callee": {
            "node": "Name",
            "identifier": "square"
          },
          "args": [
            [
              false,
              {
                "node": "IntegerLiteral",
                "value": 6
              }
            ]
          ],
          "kwargs": []
        }
      ]
    }
  ]
}

venutian ast <file> prints a program's own parsed structure as JSON: the language looking at itself. stdlib/introspect.ven builds on the same real, parsed AST to answer what any module actually exports, its functions with their real parameter signatures, its shapes with their real fields, its behaviors with their real methods, never guessed or hand maintained. The code and its structure are the same information, seen twice.

Modules

use "lib/geometry.ven" imports another file's make, tool make, shape and behavior declarations by name, with as name for a qualified import and show a, b for a selective one.

A small program that imports a Point shape and a distance function from lib/geometry.ven, builds two points and prints the distance between them, is the whole of the idea: a module is a file, an import is a path, and all 113 packages in the standard library are built the same way. The example program examples/modules_demo.ven does exactly this.

Readable by agents

Venutian treats AI agent capability as a language level concern, built in rather than added as a library.

tool, placed before make, compiles a function to a structured JSON calling schema that venutian tools prints, the language level analogue of a hand written function calling schema, discoverable and callable by an external agent directly.

venutian ast prints any program's parsed structure as JSON for the same reason: code that people and agents can both read and drive.

venutian tools examples/ai_native.ven
[
  {
    "name": "convert_to_fahrenheit",
    "description": "Convert a Celsius temperature to Fahrenheit.",
    "parameters": {
      "type": "object",
      "properties": {
        "celsius": {
          "type": "number"
        }
      },
      "required": [
        "celsius"
      ]
    }
  }
]
venutian ast examples/hello.ven
{
  "tree": {
    "node": "Program",
    "statements": [
      {
        "node": "PrintStatement",
        "args": [
          {
            "node": "TextLiteral",
            "value": "Hello from Venutian"
          }
        ]
      }
    ]
  }
}

Next steps

Read the compiler stepper to watch a real program move from source to tokens to a syntax tree to bytecode to output, stage by stage. Read the language tour for the full grammar, chapter by chapter. Or skip straight to a terminal: install Venutian and open one.

Next chapter Performance