Declarations
Each .fol file inside a folder is part of the same package.
The important part is that files are connected, but not merged:
- every physical
.folfile is still its own source file - files in the same folder share one package scope
- declarations are order independent across those files
- a declaration started in one file can not continue into the next file
There is no need to import sibling files from the same package. They already belong to the same package scope.
Package, Namespace, And File Scope
It helps to think about source layout in three layers:
- package scope: all
.folfiles directly inside the package folder - namespace scope: declarations inside subfolders of that package
- file scope: declarations marked
hid, which stay visible only inside their own file
In short:
- same folder = same package
- subfolder = nested namespace
hid= file only- files are never imported directly as standalone modules
Package scope
Files that live directly in the package root share one package scope:
root/
math/
add.fol
sub.fol
Both add.fol and sub.fol belong to package math, so declarations from one file may be used by the other without importing the sibling file.
Namespace scope
Subfolders do not create a new package by themselves. They create nested namespaces inside the same package:
root/
math/
add.fol
stats/
mean.fol
Here:
add.folis in package namespacemathmean.folis in package namespacemath::stats
Code may reach namespace members either by:
- direct
use - or qualified access with
::
File scope
Sometimes a declaration should stay inside one file even though the package is shared.
That is what hid is for.
// file1.fol
var[hid] cache_key: str = "local"
// file2.fol
pro[] main: int = {
.echo(cache_key) // error: hidden declarations are file-only
}
Namespaces
A namespace can be defined in a subfolder of the main folder, and namespaces can be nested.
To acces the namespace there are two ways:
- direct import with
use - or code access with
::
Direct import
use aNS: loc = { "home/folder/printing/logg" }
pro[] main: int = {
logg.warn("something")
}
Code access
use aNS: loc = { "home/folder/printing" }
pro[] main: int = {
printing::logg.warn("something")
}
Mental model
For source layout, the mental model is:
- one folder root gives one package
- each file in that folder is a real source file in that package
- subfolders extend the namespace path
useimports packages or namespaceshidkeeps a declaration private to one filelocimports a local directory tree without package metadatastdimports a toolchain-owned directory treepkgimports a formal external package defined bypackage.yaml+build.fol
This means FOL is not “one file = one module”. The package is the folder; the file is a source unit inside that package.
Package Roots
When a directory is treated as a package root, the exact contract depends on the import kind:
loc: plain local directory import, no package metadata requiredstd: toolchain standard-library directory importpkg: installed external package import with explicit root files
For pkg, the root is not just “a folder containing .fol files”.
It is a formal package root with:
package.yamlfor metadatabuild.folas the package build entry file
This keeps the language model clean:
- source files
useother namespaces/packages - package build execution starts from
pro[] build(graph: Graph): noninbuild.fol - package metadata lives in
package.yaml - package loading happens before ordinary name resolution
build.fol itself is still ordinary FOL syntax.
It is not a separate mini-language.
The package layer simply gives package/build meaning to the canonical build
routine there.
Blocks
Block statement is used for scopes where members get destroyed when scope is finished. And there are two ways to define a block:
- unnamed blocks and
- named blocks
Unnamed blocks
Are simply scopes, that may or may not return value, and are represented as: { //block }, with . before the brackets for return types and _ for non return types:
pro[] main: int = {
_{
.echo("simple type block")
}
.echo(.{ return "return type block" })
}
Named blocks
Blocks can be used as labels too, when we want to unconditionally jump to a specific part of the code.
pro[] main: int = {
def block: blk[] = { // $block A named block that can be referenced
// implementation
}
def mark: blk[] // $mark A named block that can be referenced, usually for "jump" statements
}