The Fog Programming Language
This documentation aims to provide a simple and easy way to understand the basics of the language. Most of the documentation is subject to change as it covers the earliest build of the language. The documentation is updated per build and assumes you are using the latest build of the compiler.
The documentation also hosts examples of the code syntax for understandability.
Translations may be available in the future for multiple languages.
Fog is an open-source project and all of the source code is available on github.
If you find this project interesting you should take a look at my other projects!
Introduction
Welcome to the Documentation of Fog, the statically typed, simple, flexible language.
What is Fog for
Fog was primarily created as a way to get experience with creating programming languages, and more importantly the inner workings of LLVM. It is also extremely useful to create quick programming ideas. We must also mention that the language is not slow even by modern standards due to its performant backend.
Who is Fog for
The language is mostly implemented for personal use for now, as I am sure the language does not hold much value for enterprises especially in this state.
The use of the documentation
The documentation assumes you are using the latest release of the language. It is advisable that you read the documentation from front to back, so that you are familiar with all the concepts the language brings.
Getting Started
The language can be installed and used fairly easily. You can build the binary for the compiler from the source available here. Additionally, prebuilt installers are available for Windows in the Github repository.
It is best advisable to add the binary to PATH, if compiling the binary manually so that it can be used easily later.
Initializing a project
There are multiple commands available for creating a new project.
You can use the commands on Windows as following:
- For Initializing a project in a pre-existing folder:
$ fog init
Successfully initalized a project at: %project-path%
- For creating a new folder specifically for the project:
$ fog new %project-name%
Successfully created project `%project-name%`
Language Concepts
This section will showcase the language's basic concepts including primitives, syntax, keywords and basic concepts. The section will include a few example of the language syntax, for ease of understanding.
Primitives
Scalar Types
Almost all of the basic scalar types are implemented in this language.
Basic Types
| Bits | Signed | Unsigned | Float |
|---|---|---|---|
| 8-bit | - | uintsmall | - |
| 16-bit | inthalf | uinthalf | floathalf |
| 32-bit | int | uint | float |
| 64-bit | intlong | uintlong | floatlong |
Additional Types
bool: Used for storing a boolean value.
void: Used for indicating a function with no returned value.
string: A string variable can be used to store text. The language handles strings as a pointer to an array. (This comes into play when interacting with FFI)
array<T, L>: An array can be used to store multiple values in the same variable. An array has a predetermined type and length, as indicated by the generic T and L. A length can only be an int.
Custom Types
Structs can also be created by the user via the struct keyword. Structs cannot contain themselves. Defining a struct is similar to how one would do it in other languages.
struct my_struct {
field1: int,
field2: string,
field3: bool,
}
Enums are also available and are accessible via the enum keyword. Enums can also serve as a Default value for any type.
enum Weekdays {
Monday,
Tuesday,
Friday,
}
enum FavWords<string> {
choco = "Chocolate",
banana = "Banana",
}
Variables
Creating Variables
Since the language is statically typed, every variable must have its type defined at compile time. Initializing a variable is not crucial, as variables get a default value if left uninitialized by the user.
Here is how one can define a variable with the aforementioned types:
int age = 23;
string name = "marci1175";
bool is_male = true;
Defining a struct may seem tricky at first, but it is no different from most languages. Every field must be manually initialized with its own default value.
struct person {
age: int,
name: string,
is_male: bool,
}
person somebody = person { age: 23, name: "marci", is_male: true };
Accessing an enum variable is no different from other languages. The default type for an enum is a uint if not defined by the user.
struct Apple {
color: float,
name: string
}
enum<Apple> Apples {
Idared = Apple { color: 1.0, name: "Idared" },
Granny = Apple { color: 0.5, name: "Granny Smith" }
}
enum Numbers {
One,
Two,
SixtySeven = 67
}
string ida_name = Apples::Idared.name;
int integer_zwei = Numbers::Two;
# Error
int float_zwei = Numbers::Two as float;
Functions
Creating Functions
To create functions, the function keyword can be used. This may be familiar to some from other languages that have similar keywords.
Visibility
Every function requires a predetermined visibility attribute.
| Keyword | Explanation |
|---|---|
| pub | A public function is publicly available in the module. |
| publib | A public library function is available outside of the project when imported as a library. |
| private | A private function is only available in the module it is defined in. |
Every source file serves as a different module.
Function statement:
<visibility> function <name>(<arguments>): <return type> {
<body>
}
Function definition example:
pub function name(arg1: int, arg2: float) {
// Function body
}
pub function name_2(arg1: int, arg2: float): int {
// Function body
return 0;
}
Importing Functions
We can import functions from other source files or from libc.
- To import functions from pre-compiler libraries, we can use the
externalkeyword. - When importing functions from dependencies or from other fog source files we can import the item via defining the the module path.
- We can import source files by manually defining their paths on the host system.
Here is how to import both types of functions:
// -- other.f --
pub function return_2(): int {
return 2;
}
// main.f
import "other.f";
// Import with a fully defined path
import "foo/bar/faz/test.f";
// Importing from a precompiled library (libc in this case)
external printf(msg: string, ...): void;
// Importing from a source file
import other::return_2;
// Importing from a dependency
import foo::bar::baz::hello;
pub function main(): int {
int num = return_2();
// Greet the user
hello();
printf("Returned number: %i", num);
return 0;
}
Note that we can also use variable arguments when constructing symbols for other functions. VarArgs cannot be used in a Fog function.
Logic Gates and Comparisons
Please note that this part of the language is not finished. I cannot guarantee the reliability of the code provided. This part of the language is subject to change.
Logic gates work almost perfectly. The issue is with comparisons, as every comparison "trait" is hardcoded into the language. I am aiming to implement traits or something similar to Rust's solution so that comparison traits are more flexible and easier to use.
An example of using logic gates:
external printf(msg: string): void;
pub function main(): int {
if (3 > 8) {
printf("Oh no! Math broke!");
}
else {
printf("Oh yes! Math didn't break!");
}
return 0;
}
The following comparison operators are currently implemented (for all types):
!===>>=<<=
Control Flow Statements
The language offers multiple ways of controlling code flow. This includes basic concepts such as loops and code flow statements.
An example of loop usage (which is similar to Rust's loop keyword):
Please note that loops are unstable in the current build of Fog due to faulty optimizations.
loop {
# Do something
break;
}
Example for while and for usage:
This is currently in development and may not be available in the latest edition of the compiler!
A
forloop can only be used to iterate through a range of numbers; it does not support iterator objects.
While statement:
while (<condition>) {
<body>
}
For statement:
for <local_variable> in (<range>, <step>) {
<body>
}
Example usage of both:
int counter = 0;
while (counter < 10) {
# Do something
}
array<int, 3> numbers = {1, 634, 4};
for idx in (0..2, 1) {
int number = numbers[idx];
}
Hello World!
Writing the code for the project
Fog source files always end with the .f extension. A wide range of naming schemes can be used with fog support them all.
Warning⚠️: File names cannot contain any special characters except "_".
Navigate to: %project-name%/src/main.f
And enter the code:
external puts(msg: string): int;
function main(): int {
puts("Hello World!");
return 0;
}
The compiler can output LLVM-IR or even link automaticly, with clang (This requires clang to be added to $PATH).
When we automaticly want to run code we have written we need to run fog run. This compiles the code and links it with the built in project linker (Which is a wrapper around clang).
To only output LLVM-IR fog compile should be called.
All build arctifacts are placed in the
build_pathdefined in the project configuration.
Example code of running the code above:
$ fog run
<compiler output>
Running `<path to the linked binary>`
Hello World!
Project Management
This section of the documentation will explain the basics of project and config management. This includes handling dependencies, creating libraries, importing functions from libraries.
Project Configuration
To compile or work on a project, every project is required to have its own configuration file. This configuration file provides crucial information to the compiler, LSP, and the user alike.
The default project configuration looks something like this:
name = "test_project"
is_library = false
version = "0.0.1"
build_path = "out"
additional_linking_material = []
[dependencies]
| Field Name | Usage |
|---|---|
| name | Used for naming your project. It is also used to specify the library when importing a function. |
| is_library | Tells the compiler not to search for a main function and enables the project to be imported as a dependency. |
| version | Specifies the version of a project. This is used to identify multiple editions of the same dependency. |
| build_path | Tells the compiler where to place the build artifacts. |
| additional_linking_material | Tells the linker which additional files to link the object files with. |
| features | Sets the enabled features for the project, if it is not library these are ignored. |
| dependencies | Specifies the dependencies the project uses. |
Config file composition:
Optional fields are marked with
*.
name = <string>
is_library = <bool>
version = <version> # This must follow the semver specification.
build_path = <path>
*features = [<feature>, <feature>]
additional_linking_material = [<path>, <path>, ...]
[dependencies]
<dependency name> = { version = <version>, features = [<feature name>, <feature name>, ...] }
<dependency name> = { version = <version>, features = [<feature name>, <feature name>, ...] }
...
Learn more about SemVer here.
Linking and the building process
When compiling the compiler first builds all the different dependencies a project could have. The compiler puts all the build arctifacts into the specified build folder. It then stores the path of the compiled LLVM-IR of the dependency.
Additional linking materal can be provided, for example when wanting to use external function from another pre-compiled library. (Using FFI)
The information related to the building of the project is then output into a build manifest file.
Example of a build manifest file:
build_output_paths = ['C:\Users\marci\Desktop\fog\test_project\deps\dep1\out\dep1.ll', 'C:\Users\marci\Desktop\fog\test_project\out\test_project.ll']
additional_linking_material = []
output_path = 'C:\Users\marci\Desktop\fog\test_project\out\test_project.exe'
This build manifest file can only be read by the proprietary fog linker, which is a wrapper around clang with a parser.
The LLVM-IR files can be linked manually with any linker, however it is far less intuitive compared to the builtin linker.