05 - Language Basics

Verge feels fairly terse, fairly direct, and fairly C-adjacent, but with less ceremony in the obvious places. The language generally aims to stay out of your way.

This chapter is the “how does the syntax feel” tour. Nothing here is deep; it’s the handful of rules that make the rest of the code readable.

Braces, not vibes

Verge uses braces for blocks, and braces alone define where a block starts and ends:

import std.io

main: f() {
	if true {
		io.writeln("braces win again")
	}
}

Indentation carries no meaning to the compiler. By convention you indent with tabs inside braced blocks so the structure stays visible, but formatting has no effect on how the code compiles. The braces are the source of truth.

Comments

Ordinary comments start with # and run to the end of the line. Documentation comments start with ## and sit immediately above a declaration — the formatter preserves them and the language server surfaces them on hover and in completions.

import std.io

## Greets a person by name.
greet: f(name: string) {
	# greet the caller by name
	io.writeln("Hello, ", name)
}

main: f() {
	greet("Ada")
}
Hello, Ada

Get in the habit of ## on anything another human will call — it’s the same text your editor shows them later.

Newlines end statements

Most statements end at the newline. You don’t need semicolons, which keeps lines uncluttered:

import std.io

main: f() {
	x := 10
	y := 20
	sum := x + y
	io.writeln("sum = ", sum)
}
sum = 30

Line continuation is automatic

If a line has an unclosed (, [, or {, Verge keeps reading onto the next line until the bracket closes. That means you can break long expressions and collection literals across lines without any special marker:

import std.io

main: f() {
	total := (
		10 +
		20 +
		30
	)

	values := []i32{
		1,
		2,
		3,
	}

	io.writeln("total = ", total, ", first = ", values[0])
}
total = 60, first = 1

Explicit types vs inference

Write an explicit type when it helps the reader; reach for := when the value already makes the type obvious:

import std.io

main: f() {
	count: i32 = 3          # explicit type
	name: string = "verge"  # explicit type

	inferred_count := 3     # inferred as i32
	inferred_name := "verge" # inferred as string

	limit: const i32 = 42   # a constant — cannot be reassigned

	io.writeln(name, " ", inferred_name, " ", count + inferred_count, " ", limit)
}
verge verge 6 42

The rule of thumb: if the type matters to whoever reads the line, spell it out; if it’s painfully obvious, let inference handle the boring part. const marks a value that must not change, and it always takes an explicit type.

The overall feel

Verge aims to be readable without being verbose: compact declarations, braces for blocks, plain # comments, and syntax that favours stating the intent directly over filling in boilerplate. That’s enough to read Verge comfortably — the next chapter gets specific about the values you’ll actually be moving around.