Packages and files
Larger programs are written across several files. Each file belongs to a package, and a package shares with the rest of the program only what it marks pub.
Packages
A file that begins with package name belongs to that package. Only the functions, constants, types and fields marked pub can be used from outside it; everything else is an internal detail that you are free to change later without breaking anyone. A file without a package line is in the package main.
package tools
pub const VERSION := "1.0"
func double(n: int): int:
ret n * 2
end
pub func quadruple(n: int): int:
ret double(double(n))
endThis file has no main of its own, so on its own it does not run; it is meant to be included by a program, as in the next section.
Including your own files
include name "file.salam" brings in a file of your own project under a short name, and you reach its public members as name.thing. The path is relative to the file that does the including.
include tools "pkg-lib.salam"
func main:
println tools.quadruple(5)
println "tools version:", tools.VERSION
end- The name is optional:
include "lib/money.salam"is reached asmoney.Format(...), after the file name. Give it a name whenever the file name is long or unclear. - A path that starts with
@/starts at the project root instead of at the current file, soinclude "@/lib/money.salam"means the same file from every folder of the project. Build from the project's folder so the root is where you expect it. - Do not start a path with
./. The compiler stops withE008: include path './lib/money.salam' must not start with './'and tells you the two spellings it does accept. - A file that cannot be found is an error too (
E008: included file not found), never something skipped quietly.
Importing a library
Standard library packages are imported by name, without quotes: import str, import os. A package inside another one is written with a dot, as in import encoding.json, and is used through its last part, json.Get(...). In short: import is for libraries and include is for your own files.
import str
import math
func main:
text := " hello world "
println str.Trim(text)
println str.Contains(text, "world")
println math.Sqrt(81.0), math.Pow(2.0, 10.0)
endimport encoding.json
func main:
order := `{"id": 42, "item": "notebook", "qty": 3}`
if not json.Valid(order):
println "not valid JSON"
ret 1
end
println "order", json.GetInt(order, "id")
println json.GetInt(order, "qty"), "x", json.Get(order, "item")
println json.Has(order, "coupon")
endAn import nobody uses is an error (E082: unused import), so the imports at the top of a file always tell the truth about what the file depends on.
pub and private
Privacy works on struct members too. In this bank package, owner is public but balance is not, so the only way to change a balance is through Deposit and Withdraw, which check every amount. The helper valid_amount is private as well. Account.Open is a static constructor, the usual way to hand out a ready-made value of a type whose fields are private.
package bank
pub const MAX_DEPOSIT := 1000000
struct Account:
pub owner: str = ""
balance: int = 0
pub static func Open(owner: str): Account:
ret Account { owner = owner }
end
pub func Balance(): int:
ret this.balance
end
pub mut func Deposit(amount: int): bool:
if not valid_amount(amount) or amount > MAX_DEPOSIT:
ret false
end
this.balance += amount
ret true
end
pub mut func Withdraw(amount: int): bool:
if not valid_amount(amount) or amount > this.balance:
ret false
end
this.balance -= amount
ret true
end
end
func valid_amount(amount: int): bool:
ret amount > 0
endinclude bank "pkg-bank.salam"
func main:
mut acc := bank.Account.Open("Sara")
println acc.Deposit(500), acc.Withdraw(800), acc.Withdraw(120)
println acc.owner, acc.Balance()
println acc.Deposit(bank.MAX_DEPOSIT + 1)
endFrom the program, acc.balance would be refused with E017: field 'balance' is private in struct 'Account' (mark it 'pub'), and calling the private helper with E017: 'valid_amount' is not exported by package 'bank' (mark it 'pub'). Make something pub because other code is meant to use it, not to silence an error: everything public is a promise you have to keep.
A package across a folder
When a package grows, split it across several files in one folder. Name the folder after the package and give it a file of the same name: shop/shop.salam. Including that file brings in every other .salam file in the folder as part of the same package.
| File | What it holds |
|---|---|
pkg-folder.salam | The program; it includes shop/shop.salam |
shop/shop.salam | package shop, import str, the private price_label and the public Welcome |
shop/cart.salam | package shop and the public Total |
include shop "shop/shop.salam"
func main:
println shop.Welcome("Sara")
println shop.Total([1250, 399, 205])
endThe files of one package share everything: cart.salam calls the private price_label from shop.salam, and uses str without importing it, because the package already imports it once.
Names in two languages
In an English file you write the English names, and a Persian one such as str.طول is an error; a Persian file needs the Persian names. Every standard library function carries both: its Persian name is written next to it in the library source with @fa. Give your own public functions both names the same way, and Persian files can call them too:
@en "Twice"
@fa "دوبار"
pub func Twice(n: int): int:
ret n * 2
end
func main:
println Twice(21)
endA Persian program that includes this file calls دوبار(۲۱) and gets the same 42.
The order of definitions
Salam asks for a fixed order of the top-level definitions in every file, so that every file reads the same way, and each step has its own error code:
package- Imports, then includes (an import after an include is
E108) - Constants and global variables (
E084andE085when they come after a function or type) - Types: structs, enums, interfaces and
implblocks (E087when one comes after a function) - Functions, private ones first and public ones last
salam format tidies a file up and does much of this moving for you.
Laying out a project
salam new shop creates a folder shop with one file, salam.salam, which holds main. Inside that folder a bare salam build builds that file into a program named after the folder. From there a project usually grows like this:
salam.salamstays short: it reads arguments, calls into the packages and prints results.- Each area of the program gets a folder that is a package:
cart/cart.salam,store/store.salam, each split into more files as it grows. - Packages include each other with
@/paths, so moving a file does not break the includes inside it. - Each package keeps its public surface small and documents it with
@enand@fanames.
Every standard library package has a page of its own in the library reference, and the next lesson collects the errors the compiler reports, including the ones above.