Types and bindings
Scalars are int, dec, str, bool; uni accepts anything. none means “no value” and may sit in any binding.
def const name (str) = "Luca"
def var visits (int) = 1
def var inferred = "hello" -- type inferred as str
visits (int) = visits + 1 -- update without changing type
def var x (int) = none
x.clear() -- mutable var back to none (error on const)
forget var x (int) -- remove the binding entirely
change x (str) = "hi" -- change a var's type and valuedef varis mutable,def constis immutable. One binding per name per scope.- Annotations are optional when inference suffices:
def var n = 5infersint. - A value must match its declared type (or be
none, or anything foruni). - Comments:
--for one line. Strings use double quotes with{name}interpolation wherever a string value is created.
Expressions and operators
Decimals are exact (no binary floats): 20.00 + 1.50 displays 21.50. Division is numeric: 10 / 4 → 2.5 (dec).
| Level | Operators | Notes |
|---|---|---|
| 1 | not, unary - | not needs bool |
| 2 | * / % | Division / modulo by zero is an error |
| 3 | + - | String + needs str + str only |
| 4 | is == to … is <= to | Worded comparisons, exact forms |
| 5 | and, then or | Both need bool, short-circuit |
print(10 / 4) -- 2.5 (dec)
print(2.50 * 2.00) -- 5.00
if name is == to "Luca" then :
print("Hello, {name}!")
print(true and false) -- false
print(x is == to none) -- absence checkint/decmix →dec; otherwise integer arithmetic staysint. Integer overflow is an error.- Ordering allows
int/decmixes andstr/str; anything else is an error. and/or/notrequirebool.nonecan be compared for equality but not used in arithmetic or logic.print()takes exactly one argument; combine values with interpolation. It printsnoneasnone.
Control flow
Blocks use : plus consistent indentation. Empty blocks are errors. Conditions must be bool.
if visits is > to 1 then :
print("Welcome back, {name}!")
else if visits is == to 1 then :
print("Welcome!")
else :
print("Hello.")
for 3 times repeat :
print("hi")
for item from names repeat :
print(item)
while visits is > to 0 then :
visits (int) = visits - 1
if visits is == to 0 then :
stop -- exits the innermost loop
skip -- skips to the next iterationfor N times repeat :—Nmust be a non-negativeint.while condition repeat :— condition must bebool.for item from collection repeat :— iterates list/tuple/set values and dict keys.stop/skipoutside a loop are errors.returninside a loop exits the current function.
Functions
Defaults, closures, recursion — and strict argument counts.
def func greet(name (str) = "Luca") (str):
return "Hello, {name}!"
print(greet())
print(greet("Alex"))- Syntax:
def func name(params) (optional_return_type):with a:block. Calls:name(args). - Parameters look like
name (type) = default; untyped params accept any value. Missing return annotation allows varying returns; barereturnor falling off the end returnsnone. - An explicit return type requires every returned value to match. Wrong argument count is an error. No overloading.
- Recursion, nested functions, and closures are supported: closures can read and modify captured
var; primitive args are independent values while passed collections stay mutable. returnoutside a function is an error. Deep recursion (limit 64) is an error.
Collections
1-based indexing, content equality, and warnings — never silent corruption.
def list names (str) = ["Luca", "Alex"]
def tuple data (uni) = (123, true, "Luca")
def set labels (str) = {"alpha", "stable"}
def dict person = {"Name" = "Luca" (str), "Age" = 16 (int)}
print(names[1]) -- 1-based: "Luca"
names.add("Sam")
print(names.length) -- item count
print(names.contains("Luca")) -- true
change names[1] = "Lucca"
person.add("City", "Oslo")
print(person["Name"])- Literals: lists
[ … ], tuples( … )(single-element(x,)), sets{ … }, dicts{key = value (type), …}with string keys.unipermits mixed elements. - Indices are 1-based. Missing index/key warns and yields
none; execution continues. .add/.remove/.clearare statements;.contains/.length/[i]are expressions. Dicts use.add(k, v),.contains(key = k)/.contains(value = v).change l[i] = vpreserves the element type. Mutating the collection being iterated is an error..lengthcounts collection items, string chars, or int/dec digits;.length(decimals)gives a decimal’s scale. Equality is content-based and recursive.
Modules
Real namespaces, own scopes, once-only init.
import Math
import Utils
print(Math.sqrt(25))
Utils.helper()import Name(single identifier, no alias/extension) resolvesName.lucc— program directory first, then working directory. Built-ins (Math,Text,File,System) take precedence over same-named files.- Each module has its own scope; every top-level definition is exported automatically. Access via the real namespace:
Module.member,Module.func(args). - Initialization runs once on first import; later imports reuse it. Circular imports are errors; init errors propagate and may be captured.
Errors and warnings
Errors stop execution unless captured. Warnings are nonfatal and never captured.
try to :
File.read("data.txt")
capture error :
print("Could not read the file.")
finally :
print("Cleanup")try to :runs its block; optionalcapture error :runs on error and bindserrorfor that block; optionalfinally :always runs. A baretry to :with neither block just propagates.- Only runtime errors are capturable. The caught value is a dictionary — read fields with
error["code"]. Runtime errors carrycode = noneplusmessage/line/column.
Custom errors with raise
def error NotFound :
code = 404
message = "missing"
raise NotFound()
raise NotFound(code = 410)
try to :
raise NotFound()
capture error :
print(error["code"]) -- 404
print(error["message"]) -- missingdef error Name :needs at leastcodeandmessagefields (body runs once at definition;code = noneis allowed).raise Name(...)is a statement (never a value); parentheses are required. Overrides must name existing fields with matching types (noneaccepted); repeats warn and keep the first.- Caught custom errors add
line/columnof the raise.System.exitstays uncapturable;finallystill runs around it.
Input and the standard library
The binding’s declared type controls conversion; invalid input is an error you can capture.
def const name (str) = ask "What is your name?"
def const age (int) = ask "How old are you?"
print("Hello, {name}. You are {age}.")int/decparse trimmed numbers;boolaccepts exactlytrue/false;strkeeps the raw line. Running out of input is an error.- All library calls use strict argument types, reject
nonewhere a real value is required, and check arity.
| Module | Calls | Notes |
|---|---|---|
Math | sqrt · pow · abs · round · floor · ceil · min · max | sqrt/pow → dec; round/floor/ceil → int; min/max take 2+ numerics |
Text | upper · lower · trim · replace · split · join | split(text, sep) → list(str); join(list, sep) → str |
File | read · write · append · exists · delete | Relative paths resolve against the working directory; missing delete warns, other failures error |
System | os · arch · args() · env() · exit(int) | os/arch are bare reads; args() → list(str); exit flushes output, runs finally |
Current boundary: everything locked for 1.0 Beta is implemented — all types, I/O, collections, functions, control flow, modules, custom-error raising and inspection, the four library modules, and CLI integration (exit codes, System.args wiring, .lucc enforcement).