Basic Syntax

Sec source is deliberately regular: declarations use explicit keywords, blocks use braces, ordinary statements do not use semicolons, and punctuation is assigned narrow, consistent meanings.

This chapter introduces the surface syntax used throughout the rest of the manual. Detailed semantics belong to the chapters for types, functions, control flow, ownership, error handling and other language features.

Rule: Syntax determines the structure of source code. Semantic analysis determines what names mean, which types are compatible, whether ownership operations are valid, and whether a program is safe.

Source Text

Sec source files are UTF-8 text.

  • Invalid UTF-8 is rejected.
  • An initial UTF-8 byte-order mark is accepted and ignored.
  • A byte-order mark anywhere else in the file is invalid.
  • LF, CRLF and CR are recognized as line endings.
  • The formatter writes LF by default.

Outside literals and comments, ordinary lexical whitespace consists of space, tab and line-ending characters. Other Unicode spacing characters are rejected rather than silently treated as whitespace.

let value := 10
let    other    :=    20

These declarations have the same meaning. Formatting is canonicalized by the formatter.

No statement semicolons

Ordinary statements are not terminated with semicolons:

let value := 10
value += 1
return value

This is not canonical Sec:

let value := 10;

The compiler diagnoses a semicolon used as an ordinary statement terminator.

Source Files

Ordinary Sec source files use the .sec extension.

A canonical source file has this high-level order:

#target(os: "linux", arch: "amd64")   // optional

module server

import (
    "fmt"
    "net/http"
)

type Server struct {
    port: uint16,
}

fn main() void {
}

The optional #target directive, when present, comes first. The module declaration follows, then imports, then top-level declarations.

Ordinary executable statements are not permitted directly at module scope.

Modules

Every ordinary Sec source file declares exactly one source module:

module orders

The source-level module declaration contains one identifier only.

Dotted module declarations are invalid:

module domain.orders
module internal.storage

Directory hierarchy belongs to the canonical import path, not to the source module name.

domain/orders/
    create.sec
    order.sec
    validation.sec

Each file in that directory normally declares:

module orders

All applicable .sec files in one source directory contribute to one shared module declaration namespace. Source-file order has no semantic meaning.

Rule: A module name is an identifier. An import path is a path. They are related, but they are not the same thing.

Imports

Imports use canonical logical paths written with / separators:

import "fmt"
import "domain/orders"
import sys "platform/linux"

Imports may also be grouped:

import (
    "fmt"
    "domain/orders"
    sys "platform/linux"
)

The imported module's declared module name becomes the default qualifier. The import path itself does not become a dotted source name.

import "customer/order"

let value := order.CreateCustomerOrder()

An alias changes only the local qualifier:

import customerOrder "customer/order"

let value := customerOrder.CreateCustomerOrder()

Import bindings are local to the source file that declares them.

Imports do not inject declarations as unqualified names:

import "storage"

let file := storage.Open()

The following is not made valid merely by the import:

let file := Open()

Selective imports, wildcard imports and implicit re-exports are not part of Sec 0.1.

Identifiers

An identifier begins with a Unicode letter or underscore.

Following characters may be Unicode letters, ASCII digits or underscores.

value
currentValue
HTTPServer
_speed
__state
Ω
Σ
μs
Meter2

Identifiers are:

  • case-sensitive;
  • required to already be in Unicode NFC form;
  • not locale-sensitive;
  • not escapable when they collide with a reserved word.
Value
value

These are different identifiers.

Underscore has special forms

The bare spelling _ is contextual and is not an ordinary binding name.

_
_name
__name

_name and __name are ordinary identifiers. Their prefixes participate in Sec visibility rules:

Form General visibility meaning
NamePublic
_nameModule-internal
__nameSource-file-private

The bare _ is used only where another grammar rule assigns it a meaning, such as discard positions, wildcard patterns or reserved register bits.

Comments

Line comments

// Calculate the next value.
let next := value + 1

A line comment begins with // and continues to the end of the line.

Block comments

/*
 * This is a block comment.
 */

Block comments may span multiple lines and may nest:

/*
 * Outer comment.
 *
 * /*
 *  * Inner comment.
 *  */
 *
 * Outer comment continues.
 */

Documentation comments

A block comment beginning with /** is a documentation comment:

/**
 * Returns the current temperature.
 */
fn CurrentTemperature() Temperature {
    return temperature
}

Documentation comments are lexically comments, but tooling may attach them to declarations for generated documentation, hover information and editor support.

/// is an ordinary line comment.

Rule: Comments do not secretly change program semantics. Compiler directives use explicit directive syntax instead.

Literals

Boolean literals

true
false

Sec has no ordinary nil keyword.

Integer literals

0
42
1_000_000

0b1111_0000
0o755
0xFFFF_FFFF

Binary, octal and hexadecimal forms use explicit prefixes. Leading zeroes on decimal literals do not imply octal.

Fractional and exponent literals

3.14
.5
1.25e-3
1E6

A decimal point uses ., never a locale-specific comma. 1. is not a fractional literal; write 1.0.

Unsuffixed fractional and exponent forms follow Sec's exact-decimal inference. A numeric family suffix may request another family.

8i
8u
8g
8m
65t
65r
1.5g
1.5m
Suffix Numeric family
iSigned integer
uUnsigned integer
gBinary floating point
mExact decimal
tchar scalar
rrune scalar

The suffix selects a family, not a fixed bit width.

Character literals

'A'
'\n'
'Ω'

After escape processing, a character literal contains exactly one Unicode scalar value.

Strings

Ordinary strings use double quotes:

"hello"
"hello\nworld"

Raw strings use backticks and do not process backslash escapes:

let text := `first line
second line`

Interpolated strings begin with $":

let message := $"Hello {name}"

Literal braces in an interpolated string are doubled:

let text := $"{{value}}"

This produces the text {value}.

Declarations

Sec uses explicit declaration forms rather than deriving declaration kind from context.

Common declaration forms include:

Syntax Purpose
moduleDeclares the source module.
importCreates a source-file-local module binding.
typeDeclares a named type.
enumDeclares an enum.
unitDeclares a semantic unit.
interfaceDeclares an interface contract.
fnDeclares a function or method.
implDeclares behavior and associated members for a type.
letDeclares a binding.
staticModifies a declaration to give it static storage or type-associated semantics where permitted.
externDeclares an external function boundary.
testDeclares a test in an applicable *_test.sec file.

Named types

type Percent int range 0..100
type Speed decimal<m/s>

Structs

type Point struct {
    x: int,
    y: int,
}

Functions

fn Add(left: int, right: int) int {
    return left + right
}

Impl blocks

impl Point {
    fn IsOrigin() bool {
        return self.x == 0 && self.y == 0
    }
}

Ordinary impl methods receive compiler-provided implicit self. Only static methods omit instance binding entirely.

Variables

let declarations are immutable by default:

let count := 10
let name: string := "Sec"

Mutability is explicit:

let mut count := 10
count = 11

Multiple declarators may share one let statement:

let width := 10, height := 20

Type-first declarations are also part of the language:

int mut: x, y, z
float: width := 5.4, pi := 3.14

Immutable declarations require initialization. Mutable typed declarations may omit an initializer only when the declared type is defaultable.

Explicit move initialization

Move intent has dedicated declaration syntax:

let buffer :<- source
let buffer: Buffer <- source

The first form infers the type. The second names the destination type explicitly. Copy/direct construction uses :=.

Assignment

Assignment is statement syntax, not an expression that produces a value.

count = 10
buffer <- replacement

= is ordinary assignment. <- is explicit move assignment. Which transfers are copies, moves or invalid is determined by ownership semantics.

Compound assignment is supported:

count += 1
flags |= mask
value <<= 2

A compound assignment evaluates the destination once and the source once. It is not defined as naïve textual rewriting.

Blocks and Statements

Braces define blocks:

{
    let value := 10
    Use(value)
}

Sec does not use indentation as block syntax.

Control-flow blocks require braces:

if ready {
    Start()
} else {
    Wait()
}

Normal conditions do not require parentheses:

while connected {
    Poll()
}

The condition must still have type bool.

A simple infinite loop uses:

for {
    Tick()
}

Iteration uses in:

for value in values {
    Use(value)
}

C-style semicolon-separated loop headers are not part of Sec.

Expressions and Operators

Common operator families include:

// arithmetic
+  -  *  /  %

// comparison
<  <=  >  >=  ==  !=

// logical
!  &&  ||

// bitwise
~  &  |  ^  <<  >>

// membership
in

Logical && and || short-circuit. Ordered comparison and equality operators do not form comparison chains.

Precedence

From higher to lower precedence:

Level Forms
Member.
Postfixcalls, indexing/slicing, struct literals, spread
Prefix+, -, !, ~
Multiplicative*, /, %, contextual x
Additive+, -
Shift<<, >>
Comparison<, <=, >, >=, in
Equality==, !=
Bitwise&, then ^, then |
Logical&&, then ||

Assignment is not part of expression precedence because assignment is a statement.

let value := a + b * c

is interpreted as:

let value := a + (b * c)

Parentheses may override precedence or document intent.

Ranges

Sec has two range operators:

..     // inclusive upper bound
..<    // exclusive upper bound

Examples:

1..10
0..<10
..100
0..

Range syntax is contextual in Sec 0.1. It is accepted where the surrounding grammar defines a range, such as iteration, membership, contracts, slicing or switch cases.

if value in 0..<10 {
    Use(value)
}

A standalone range is not automatically a first-class Range value.

Punctuation

Several punctuation families carry stable language-wide meaning:

Syntax Meaning family
()Parameters, arguments, grouping and selected construction forms
{}Blocks, declaration bodies and literals
[]Generic parameters/arguments, arrays, sequences, indexing and slicing
<>Unit expressions in numeric type syntax
:Typed identifiers and grammar-specific separators
:=Declaration initialization
=Ordinary assignment
<-Explicit move assignment or transfer where defined
:<-Inferred declaration with explicit move initialization
=>Match/handler arm mapping
@Attributes and special annotations
#Compiler directives
...Expansion/spread family
..Inclusive range
..<Exclusive-upper range

The lexer uses longest-token matching, so punctuation such as :<-, ..<, <<= and => is recognized as the longest valid token at that position.

Formatting

Sec has a canonical formatter. Formatting choices should not become a second language layered on top of the grammar.

The formatter is expected to:

  • preserve comments;
  • preserve literal values;
  • preserve raw-string content;
  • write canonical spaces around operators;
  • remove trailing whitespace;
  • write one final newline;
  • avoid semicolon insertion;
  • preserve valid identifier spelling after NFC validation;
  • keep declaration metadata attached to the declaration it describes.

Canonical formatting may remove syntax that is provably redundant, but it must not change program meaning.

Practical rule: Write clear Sec. Let the formatter settle spacing, indentation and other mechanical layout choices.