Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80f07dc7c6 | ||
|
|
dbe387f916 | ||
|
|
c43b5dfef0 | ||
|
|
195b82408b | ||
|
|
5e58ea1796 | ||
|
|
d22b4009b0 | ||
|
|
a8d42f86c4 | ||
|
|
a3319cd3be | ||
|
|
a6d21a65cb | ||
|
|
2d006d5e2b | ||
|
|
81d07aa6e5 | ||
|
|
306be6b82d | ||
|
|
c2532ae11f | ||
|
|
345d711570 |
@@ -1,6 +1,6 @@
|
||||
# FromZero / Language
|
||||
|
||||
[README in Russian](./README.ru.md)
|
||||
[README in Russian](./README.ru.md) · [Documentation](./docs/en/index.md)
|
||||
|
||||
**FromZero/Language** is a package consisting of a JIT compiler and a virtual machine for the FZL language.
|
||||
|
||||
@@ -24,14 +24,6 @@ At the moment, the language has the following capabilities:
|
||||
Working examples of code can be found in `exl/` directory.
|
||||
|
||||
|
||||
## File extensions
|
||||
|
||||
Standard file extensions:
|
||||
|
||||
- `.fzs` - source code
|
||||
- `.fzb` - compiled binary file
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
1. Clone the repository: `git clone https://git.samsonium.dev/samsonium/fz-lang.git`
|
||||
|
||||
+1
-9
@@ -1,6 +1,6 @@
|
||||
# FromZero / Language
|
||||
|
||||
[README in English](./README.md)
|
||||
[README in English](./README.md) · [Документация](./docs/ru/index.md)
|
||||
|
||||
**FromZero/Language** - пакет из JIT-компилятора и виртуальной машины для языка FZL.
|
||||
|
||||
@@ -24,14 +24,6 @@
|
||||
Примеры кода лежат в директории `exl/`.
|
||||
|
||||
|
||||
## Расширения файлов
|
||||
|
||||
Стандартными для языка являются два расширения файлов:
|
||||
|
||||
- `.fzs` - исходный код
|
||||
- `.fzb` - скомпилированный бинарный файл
|
||||
|
||||
|
||||
## Инструкции по сборке
|
||||
|
||||
1. Склонировать проект: `git clone https://git.samsonium.dev/samsonium/fz-lang.git`
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Introduction
|
||||
|
||||
Welcome to this short book on the basics of FZLang, and thank you for giving this programming language a try.
|
||||
|
||||
First, let's talk about what FZLang is and how this book is organized.
|
||||
|
||||
## What is FZLang?
|
||||
|
||||
FZLang is a programming language with its own toolchain, which includes a compiler and a virtual machine. The compiler checks and compiles source code into a portable file, and the virtual machine runs programs written in FZLang.
|
||||
|
||||
This project originally began as a way to learn about building compilers. I (Samsonium) wanted to explore a few questions:
|
||||
|
||||
- Why is there a meaningful difference between a compiler and an interpreter?
|
||||
- How does a program called a compiler understand what I want it to do?
|
||||
- How does execution of a compiled program work? And how does the process of translating from human-readable code to machine instructions happen?
|
||||
|
||||
I've been programming for more than ten years, but had never taken the time to find out!
|
||||
|
||||
So this project became a place to experiment and learn more about the foundations of the tools programmers use every day. Along the way, the language began to take shape and develop a philosophy of its own.
|
||||
|
||||
Today, I would describe that philosophy as: “Be as convenient as a tool, while always trying to remain predictable.” I imagine every language community wants something similar for its language, but this principle has helped me make choices at important points in the project's development.
|
||||
|
||||
### How FZLang handles memory
|
||||
|
||||
The question first came up when I introduced stacks to the virtual machine: “A garbage collector, or something else?” Learning how garbage collection works would certainly have been interesting. However, having chosen Java as my first language, I was familiar with its “weight” and runtime behavior. Of course, its garbage collector deserves some of the credit for that.
|
||||
|
||||
So instead of a GC, I decided to explore RAII. This led to a simple model for how objects move through a program: when an object is passed as an argument, it is “borrowed” into a nested environment; when returned, it is moved into the parent environment. This approach avoids a GC and, most importantly, gives objects a deterministic lifetime—and therefore more predictable memory use, as far as possible.
|
||||
|
||||
In short, FZLang uses scope-based RAII.
|
||||
|
||||
---
|
||||
|
||||
Next: [Getting started](./01_getting-started.md)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# Getting started
|
||||
|
||||
To get started with FZLang, you need two executable files on your computer:
|
||||
|
||||
- `fzc` — the compiler
|
||||
- `fz` — the virtual machine
|
||||
|
||||
To compile a program, run `fzc` and pass it the paths to your source files. If compilation succeeds, run the compiled program with `fz`, passing the path to the compiled file. If you do not specify an output path for the compiler, it saves the compiled program as `a.fzb`.
|
||||
|
||||
## Compiler options
|
||||
|
||||
To see the available compiler options, run `fzc --help` (or `fzc -h`). The help currently looks like this:
|
||||
|
||||
```
|
||||
Usage: fzc [flags] <sources...>
|
||||
Available flags:
|
||||
-h --help Print help
|
||||
-v --verbose Display compilation info (tokens, AST, consts table, etc)
|
||||
-V --version Print compiler version
|
||||
-o --out Set compiled file name
|
||||
```
|
||||
|
||||
The `-h` (`--help`) flag prints the same help text. The `-v` / `--verbose` flags print detailed compilation information, including:
|
||||
|
||||
- The tokens that were read;
|
||||
- The syntax tree that was built;
|
||||
- The values stored in the constants table;
|
||||
- The opcodes written to the program.
|
||||
|
||||
The `-V` / `--version` flags print the compiler version. The `-o` / `--out` flags let you choose the output file path.
|
||||
|
||||
## Virtual machine options
|
||||
|
||||
The virtual machine has two options: `--help` (`-h`) and `-d`.
|
||||
|
||||
- `--help` (or `-h`) prints the help text;
|
||||
- `-d <path to file>` prints the opcodes with their indices. Use this flag to see how the VM will read your program.
|
||||
|
||||
---
|
||||
|
||||
Next: [Basics and types](./02_basics-and-types.md)
|
||||
|
||||
Previous: [Introduction](./00_introduction.md)
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
# Basics and types
|
||||
|
||||
In our first program, we asked FZLang to print some text. That's a good start: a program isn't mysterious; it's a set of instructions that the computer carries out one after another. Now let's see how those instructions work, how to store data, and how to ask a program to make a decision.
|
||||
|
||||
Don't worry if some words—such as “type” or “expression”—sound unfamiliar for now. We'll unpack them with small examples. Try changing the values as you read: seeing what changes is one of the best ways to understand what a program does.
|
||||
|
||||
## A first program: instructions and comments
|
||||
|
||||
The built-in `printline` function writes the value you pass to it to standard output:
|
||||
|
||||
```fz
|
||||
printline("Hello, FZLang!");
|
||||
```
|
||||
|
||||
Here we call `printline` and pass it text inside parentheses. The quotation marks show where the text begins and ends. The semicolon marks the end of the instruction. Try replacing the greeting with your own—the program will print the text you gave it.
|
||||
|
||||
Code doesn't have to contain only instructions. You can add notes for yourself and other readers, called comments. A single-line comment starts with `//`; the program ignores everything from those characters to the end of the line:
|
||||
|
||||
```fz
|
||||
// First, calculate a result, then print it.
|
||||
printline(2 + 2);
|
||||
```
|
||||
|
||||
A comment doesn't affect how the program runs. It helps explain why the code is there, especially when you come back to it later.
|
||||
|
||||
## Variables: giving values names
|
||||
|
||||
Imagine that a program needs to use the same value more than once. You could write it out every time, but it's more convenient to save it under a name. That's what variables are: named places to keep values.
|
||||
|
||||
To declare a variable, write its type, its name, and, if you like, an initial value:
|
||||
|
||||
```fz
|
||||
int count = 10;
|
||||
str greeting = "Hello there!";
|
||||
bool ready = true;
|
||||
```
|
||||
|
||||
These lines create three variables: `count` stores a number, `greeting` stores text, and `ready` stores a yes-or-no value. A type helps the language understand what kind of value we're working with. For example, numbers can be added, while text is useful for messages.
|
||||
|
||||
You can also declare a variable first and assign a value to it later:
|
||||
|
||||
```fz
|
||||
u64 total;
|
||||
total = 15;
|
||||
printline(total);
|
||||
```
|
||||
|
||||
Once a variable has been declared, you can use its name to read its value or assign it a new one. For example, `count = count + 1;` means: take the current value of `count`, add one, and store the result back in `count`. If it was 10, it will be 11 afterward.
|
||||
|
||||
### Numbers: integers and their sizes
|
||||
|
||||
FZLang has signed and unsigned integer types. Signed numbers can be negative; unsigned types are for non-negative values. For example, a temperature below zero fits a signed type, while a visitor count can use an unsigned type:
|
||||
|
||||
```fz
|
||||
int temperature = -5;
|
||||
uint visitors = 42;
|
||||
```
|
||||
|
||||
For more precise choices, there are fixed-width types: `i8`, `i16`, `i32`, `i64` and their unsigned counterparts `u8`, `u16`, `u32`, `u64`. The number after `i` or `u` tells you the type's width in bits. `int` and `uint` are the size of a machine word.
|
||||
|
||||
```fz
|
||||
i16 offset = -120;
|
||||
u8 level = 7;
|
||||
```
|
||||
|
||||
For now, it's enough to remember that `int` works well for ordinary integers. You can choose an exact width when it matters. Numeric types are compatible with one another under the language's rules, but you still state a variable's type when declaring it.
|
||||
|
||||
### Boolean values and text
|
||||
|
||||
The `bool` type has just two values: `true` and `false`. It's useful for storing the answer to a question—for example, whether a door is open or a program is ready to continue.
|
||||
|
||||
```fz
|
||||
bool is_open = true;
|
||||
printline(is_open);
|
||||
```
|
||||
|
||||
Text uses the `str` type, short for “string”:
|
||||
|
||||
```fz
|
||||
str language = "FZLang";
|
||||
printline(language);
|
||||
```
|
||||
|
||||
The type reference also lists `char`, but it isn't currently used by the language. We'll leave classes and object values for later sections; for now, numbers, text, and boolean values are plenty to get comfortable with.
|
||||
|
||||
## Expressions: asking the program to calculate
|
||||
|
||||
An expression is a piece of code that can be evaluated to produce a value. The simple expression `2 + 2` is one example. Expressions can be stored in variables or passed to functions:
|
||||
|
||||
```fz
|
||||
int result = 2 + 2 * 2;
|
||||
printline(result);
|
||||
```
|
||||
|
||||
The program evaluates the expression, stores the result in `result`, and then prints it. Just as in ordinary arithmetic, multiplication happens before addition, so the result is 6. To change the order, use parentheses: `(2 + 2) * 2`.
|
||||
|
||||
Numbers support addition `+`, subtraction `-`, multiplication `*`, division `/`, remainder `%`, and exponentiation `^`. A unary minus lets you write a negative number, such as `-5`.
|
||||
|
||||
Comparisons let us ask questions about values. `==` and `!=` check whether values are equal or unequal; `<`, `<=`, `>` and `>=` compare numbers. A comparison produces a boolean value, either `true` or `false`:
|
||||
|
||||
```fz
|
||||
bool enough = result >= 4 && result != 0;
|
||||
printline(enough);
|
||||
```
|
||||
|
||||
Here `enough` is true only if `result` is at least 4 and is not zero. The `&&` operator means “and,” `||` means “or,” and `!` flips a boolean value. The [Operators](../reference/operators.md) reference lists all operations and their limitations. Note that comparing objects with `==` or `!=` checks whether the variables refer to the same object, not whether the objects contain the same values.
|
||||
|
||||
## Blocks and conditions: choosing what happens next
|
||||
|
||||
Sometimes a program needs to do different things in different situations. The `if` condition lets it choose a path. It checks a boolean expression and runs the matching branch:
|
||||
|
||||
```fz
|
||||
bool is_ready = true;
|
||||
|
||||
if (is_ready)
|
||||
{
|
||||
printline("We can continue");
|
||||
}
|
||||
else
|
||||
{
|
||||
printline("Let's wait");
|
||||
}
|
||||
```
|
||||
|
||||
If `is_ready` is `true`, the program prints the first message; otherwise, it prints the second. You can leave out `else` when there's nothing to do in the other case. If there are more than two possibilities, add another condition with `else if`.
|
||||
|
||||
Curly braces `{}` group instructions into a block. You've already seen one in the condition example; functions use blocks too. Blocks make it clear which instructions belong together. An `if` condition must have type `bool`, such as a comparison result or a boolean variable.
|
||||
|
||||
## Functions: putting useful actions together
|
||||
|
||||
When you want to use the same sequence of actions in different places, you can put it in a function. A function receives values through parameters, runs instructions, and can return a result.
|
||||
|
||||
```fz
|
||||
fn sum(int left, int right) int
|
||||
{
|
||||
return left + right;
|
||||
}
|
||||
|
||||
int result = sum(2, 3);
|
||||
printline(result);
|
||||
```
|
||||
|
||||
A declaration starts with `fn`, followed by the function name, its parameters, and its result type. Here, `sum` takes two `int` values, adds them, and returns an `int`. The `return` keyword sends the result back to the code that called the function. That's why `sum(2, 3)` can be used as an expression—for example, to initialize a variable.
|
||||
|
||||
## What to remember
|
||||
|
||||
You've now met some of the basic building blocks of a program: instructions, comments, variables, types, expressions, conditions, and functions. There's no need to memorize every operator right away; you can return to the reference as you need it.
|
||||
|
||||
- A variable has a name and a type, and stores a value.
|
||||
- For your first programs, you'll use numbers (`int`, `uint`, and fixed-width types), boolean values (`bool`), and strings (`str`).
|
||||
- Expressions calculate new values, and comparisons help a program make decisions.
|
||||
- `if` conditions and `{}` blocks control which instructions the program runs.
|
||||
- Functions group actions and can return a result.
|
||||
- Instructions usually end with a semicolon, and `printline` helps you see what your program did.
|
||||
|
||||
---
|
||||
|
||||
Next: [Type system](../reference/types_system.md)
|
||||
|
||||
Previous: [Getting started](./01_getting-started.md)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# The FZLang book
|
||||
|
||||
A gentle introduction to FZLang, from getting set up to writing programs with variables, types, and functions.
|
||||
|
||||
- [Introduction](./00_introduction.md)
|
||||
- [Getting started](./01_getting-started.md)
|
||||
- [Basics and types](./02_basics-and-types.md)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# FZLang documentation
|
||||
|
||||
Choose a section to explore the FZLang documentation.
|
||||
|
||||
- [The book](./book/index.md) — a guided introduction to the language.
|
||||
- [Reference](./reference/index.md) — types, operators, grammar, and runtime diagnostics.
|
||||
- [Internals](./internals/index.md) — virtual machine implementation details.
|
||||
- [Standard library](./stdlib/index.md) — built-in functions.
|
||||
- [Language philosophy](./philosopy.md)
|
||||
|
||||
[Документация на русском языке](../ru/index.md)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Internals
|
||||
|
||||
Implementation details of the FZLang virtual machine and compiled programs.
|
||||
|
||||
- [Object layout](./object_layout.md)
|
||||
- [Opcodes](./opcodes.md)
|
||||
- [Virtual machine stacks](./stacks.md)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Object layout
|
||||
|
||||
This page is a placeholder for documentation of the in-memory layout of FZLang objects.
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
# Opcodes
|
||||
|
||||
FZLang programs run on the virtual machine included in the FZLang toolchain, which is built alongside the compiler. The FZC compiler can emit the following opcodes, which the VM understands and executes.
|
||||
|
||||
## Loading data and working with the stack
|
||||
|
||||
Codes: `0xF00001`..`0xF00004`
|
||||
|
||||
| Number | Name | Purpose | Argument |
|
||||
| ------ | ---- | ------- | -------- |
|
||||
| 0xF00001 | FZL_OPCODE_LOADCONST | Push constant `N` onto the stack | `N` — constant index |
|
||||
| 0xF00002 | FZL_OPCODE_CALL_BUILTIN | Call built-in function `N` | `N` — function index |
|
||||
| 0xF00003 | FZL_OPCODE_PUSHNUM | Push number `N` onto the stack | `N` — value |
|
||||
| 0xF00004 | FZL_OPCODE_PUSHBOOL | Push boolean `B` onto the stack | `B` — value |
|
||||
|
||||
## Context and environment
|
||||
|
||||
Codes: `0xF10001`..`0xF10005`
|
||||
|
||||
| Number | Name | Purpose |
|
||||
| ------ | ---- | ------- |
|
||||
| 0xF10001 | FZL_OPCODE_MK_ENVTABLE | Create a new environment |
|
||||
| 0xF10002 | FZL_OPCODE_RM_ENVTABLE | Destroy an environment |
|
||||
| 0xF10003 | FZL_OPCODE_STORE | Create a name in the environment from the value at the top of the stack |
|
||||
| 0xF10004 | FZL_OPCODE_LOOKUP_ENV | Take a name from the stack, look it up in the environment, and push its value |
|
||||
| 0xF10005 | FZL_OPCODE_WRITE | Take a name and value from the stack and write the value to the environment |
|
||||
|
||||
## Control flow
|
||||
|
||||
Codes: `0xF20001`..`0xF20002`
|
||||
|
||||
| Number | Name | Purpose | Argument |
|
||||
| ------ | ---- | ------- | -------- |
|
||||
| 0xF20001 | FZL_OPCODE_JMP | Jump to address `IP` | `IP` — target address |
|
||||
| 0xF20002 | FZL_OPCODE_JZ | Jump to address `IP` if the value at the top of the stack is zero | `IP` — target address |
|
||||
|
||||
## Function calls
|
||||
|
||||
Codes: `0xF30001`..`0xF30002`
|
||||
|
||||
| Number | Name | Purpose |
|
||||
| ------ | ---- | ------- |
|
||||
| 0xF30001 | FZL_OPCODE_CALL | Push the current address onto the call stack and jump to the start of a function |
|
||||
| 0xF30002 | FZL_OPCODE_RET | Pop the last address from the call stack and jump to it |
|
||||
|
||||
## Objects
|
||||
|
||||
Codes: `0xFA0001`..`0xFA0005`
|
||||
|
||||
| Number | Name | Purpose | Argument |
|
||||
| ------ | ---- | ------- | -------- |
|
||||
| 0xFA0001 | FZL_OPCODE_ALLOC_OBJECT | Allocate an object for `N` values and push it onto the stack | `N` — number of values |
|
||||
| 0xFA0002 | FZL_OPCODE_GET_FIELD | Pop an object reference from the stack and get field `N` | `N` — field index |
|
||||
| 0xFA0003 | FZL_OPCODE_SET_FIELD | Pop an object reference and a value, then write the value to field `N` | `N` — field index |
|
||||
| 0xFA0004 | FZL_OPCODE_ESCAPE_OBJECT | Move an object (including its children) to the parent environment | none |
|
||||
|
||||
## Arithmetic operations
|
||||
|
||||
Codes: `0xA00001`..`0xA00007`
|
||||
|
||||
| Number | Name | Purpose |
|
||||
| ------ | ---- | ------- |
|
||||
| 0xA00001 | FZL_OPCODE_ADD | Pop two numbers, add them, and push the result |
|
||||
| 0xA00002 | FZL_OPCODE_SUB | Pop two numbers, subtract the first from the second, and push the result |
|
||||
| 0xA00003 | FZL_OPCODE_MUL | Pop two numbers, multiply them, and push the result |
|
||||
| 0xA00004 | FZL_OPCODE_DIV | Pop two numbers, divide the first by the second, and push the result |
|
||||
| 0xA00005 | FZL_OPCODE_POW | Pop two numbers, raise the second to the power of the first, and push the result |
|
||||
| 0xA00006 | FZL_OPCODE_MOD | Pop two numbers and push the remainder of the second divided by the first |
|
||||
| 0xA00007 | FZL_OPCODE_NEG | Pop a number, negate it, and push it back onto the stack |
|
||||
|
||||
## Numeric comparisons
|
||||
|
||||
Codes: `0xB00001`..`0xB00006`
|
||||
|
||||
| Number | Name | Purpose |
|
||||
| ------ | ---- | ------- |
|
||||
| 0xB00001 | FZL_OPCODE_CMP_EQ | Pop two numbers, compare them for equality, and push the result |
|
||||
| 0xB00002 | FZL_OPCODE_CMP_NEQ | Pop two numbers, compare them for inequality, and push the result |
|
||||
| 0xB00003 | FZL_OPCODE_CMP_LT | Pop two numbers, check whether the second is less than the first, and push the result |
|
||||
| 0xB00004 | FZL_OPCODE_CMP_LTEQ | Pop two numbers, check whether the second is less than or equal to the first, and push the result |
|
||||
| 0xB00005 | FZL_OPCODE_CMP_GT | Pop two numbers, check whether the second is greater than the first, and push the result |
|
||||
| 0xB00006 | FZL_OPCODE_CMP_GTEQ | Pop two numbers, check whether the second is greater than or equal to the first, and push the result |
|
||||
|
||||
## String comparisons
|
||||
|
||||
Codes: `0xB10001`..`0xB10002`
|
||||
|
||||
| Number | Name | Purpose |
|
||||
| ------ | ---- | ------- |
|
||||
| 0xB10001 | FZL_OPCODE_CMP_EQ_STR | Pop two strings, compare them for equality, and push the result |
|
||||
| 0xB10002 | FZL_OPCODE_CMP_NEQ_STR | Pop two strings, compare them for inequality, and push the result |
|
||||
|
||||
## Object comparisons
|
||||
|
||||
Codes: `0xB20001`..`0xB20002`
|
||||
|
||||
| Number | Name | Purpose |
|
||||
| ------ | ---- | ------- |
|
||||
| 0xB20001 | FZL_OPCODE_CMP_EQ_OBJ | Pop two objects, compare them for equality, and push the result |
|
||||
| 0xB20002 | FZL_OPCODE_CMP_NEQ_OBJ | Pop two objects, compare them for inequality, and push the result |
|
||||
|
||||
## Binary logical operations
|
||||
|
||||
Codes: `0xC00001`..`0xC00002`
|
||||
|
||||
| Number | Name | Purpose |
|
||||
| ------ | ---- | ------- |
|
||||
| 0xC00001 | FZL_OPCODE_AND | Pop two values and push their logical AND |
|
||||
| 0xC00002 | FZL_OPCODE_OR | Pop two values and push their logical OR |
|
||||
|
||||
## Unary logical operations
|
||||
|
||||
Code: `0xD00001`
|
||||
|
||||
| Number | Name | Purpose |
|
||||
| ------ | ---- | ------- |
|
||||
| 0xD00001 | FZL_OPCODE_NOT | Pop a value and push its inverted boolean result |
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# Virtual machine stacks
|
||||
|
||||
While running, the FZLang virtual machine relies on three stacks: the data stack, the environment stack, and the call stack. Each stack is initialized as an array of 256 values and then zeroed. All three share the same basic property: the most recently added value is at the top and is the first one removed, decrementing the stack-top pointer.
|
||||
|
||||
## Data stack
|
||||
|
||||
The data stack is an array of 256 `fzl_value_t` elements. The `fzl_value_t` structure is a container for values used while a program runs.
|
||||
|
||||
```c
|
||||
typedef struct
|
||||
{
|
||||
fzl_type_t type;
|
||||
|
||||
union
|
||||
{
|
||||
void *obj;
|
||||
|
||||
struct
|
||||
{
|
||||
const char *value;
|
||||
size_t length;
|
||||
} s;
|
||||
|
||||
char c;
|
||||
bool b;
|
||||
|
||||
int8_t i8;
|
||||
uint8_t u8;
|
||||
|
||||
int16_t i16;
|
||||
uint16_t u16;
|
||||
|
||||
int32_t i32;
|
||||
uint32_t u32;
|
||||
|
||||
int64_t i64;
|
||||
uint64_t u64;
|
||||
|
||||
intptr_t i;
|
||||
uintptr_t u;
|
||||
} as;
|
||||
} fzl_value_t;
|
||||
```
|
||||
|
||||
The VM uses this stack to store and retrieve intermediate values while the program runs. The stack should be empty when execution finishes.
|
||||
|
||||
## Environment stack
|
||||
|
||||
The environment stack tracks the lexical environment in which evaluation and instructions are currently taking place. It is an array of 256 `fzl_vm_env_t` elements.
|
||||
|
||||
```c
|
||||
typedef struct
|
||||
{
|
||||
size_t size;
|
||||
size_t remainder;
|
||||
|
||||
fzl_vm_env_entry_t *table;
|
||||
|
||||
fzl_vm_object_t **owned_objects;
|
||||
size_t owned_objects_remainder;
|
||||
size_t owned_objects_count;
|
||||
} fzl_vm_env_t;
|
||||
```
|
||||
|
||||
Each stack element stores a table of local names (`fzl_vm_env_entry_t`) and the objects owned by the current environment. A local name is a container that wraps an `fzl_value_t` and gives it a name:
|
||||
|
||||
```c
|
||||
typedef struct
|
||||
{
|
||||
char *name;
|
||||
fzl_value_t value;
|
||||
} fzl_vm_env_entry_t;
|
||||
```
|
||||
|
||||
An object is an array of its fields. The number of fields is set when the object is allocated by the `FZL_OPCODE_ALLOC_OBJECT N` opcode (see the [opcode reference](./opcodes.md) for details).
|
||||
|
||||
```c
|
||||
typedef struct
|
||||
{
|
||||
fzl_value_t *values;
|
||||
size_t values_count;
|
||||
} fzl_vm_object_t;
|
||||
```
|
||||
|
||||
An environment is created or removed by the `FZL_OPCODE_MK_ENVTABLE` and `FZL_OPCODE_RM_ENVTABLE` opcodes, respectively. If the compiler sees a value of type `FZL_TYPE_OBJECT` being returned from a function, it inserts `FZL_OPCODE_ESCAPE_OBJECT` before `FZL_OPCODE_RET` and `FZL_OPCODE_RM_ENVTABLE`. This moves ownership of the object to the parent environment.
|
||||
|
||||
## Call stack
|
||||
|
||||
The call stack stores instruction addresses when entering a function through the `FZL_OPCODE_CALL` opcode. `FZL_OPCODE_RET` pops an address from the call stack and jumps to it.
|
||||
|
||||
The stack itself is an array of 256 `size_t` elements.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Language philosophy
|
||||
|
||||
FZLang has two guiding goals: “be convenient as a tool, while always trying to remain predictable.”
|
||||
|
||||
Here are some of the ways the language tries to follow that philosophy.
|
||||
|
||||
## Memory and resources
|
||||
|
||||
In environments with a garbage collector (GC), programmers don't know exactly when the collector will run. An object may remain in memory for seconds after a method has finished. And when cleanup does happen, the runtime may pause briefly—something that can matter in audio, games, networking, or embedded systems.
|
||||
|
||||
FZLang uses scope-based RAII, a model in which memory use is deterministic. Every object belongs to a scope, and when that scope is destroyed, the object is destroyed with it. This makes response times more consistent and predictable.
|
||||
|
||||
Scope-based RAII also helps manage resources such as sockets and files. Resource cleanup follows the same clear rule: when a scope ends, everything attached to it is cleaned up.
|
||||
|
||||
Finally, scope-based RAII avoids the runtime overhead of a full garbage collector. A GC needs mechanisms such as memory maps, background threads, and read and write barriers. FZLang's virtual machine is small, and memory is released as instructions are processed.
|
||||
|
||||
## Syntax
|
||||
|
||||
FZLang deliberately keeps some parts of its syntax explicit. For example, methods of a class instance must use `this`, and declarations require types. These are strict parts of the language.
|
||||
|
||||
On the other hand, a function declaration can omit a return type when it doesn't return a value; writing `void` every time would be unnecessary.
|
||||
|
||||
In this way, FZLang aims to balance syntax that keeps behavior predictable with simplifications that are genuinely useful.
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# Error output
|
||||
|
||||
When a runtime error occurs, the VM prints its state at the time it stopped. The output includes:
|
||||
|
||||
1. The instruction pointer;
|
||||
2. The data stack;
|
||||
3. The environment stack;
|
||||
4. The call stack.
|
||||
|
||||
## Instruction pointer (IP)
|
||||
|
||||
The instruction pointer is useful for debugging alongside the program's opcode listing. Run the VM with the `--debug` (`-d`) flag and the path to a compiled program to print its opcodes without running it.
|
||||
|
||||
The output looks roughly like this:
|
||||
|
||||
```
|
||||
0000: MK_ENVTABLE
|
||||
0001: JMP IP: 53
|
||||
0003: MK_ENVTABLE
|
||||
0004: LOADCONST #0 ('a')
|
||||
0006: STORE
|
||||
0007: LOADCONST #0 ('a')
|
||||
0009: WRITE
|
||||
0010: MK_ENVTABLE
|
||||
0011: LOADCONST #0 ('a')
|
||||
0013: LOOKUP_ENV
|
||||
0014: GET_FIELD #0
|
||||
...
|
||||
```
|
||||
|
||||
The instruction index helps you find the instruction where the exception occurred. See the [opcode reference](../internals/opcodes.md) for the complete list and descriptions.
|
||||
|
||||
## Data stack
|
||||
|
||||
The data stack is printed as a list of values in the form `[T] V`, where `T` is the type number and `V` is the value. Strings and objects are prefixed with `str: V` and `ref: V`, respectively.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
> Data stack:
|
||||
> [2] str: 'this'
|
||||
```
|
||||
|
||||
Values are printed from top to bottom, so the most recent values appear first.
|
||||
|
||||
## Environment stack
|
||||
|
||||
The environment stack is printed as two lists: objects owned by the environment and names local to it.
|
||||
|
||||
Example environment:
|
||||
|
||||
```
|
||||
> Environments:
|
||||
> [1] 0x55a9d8196a20:
|
||||
> Owned objects (0/2):
|
||||
> Local entries (3/4):
|
||||
> [14] b = 4
|
||||
> [1] a = ref 0x55a9d81969e0
|
||||
> [0] this = 0
|
||||
> [0] 0x55a9d8196930:
|
||||
> Owned objects (1/2):
|
||||
> ref: 0x55a9d81969e0 (entries: 1)
|
||||
> Local entries (1/2):
|
||||
> [1] sum = ref 0x55a9d81969e0
|
||||
```
|
||||
|
||||
`> [N] A` identifies the environment table itself. `N` is the table's nesting level (larger values mean a deeper environment), and `A` is its memory address.
|
||||
|
||||
The first list is `Owned objects`; it shows the number of elements and the allocated array size as `(<count>/<capacity>)`. Objects are listed as `ref: A (entries: N)`, where `A` is the object's memory address and `N` is the number of fields it contains.
|
||||
|
||||
The second list is `Local entries`, with the number of entries and allocated capacity. Each entry has the form `[T] N = V`, where `T` is the type number, `N` is the entry name, and `V` is its value.
|
||||
|
||||
## Call stack
|
||||
|
||||
The call stack contains return addresses. An address is pushed when the `CALL` opcode runs and popped by `RET`.
|
||||
|
||||
```
|
||||
> Call stack:
|
||||
> [0] 53
|
||||
```
|
||||
|
||||
Each entry has the form `[L] IP`, where `L` is the nesting level (larger means deeper) and `IP` is the instruction index to jump to after returning.
|
||||
|
||||
## Complete output
|
||||
|
||||
A complete error report looks like this:
|
||||
|
||||
```
|
||||
===== ERROR =====
|
||||
> Caused on IP: 21
|
||||
|
||||
> Data stack:
|
||||
> [2] str: 'this'
|
||||
|
||||
> Environments:
|
||||
> [1] 0x55a9d8196a20:
|
||||
> Owned objects (0/2):
|
||||
> Local entries (3/4):
|
||||
> [14] b = 4
|
||||
> [1] a = ref 0x55a9d81969e0
|
||||
> [0] this = 0
|
||||
> [0] 0x55a9d8196930:
|
||||
> Owned objects (1/2):
|
||||
> ref: 0x55a9d81969e0 (entries: 1)
|
||||
> Local entries (1/2):
|
||||
> [1] sum = ref 0x55a9d81969e0
|
||||
|
||||
> Call stack:
|
||||
> [0] 53
|
||||
Error: runtime error - expected 2 values in data stack
|
||||
```
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/* FZL (FromZero Language) syntax */
|
||||
/* Version 1 */
|
||||
/* Authors: */
|
||||
/* - Samsonium (https://git.samsonium.dev/Samsonium) */
|
||||
|
||||
<program> ::= <_> <statement>+
|
||||
|
||||
<statement> ::= <class_stmt>
|
||||
| <fn_stmt>
|
||||
| <if_stmt>
|
||||
| <block_stmt>
|
||||
| <ret_stmt>
|
||||
| <expr_stmt>
|
||||
|
||||
<_> ::= " "*
|
||||
<ws> ::= " "+
|
||||
|
||||
<expr_stmt> ::= <expr> <_> ";" <_>
|
||||
<block_stmt> ::= "{" <_> <statement>* "}" <_>
|
||||
<ret_stmt> ::= "return" (<ws> <expr>)? <_> ";" <_>
|
||||
|
||||
<class_stmt> ::= "class" <ws> <name> <_> "{" <_> (<declaration> | <fn_stmt>)* "}" <_>
|
||||
<fn_stmt> ::= "fn" <ws> <name> <_> "(" <_> (<param> (<_> "," <_> <param>)*)? ")" <_> <block_stmt>
|
||||
<param> ::= <type> <ws> <name>
|
||||
|
||||
<if_stmt> ::= "if" <_> "(" <_> <expr> <_> ")" <_> <statement> (<_> "else" <ws> <statement>)?
|
||||
|
||||
/* Assign (lowest expression priority, right-associative) */
|
||||
<expr> ::= <assign_expr>
|
||||
<assign_expr> ::= <new_expr> (<_> "=" <_> <assign_expr>)?
|
||||
<new_expr> ::= ("new" <ws> <name>) | <or_expr>
|
||||
<or_expr> ::= <and_expr> (<_> "||" <_> <and_expr>)*
|
||||
<and_expr> ::= <cond_expr> (<_> "&&" <_> <cond_expr>)*
|
||||
<cond_expr> ::= <term_expr> (<_> <cond_op> <_> <term_expr>)*
|
||||
<cond_op> ::= "==" | "!=" | "<=" | ">=" | "<" | ">"
|
||||
|
||||
<term_expr> ::= <factor_expr> (<_> ("+" | "-") <_> <factor_expr>)*
|
||||
<factor_expr> ::= <power_expr> (<_> ("*" | "/" | "%") <_> <power_expr>)*
|
||||
<power_expr> ::= <unary_expr> (<_> "^" <_> <power_expr>)?
|
||||
<unary_expr> ::= (("!" | "-" | "+") <_>)* <postfix_expr>
|
||||
|
||||
/* Postfix: chain of calls and dots */
|
||||
<postfix_expr> ::= <primary_expr> (<call_suffix> | <member_suffix>)*
|
||||
<call_suffix> ::= <_> "(" <_> (<expr> (<_> "," <_> <expr>)*)? <_> ")"
|
||||
<member_suffix> ::= <_> "." <_> <name>
|
||||
|
||||
<primary_expr> ::= <literal>
|
||||
| <declaration>
|
||||
| "(" <_> <expr> <_> ")"
|
||||
| <name>
|
||||
|
||||
<declaration> ::= <type> <ws> <name>
|
||||
|
||||
<type> ::= "int" | "uint" | "bool" | "char" | "str"
|
||||
| "i8" | "i16" | "i32" | "i64"
|
||||
| "u8" | "u16" | "u32" | "u64"
|
||||
|
||||
<literal> ::= <number> | <string_literal> | "true" | "false" | "null"
|
||||
<name> ::= ([A-z] | "_") | ([A-z] | [0-9] | "_")*
|
||||
<string_literal> ::= ('"' [^"]* '"') | ("'" [^']* "'")
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Grammar (EBNF)
|
||||
|
||||
The formal grammar is maintained in the [grammar.ebnf](./grammar.ebnf) file.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Reference
|
||||
|
||||
Language and runtime reference material.
|
||||
|
||||
- [Operators](./operators.md)
|
||||
- [Type system](./types_system.md)
|
||||
- [Grammar (EBNF)](./grammar_ebnf.md)
|
||||
- [Error output](./errors_print.md)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Operators
|
||||
|
||||
FZLang supports arithmetic, comparison, logical, and unary operations. During execution, a binary operation takes two values from the stack and puts its result back. A unary operation takes one value from the stack and puts its result back.
|
||||
|
||||
## Arithmetic operations
|
||||
|
||||
| Operation | Supported types | Description |
|
||||
| --------- | --------------- | ----------- |
|
||||
| `N + N` | *all numeric types* | Add two operands |
|
||||
| `N - N` | *all numeric types* | Subtract the right operand from the left |
|
||||
| `N * N` | *all numeric types* | Multiply two operands |
|
||||
| `N / N` | *all numeric types* | Divide the left operand by the right |
|
||||
| `N % N` | *all numeric types* | Remainder of the left operand divided by the right |
|
||||
| `N ^ N` | *all numeric types* | Raise the left operand to the power of the right |
|
||||
| `-N` | *all numeric types* | Negate the operand |
|
||||
|
||||
## Comparison operations
|
||||
|
||||
| Operation | Supported types | Description |
|
||||
| --------- | --------------- | ----------- |
|
||||
| `N == N` | *all numeric types*, booleans, objects, strings | Check whether the operands are equal |
|
||||
| `N != N` | *all numeric types*, booleans, objects, strings | Check whether the operands are unequal |
|
||||
| `N > N` | *all numeric types* | Check whether the left operand is greater than the right |
|
||||
| `N >= N` | *all numeric types* | Check whether the left operand is greater than or equal to the right |
|
||||
| `N < N` | *all numeric types* | Check whether the left operand is less than the right |
|
||||
| `N <= N` | *all numeric types* | Check whether the left operand is less than or equal to the right |
|
||||
| `!N` | booleans | Invert the operand |
|
||||
|
||||
> **Important:** Comparing objects with `==` or `!=` compares their addresses, not their values.
|
||||
|
||||
## Logical operations
|
||||
|
||||
| Operation | Supported types | Description |
|
||||
| --------- | --------------- | ----------- |
|
||||
| `N && N` | booleans | Logical **AND** of two operands |
|
||||
| `N || N` | booleans | Logical **OR** of two operands |
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Type system
|
||||
|
||||
FZLang's type system has two foundations: primitives and objects.
|
||||
|
||||
Primitive types include strings, numeric types, booleans, and the internal object type. Objects currently include class instances and the `null` literal.
|
||||
|
||||
## Type table
|
||||
|
||||
| Internal name | Syntax | Description |
|
||||
| ------------- | ------ | ----------- |
|
||||
| FZL_TYPE_NULL | *none* | *currently unused* |
|
||||
| FZL_TYPE_OBJECT | *none* | Class instance or the `null` literal |
|
||||
| FZL_TYPE_STRING | `str` | String |
|
||||
| FZL_TYPE_CHAR | `char` | *currently unused* |
|
||||
| FZL_TYPE_BOOL | `bool` | Boolean |
|
||||
| FZL_TYPE_INT8 | `i8` | 8-bit signed integer |
|
||||
| FZL_TYPE_UINT8 | `u8` | 8-bit unsigned integer |
|
||||
| FZL_TYPE_INT16 | `i16` | 16-bit signed integer |
|
||||
| FZL_TYPE_UINT16 | `u16` | 16-bit unsigned integer |
|
||||
| FZL_TYPE_INT32 | `i32` | 32-bit signed integer |
|
||||
| FZL_TYPE_UINT32 | `u32` | 32-bit unsigned integer |
|
||||
| FZL_TYPE_INT64 | `i64` | 64-bit signed integer |
|
||||
| FZL_TYPE_UINT64 | `u64` | 64-bit unsigned integer |
|
||||
| FZL_TYPE_INT | `int` | Signed integer the size of a machine word |
|
||||
| FZL_TYPE_UINT | `uint` | Unsigned integer the size of a machine word |
|
||||
|
||||
## Compatibility
|
||||
|
||||
Type compatibility is checked using these rules:
|
||||
|
||||
1. If both types are objects (`FZL_TYPE_OBJECT`):
|
||||
1. If either type has the class name `<nullptr>`, the check **passes**.
|
||||
2. If both types have the same class name, the check **passes**.
|
||||
3. Otherwise, the check **fails**.
|
||||
2. If the types are equal, the check **passes**.
|
||||
3. If both types are numeric (`u8`..`uint`, `i8`..`int`), the check **passes**.
|
||||
4. If none of the rules above apply, the check **fails**.
|
||||
|
||||
**Passes** means the types are compatible; **fails** means they are not.
|
||||
|
||||
## Runtime behavior
|
||||
|
||||
Primitive values (that is, values other than `FZL_TYPE_OBJECT`) are used directly. Operations on them use the value stored in the stack slot.
|
||||
|
||||
Object values are stored on the stack as **references to objects**. Valid operations on them include:
|
||||
|
||||
- `FZL_OPCODE_ESCAPE_OBJECT` (move an object to the parent environment)
|
||||
- `FZL_OPCODE_GET_FIELD N` (read field `N` from an object)
|
||||
- `FZL_OPCODE_SET_FIELD N` (write a value to field `N` of an object)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Built-in functions
|
||||
|
||||
FZLang provides built-in functions that are available in every program.
|
||||
|
||||
## Exit: `exit`
|
||||
|
||||
```
|
||||
fn exit(uint code)
|
||||
```
|
||||
|
||||
Takes an unsigned integer argument and exits the program with the specified code.
|
||||
|
||||
## Print to stdout: `printline`
|
||||
|
||||
```
|
||||
fn printline(value)
|
||||
```
|
||||
|
||||
Accepts any value and writes it to standard output. Primitive values are printed directly according to their types. Objects are printed as `obj<...>`, where `...` is a list of their field values. Each field is printed with its type and value in the format `T<V>`, where `T` is the type name and `V` is the current value.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Standard library
|
||||
|
||||
Documentation for functions available to FZLang programs without additional imports.
|
||||
|
||||
- [Built-in functions](./builtins.md)
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
program := statement*
|
||||
statement := expression
|
||||
expression := call | string_literal
|
||||
call := name "(" argument-list? ")"
|
||||
argument-list := string_literal ("," string_literal)*
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Вступление
|
||||
|
||||
Добро пожаловать в небольшую книжку по основам языка FZLang и спасибо, что решились попробовать этот язык программирования.
|
||||
|
||||
Для начала, стоит рассказать, что это за язык и как будет идти обучение в этой "книжке".
|
||||
|
||||
## Что такое FZLang
|
||||
|
||||
FZLang - это язык программирования с собственным тулчейном, включающим в себя компилятор и виртуальную машину.
|
||||
Компилятор позволяет проверить и скомпилировать исходный код программы, чтобы сохранить в переносимый файл, а виртуальная машина - выполнить программу, написанную на FZLang.
|
||||
|
||||
Изначально, этот проект задумывался как самоучитель по компиляторостроению.
|
||||
Мне (Samsonium) было интересно разобрать:
|
||||
|
||||
- Почему между компилятором и интерпретатором есть значимая разница?
|
||||
- Как программа, под названием "компилятор" понимает, что я хочу сделать?
|
||||
- Как устроено исполнение скомпилированной программы? Да и как, вообще, происходит процесс "перевода" с человеческого на машинный?
|
||||
|
||||
Я занимаюсь программированием уже более 10 лет, но за всё время так и не удосужился об этом разузнать!
|
||||
|
||||
Поэтому, этот проект был рождён как поле для экспериментов, разрабатывая который я стану лучше понимать устройство того, на чём строится весь мир программистов.
|
||||
Однако, по ходу разработки у языка стала появлятся своя форма, а также философия.
|
||||
|
||||
Сейчас, я бы описал философию языка так: "быть удобным, как инструмент, но всегда стараться оставаться предсказуемым".
|
||||
Полагаю, любое комьюнити желает своему языку того же, но мне это помогло сделать определённый выбор в ключевых точках развития.
|
||||
|
||||
### Как в FZLang устроена работа с памятью
|
||||
|
||||
Изначально вопрос появился после внедрения стеков виртуальной машины, и звучал он так: "сборщик мусора или что-то другое?".
|
||||
Изучить работу сборщика было, конечно, интересно.
|
||||
Однако мне, человеку, выбравшему Java как первый язык, была знакома его "тяжесть" и "скорость" работы.
|
||||
Конечно же, отдельное "спасибо" за это можно сказать его сборщику мусора.
|
||||
|
||||
Поэтому, вместо GC я решил посмотреть в сторону RAII и, в итоге, получил хорошую формулу гуляния объекта в программе:
|
||||
если объект подан как аргумент, то происходит "заимствование" его во вложенное окружение, и если объект возвращается, то происходит перемещение объекта в родительское окружение.
|
||||
Такой подход позволяет не делать GC, а ещё, что самое важное, получить детерминированное время жизни объекта, а значит, детерминированную память (настолько, насколько это возможно).
|
||||
|
||||
Короче говоря, FZLang обзавёлся scope-based RAII.
|
||||
|
||||
---
|
||||
|
||||
Далее: [Начало работы](./01_getting-started.md)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Начало работы
|
||||
|
||||
Для того, чтобы начать работу с FZLang, достаточно установить себе на компьютер два исполяемых файла:
|
||||
|
||||
- `fzc` - компилятор
|
||||
- `fz` - виртуальная машина
|
||||
|
||||
Для того, чтобы скомпилировать программу, достаточно выполнить программу `fzc`, указав в качестве аргументов пути к файлам с исходным кодом.
|
||||
Если программа успешно скомпилировалась, то её можно запустить через программу `fz`, также указав путь скомпилированному файлу.
|
||||
Без указания компилятору пути к выходному файлу, скомпилированная программа будет сохранена как `a.fzb`.
|
||||
|
||||
## Настройки компилятора
|
||||
|
||||
Чтобы посмотреть о доступных настройках компилятора, выполните `fzc --help` (или `fzc -h`).
|
||||
На данный момент, справка выглядит так:
|
||||
|
||||
```
|
||||
Usage: fzc [flags] <sources...>
|
||||
Available flags:
|
||||
-h --help Print help
|
||||
-v --verbose Display compilation info (tokens, AST, consts table, etc)
|
||||
-V --version Print compiler version
|
||||
-o --out Set compiled file name
|
||||
```
|
||||
|
||||
Флаг `-h` (`--help`) просто выводит ту же справку. Флаги `-v` / `--verbose` делает подробный вывод компиляции:
|
||||
|
||||
- Какие лексемы были прочитаны;
|
||||
- Какое синтаксическое дерево было собрано;
|
||||
- Какие значения были записаны в таблицу констант;
|
||||
- Какие опкоды были записаны в программу.
|
||||
|
||||
Флаги `-V` / `--version` выводят версию компилятора. Флаги `-o`, `--out` позволяют задать путь выходного файла.
|
||||
|
||||
## Настройки виртуальной машины
|
||||
|
||||
У виртуальной машины есть всего два флага: `--help` (`-h`) и `-d`.
|
||||
|
||||
- `--help` (или `-h`) выводит справку;
|
||||
- `-d <путь к файлу>` выводит список опкодов с нумерацией. С помощью этого флага вы можете посмотреть, как вашу программу будет читать ВМ.
|
||||
|
||||
---
|
||||
|
||||
Далее: [Основы и типы](./02_basics-and-types.md)
|
||||
|
||||
Назад: [Вступление](./00_introduction.md)
|
||||
@@ -0,0 +1,199 @@
|
||||
# Основы и типы
|
||||
|
||||
В первой программе мы уже попросили FZLang вывести текст на экран.
|
||||
Это хороший старт: программа — не что-то загадочное, а набор инструкций, которые компьютер выполняет одну за другой.
|
||||
Теперь разберёмся, как эти инструкции устроены, как сохранить данные и как попросить программу принять решение.
|
||||
|
||||
Не переживайте, если некоторые слова — например, «тип» или «выражение» — пока звучат непривычно.
|
||||
Мы будем разбирать их на небольших примерах.
|
||||
Попробуйте не только читать код, но и менять значения: так гораздо легче увидеть, что именно делает программа.
|
||||
|
||||
## Первая программа: инструкция и комментарий
|
||||
|
||||
Встроенная функция `printline` выводит переданное ей значение в стандартный вывод:
|
||||
|
||||
```fz
|
||||
printline("Привет, FZLang!");
|
||||
```
|
||||
|
||||
Здесь мы вызываем функцию `printline` и передаём ей текст в круглых скобках.
|
||||
Кавычки показывают, где текст начинается и заканчивается.
|
||||
Точка с запятой сообщает, что инструкция завершена.
|
||||
Попробуйте заменить приветствие на своё — программа выведет именно тот текст, который вы ей передали.
|
||||
|
||||
Код не обязан состоять только из команд.
|
||||
В него можно добавлять пояснения для себя и других читателей — комментарии.
|
||||
Однострочный комментарий начинается с `//`; всё, что стоит после этих символов до конца строки, программа пропускает:
|
||||
|
||||
```fz
|
||||
// Сначала посчитаем, потом выведем результат.
|
||||
printline(2 + 2);
|
||||
```
|
||||
|
||||
Комментарий не влияет на выполнение программы. Он помогает объяснить, зачем написан код, особенно когда вы вернётесь к нему позже.
|
||||
|
||||
## Переменные: давайте дадим значениям имена
|
||||
|
||||
Представьте, что программе нужно несколько раз использовать одно и то же значение.
|
||||
Можно каждый раз писать его заново, но удобнее сохранить его под именем.
|
||||
Для этого существуют переменные — именованные места для значений.
|
||||
|
||||
При объявлении переменной указывают её тип, имя и, если нужно, начальное значение:
|
||||
|
||||
```fz
|
||||
int count = 10;
|
||||
str greeting = "Здравствуйте!";
|
||||
bool ready = true;
|
||||
```
|
||||
|
||||
В этих строках мы создали три переменные: `count` хранит число, `greeting` — текст, а `ready` — ответ «да» или «нет».
|
||||
Тип помогает языку понять, с каким значением мы работаем. Например, число можно складывать, а текст удобно выводить как сообщение.
|
||||
|
||||
Переменную можно сначала объявить, а значение присвоить позже:
|
||||
|
||||
```fz
|
||||
u64 total;
|
||||
total = 15;
|
||||
printline(total);
|
||||
```
|
||||
|
||||
Когда переменная уже объявлена, её имя можно использовать, чтобы прочитать значение или присвоить новое.
|
||||
Например, `count = count + 1;` означает: взять текущее значение `count`, прибавить единицу и сохранить результат обратно.
|
||||
Если до этого `count` было равно 10, после инструкции оно станет равно 11.
|
||||
|
||||
### Числа: целые и разных размеров
|
||||
|
||||
Для целых чисел в FZLang есть знаковые и беззнаковые типы.
|
||||
Знаковые числа могут быть отрицательными, а беззнаковые предназначены для неотрицательных значений.
|
||||
Например, температуру ниже нуля удобно представить знаковым типом, а количество посетителей — беззнаковым:
|
||||
|
||||
```fz
|
||||
int temperature = -5;
|
||||
uint visitors = 42;
|
||||
```
|
||||
|
||||
Для более точного выбора есть типы фиксированной ширины: `i8`, `i16`, `i32`, `i64` и их беззнаковые варианты `u8`, `u16`, `u32`, `u64`.
|
||||
Число после `i` или `u` указывает ширину типа в битах. Типы `int` и `uint` имеют размер машинного слова.
|
||||
|
||||
```fz
|
||||
i16 offset = -120;
|
||||
u8 level = 7;
|
||||
```
|
||||
|
||||
На первых шагах достаточно запомнить, что `int` подходит для обычных целых чисел, а точный тип можно выбрать, когда это действительно важно.
|
||||
Числовые типы совместимы между собой по правилам языка, но при объявлении переменной тип всё равно указывается явно.
|
||||
|
||||
### Логические значения и текст
|
||||
|
||||
У типа `bool` всего два значения: `true` (истина) и `false` (ложь).
|
||||
Это удобно, когда нужно хранить ответ на вопрос: например, открыта ли дверь или готова ли программа продолжать работу.
|
||||
|
||||
```fz
|
||||
bool is_open = true;
|
||||
printline(is_open);
|
||||
```
|
||||
|
||||
Для текста используется тип `str` (строка):
|
||||
|
||||
```fz
|
||||
str language = "FZLang";
|
||||
printline(language);
|
||||
```
|
||||
|
||||
В справочнике типов также упоминается `char`, но в текущей версии языка он не используется.
|
||||
Классы и объектные значения мы оставим для следующих разделов — пока достаточно освоиться с числами, текстом и логическими значениями.
|
||||
|
||||
## Выражения: попросим программу посчитать
|
||||
|
||||
Выражение — это часть кода, которую можно вычислить и получить значение.
|
||||
Самый простой пример — `2 + 2`. Выражения можно сохранять в переменные или передавать функциям:
|
||||
|
||||
```fz
|
||||
int result = 2 + 2 * 2;
|
||||
printline(result);
|
||||
```
|
||||
|
||||
Здесь программа сначала вычисляет выражение, сохраняет результат в `result`, а затем выводит его.
|
||||
Как и в обычной математике, умножение выполняется раньше сложения, поэтому результат будет 6.
|
||||
Если хотите изменить порядок вычислений, используйте скобки: ` (2 + 2) * 2`.
|
||||
|
||||
Для чисел доступны сложение `+`, вычитание `-`, умножение `*`, деление `/`, остаток от деления `%` и возведение в степень `^`.
|
||||
Унарный минус позволяет записывать отрицательные числа, например `-5`.
|
||||
|
||||
Сравнения помогают задавать вопросы о значениях. Операторы `==` и `!=` проверяют равенство и неравенство, а `<`, `<=`, `>` и `>=` сравнивают числа.
|
||||
Результат сравнения — логическое значение `true` или `false`:
|
||||
|
||||
```fz
|
||||
bool enough = result >= 4 && result != 0;
|
||||
printline(enough);
|
||||
```
|
||||
|
||||
В примере переменная `enough` станет истинной, только если `result` не меньше 4 и одновременно не равен нулю.
|
||||
Оператор `&&` означает «и», `||` — «или», а `!` меняет логическое значение на противоположное.
|
||||
Полный перечень операций и их ограничений приведён в справочнике [«Операторы»](../reference/operators.md).
|
||||
Учтите, что при сравнении объектов `==` и `!=` проверяют, ссылаются ли переменные на один и тот же объект, а не совпадает ли содержимое объектов.
|
||||
|
||||
## Блоки и условия: программа выбирает путь
|
||||
|
||||
Иногда программе нужно выполнить разные действия в зависимости от ситуации.
|
||||
Для этого есть условие `if`. Оно проверяет логическое выражение и выполняет подходящую ветвь:
|
||||
|
||||
```fz
|
||||
bool is_ready = true;
|
||||
|
||||
if (is_ready)
|
||||
{
|
||||
printline("Можно продолжать");
|
||||
}
|
||||
else
|
||||
{
|
||||
printline("Нужно подождать");
|
||||
}
|
||||
```
|
||||
|
||||
Если `is_ready` равно `true`, программа выведет первое сообщение; иначе — второе.
|
||||
Ветвь `else` можно опустить, если для случая «нет» ничего делать не нужно.
|
||||
Если вариантов больше двух, можно добавить `else if` с ещё одним условием.
|
||||
|
||||
Фигурные скобки `{}` объединяют инструкции в блок. Вы уже встречали их в примере с условием; такие блоки используются также в функциях.
|
||||
Они помогают видеть, какие инструкции относятся к одной части программы.
|
||||
Условие `if` должно иметь тип `bool` — например, результат сравнения или логическая переменная.
|
||||
|
||||
## Функции: соберём полезные действия
|
||||
|
||||
Когда одну и ту же последовательность действий хочется использовать в разных местах, её можно оформить как функцию.
|
||||
Функция получает значения через параметры, выполняет инструкции и при необходимости возвращает результат.
|
||||
|
||||
```fz
|
||||
fn sum(int left, int right) int
|
||||
{
|
||||
return left + right;
|
||||
}
|
||||
|
||||
int result = sum(2, 3);
|
||||
printline(result);
|
||||
```
|
||||
|
||||
Объявление начинается с `fn`, затем идут имя функции, параметры и тип результата.
|
||||
В этом примере `sum` принимает два значения типа `int`, складывает их и возвращает результат типа `int`.
|
||||
Ключевое слово `return` передаёт этот результат туда, откуда функцию вызвали.
|
||||
Поэтому вызов `sum(2, 3)` можно использовать как выражение — например, присвоить его результат переменной.
|
||||
|
||||
## Что запомнить
|
||||
|
||||
Вы уже познакомились с основными строительными блоками программы: инструкциями, комментариями, переменными, типами, выражениями, условиями и функциями.
|
||||
Не обязательно запоминать все операторы сразу — к справочнику можно возвращаться по мере необходимости.
|
||||
|
||||
- Переменная хранит значение и имеет имя и тип.
|
||||
- Для первых примеров пригодятся числа (`int`, `uint` и типы фиксированной ширины), логические значения (`bool`) и строки (`str`).
|
||||
- Выражения позволяют вычислять новые значения, а сравнения помогают принимать решения.
|
||||
- Условия `if` и блоки `{}` управляют тем, какие инструкции выполнит программа.
|
||||
- Функции объединяют действия и могут возвращать результат.
|
||||
- Инструкции обычно заканчиваются точкой с запятой, а `printline` помогает увидеть результат работы программы.
|
||||
|
||||
---
|
||||
|
||||
Далее: [Система типов](../reference/types_system.md)
|
||||
|
||||
Назад: [Начало работы](./01_getting-started.md)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Книга по FZLang
|
||||
|
||||
Знакомство с FZLang: от подготовки к работе до программ с переменными, типами и функциями.
|
||||
|
||||
- [Вступление](./00_introduction.md)
|
||||
- [Начало работы](./01_getting-started.md)
|
||||
- [Основы и типы](./02_basics-and-types.md)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Документация FZLang
|
||||
|
||||
Выберите раздел документации:
|
||||
|
||||
- [Книга](./book/index.md) — последовательное знакомство с языком.
|
||||
- [Справочник](./reference/index.md) — типы, операторы, грамматика и диагностика выполнения.
|
||||
- [Внутреннее устройство](./internals/index.md) — подробности о виртуальной машине.
|
||||
- [Стандартная библиотека](./stdlib/index.md) — встроенные функции.
|
||||
- [Философия языка](./philosopy.md)
|
||||
|
||||
[Read the documentation in English](../en/index.md)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Внутреннее устройство
|
||||
|
||||
Материалы об устройстве виртуальной машины FZLang и скомпилированных программ.
|
||||
|
||||
- [Размещение объектов](./object_layout.md)
|
||||
- [Опкоды](./opcodes.md)
|
||||
- [Стеки виртуальной машины](./stacks.md)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Размещение объектов
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
# Опкоды
|
||||
|
||||
Программы, написанные на FZLang, исполняются с помощью виртуальной машины, входящей в тулчейн FZLang и компилирующейся в паре с компилятором.
|
||||
Компилятор FZC может выпускать следующие опкоды, которые понимает и исполняет ВМ:
|
||||
|
||||
## Загрузка данных и работа со стеком
|
||||
|
||||
Коды: `0xF00001`..`0xF00004`
|
||||
|
||||
| Номер | Название | Назначение | Аргумент |
|
||||
| -------- | ----------------------- | ------------------------------- | --------------------- |
|
||||
| 0xF00001 | FZL_OPCODE_LOADCONST | Загрузить константу `N` на стек | `N` - номер константы |
|
||||
| 0xF00002 | FZL_OPCODE_CALL_BUILTIN | Вызвать встроенную функцию `N` | `N` - номер функции |
|
||||
| 0xF00003 | FZL_OPCODE_PUSHNUM | Поместить на стек число `N` | `N` - значение |
|
||||
| 0xF00004 | FZL_OPCODE_PUSHBOOL | Поместить на стек булеву `B` | `B` - значение |
|
||||
|
||||
## Управление контекстом и окружением
|
||||
|
||||
Коды: `0xF10001`..`0xF10005`
|
||||
|
||||
| Номер | Название | Назначение |
|
||||
| -------- | ---------------------- | ------------------------------------------------------------------- |
|
||||
| 0xF10001 | FZL_OPCODE_MK_ENVTABLE | Создать новое окружение |
|
||||
| 0xF10002 | FZL_OPCODE_RM_ENVTABLE | Уничтожить окружение |
|
||||
| 0xF10003 | FZL_OPCODE_STORE | Создать в окружении имя, лежащее на вершине стека |
|
||||
| 0xF10004 | FZL_OPCODE_LOOKUP_ENV | Достать из стека имя, найти в окружении и положить на стек значение |
|
||||
| 0xF10005 | FZL_OPCODE_WRITE | Достать со стека имя и значение, и записать в окружение |
|
||||
|
||||
## Управление потоком
|
||||
|
||||
Коды: `0xF20001`..`0xF20002`
|
||||
|
||||
| Номер | Название | Назначение | Аргумент |
|
||||
| -------- | -------------- | ----------------------------------------------------------------- | ------------------------ |
|
||||
| 0xF20001 | FZL_OPCODE_JMP | Прыгнуть на адрес `IP` | `IP` - адрес-цель прыжка |
|
||||
| 0xF20002 | FZL_OPCODE_JZ | Прыгнуть на адрес `IP`, если значение на вершине стека равно нулю | `IP` - адрес-цель прыжка |
|
||||
|
||||
## Вызов функций
|
||||
|
||||
Коды: `0xF30001`..`0xF30002`
|
||||
|
||||
| Номер | Название | Назначение |
|
||||
| -------- | --------------- | ----------------------------------------------------------------- |
|
||||
| 0xF30001 | FZL_OPCODE_CALL | Положить на стек вызовов текущий адрес и перейти к началу функции |
|
||||
| 0xF30002 | FZL_OPCODE_RET | Снять со стека вызовов последний адрес и перейти к нему |
|
||||
|
||||
## Работа с объектами
|
||||
|
||||
Коды: `0xFA0001`..`0xFA0005`
|
||||
|
||||
| Номер | Название | Назначение | Аргумент |
|
||||
| -------- | ------------------------ | -------------------------------------------------------------------------------- | --------------------- |
|
||||
| 0xFA0001 | FZL_OPCODE_ALLOC_OBJECT | Аллоцировать объект под `N` значений и поместить его на стек | `N` - кол-во значений |
|
||||
| 0xFA0002 | FZL_OPCODE_GET_FIELD | Снять со стека ссылку на объект и получить значение поля `N` | `N` - номер поля |
|
||||
| 0xFA0003 | FZL_OPCODE_SET_FIELD | Снять со стека ссылку на объект и значение, а затем записать значение в поле `N` | `N` - номер поля |
|
||||
| 0xFA0004 | FZL_OPCODE_ESCAPE_OBJECT | Перенести объект (в т.ч. дочерние ему) в родительское окружение | отсутствует |
|
||||
|
||||
## Арифметические операции
|
||||
|
||||
Коды: `0xA00001`..`0xA00007`
|
||||
|
||||
| Номер | Название | Назначение |
|
||||
| -------- | -------------- | --------------------------------------------------------------------------------------- |
|
||||
| 0xA00001 | FZL_OPCODE_ADD | Снять два числа со стека и сложить их, положив результат на стек |
|
||||
| 0xA00002 | FZL_OPCODE_SUB | Снять два числа со стека и вычесть первое от второго, положив результат на стек |
|
||||
| 0xA00003 | FZL_OPCODE_MUL | Снять два числа со стека и переменожить их, положив результат на стек |
|
||||
| 0xA00004 | FZL_OPCODE_DIV | Снять два числа со стека и поделить первое на второе, положив результат на стек |
|
||||
| 0xA00005 | FZL_OPCODE_POW | Снять два числа со стека и возвести второе в степерь первого, положив результат на стек |
|
||||
| 0xA00006 | FZL_OPCODE_MOD | Снять два числа со стека и положить на стек остаток от деления второго на первое |
|
||||
| 0xA00007 | FZL_OPCODE_NEG | Снять число со стека, инвертировать, и положить обратно на стек |
|
||||
|
||||
## Сравнение чисел
|
||||
|
||||
Коды: `0xB00001`..`0xB00006`
|
||||
|
||||
| Номер | Название | Назначение |
|
||||
| -------- | ------------------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| 0xB00001 | FZL_OPCODE_CMP_EQ | Снять два числа со стека, проверить на равенство и положить на стек результат |
|
||||
| 0xB00002 | FZL_OPCODE_CMP_NEQ | Снять два числа со стека, проверить на неравенство и сохранить результат на стеке |
|
||||
| 0xB00003 | FZL_OPCODE_CMP_LT | Снять два числа со стека, проверить, что второе меньше первого, и затем положить результат на стек |
|
||||
| 0xB00004 | FZL_OPCODE_CMP_LTEQ | Снять два числа со стека, проверить, что второе меньше или равно первому, и затем положить результат на стек |
|
||||
| 0xB00005 | FZL_OPCODE_CMP_GT | Снять два числа со стека, проверить, что второе больше первого, и затем положить результат на стек |
|
||||
| 0xB00006 | FZL_OPCODE_CMP_GTEQ | Снять два числа со стека, проверить, что второе больше или равно первому, и затем положить результат на стек |
|
||||
|
||||
## Сравнение строк
|
||||
|
||||
Коды: `0xB10001`..`0xB10002`
|
||||
|
||||
| Номер | Название | Назначение |
|
||||
| -------- | ---------------------- | ------------------------------------------------------------------------------ |
|
||||
| 0xB10001 | FZL_OPCODE_CMP_EQ_STR | Снять две строки со стека, сравнить на равенство, положить результат на стек |
|
||||
| 0xB10002 | FZL_OPCODE_CMP_NEQ_STR | Снять две строки со стека, сравнить на неравенство, положить результат на стек |
|
||||
|
||||
## Сравнение объектов
|
||||
|
||||
Коды: `0xB20001`..`0xB20002`
|
||||
|
||||
| Номер | Название | Назначение |
|
||||
| -------- | ---------------------- | ------------------------------------------------------------------------------- |
|
||||
| 0xB20001 | FZL_OPCODE_CMP_EQ_OBJ | Снять два объекта со стека, сравнить на равенство, положить результат на стек |
|
||||
| 0xB20002 | FZL_OPCODE_CMP_NEQ_OBJ | Снять два объекта со стека, сравнить на неравенство, положить результат на стек |
|
||||
|
||||
## Бинарные логические операции
|
||||
|
||||
Коды: `0xC00001`..`0xC00002`
|
||||
|
||||
| Номер | Название | Назначение |
|
||||
| -------- | -------------- | ------------------------------------------------------------------------ |
|
||||
| 0xC00001 | FZL_OPCODE_AND | Взять два значения со стека и положить на стек результат логического И |
|
||||
| 0xC00002 | FZL_OPCODE_OR | Взять два значения со стека и положить на стек результат логического ИЛИ |
|
||||
|
||||
## Унарные логические операции
|
||||
|
||||
Коды: `0xD00001`
|
||||
|
||||
| Номер | Название | Назначение |
|
||||
| -------- | -------------- | ------------------------------------------------------------------ |
|
||||
| 0xD00001 | FZL_OPCODE_NOT | Взять значение со стека и положить инвертированный булев результат |
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# Стеки виртуальной машины
|
||||
|
||||
Во время работы, виртуальная машина FZLang полагается на три стека: данных, окружений и вызовов.
|
||||
Каждый стек инициализируется как массив из 256 значений, а затем зануляется.
|
||||
Основное свойство, присущее всем трём стекам - последнее значение является верхом стека и сниматься в первую очередь должно оно, декрементируя указатель на вершину стека.
|
||||
|
||||
## Стек данных
|
||||
|
||||
Стек данных представляет из себя массив из 256 элементов типа `fzl_value_t`.
|
||||
Структура `fzl_value_t` - это контейнер для хранения значений, используемых во время работы программы.
|
||||
|
||||
```c
|
||||
typedef struct
|
||||
{
|
||||
fzl_type_t type;
|
||||
|
||||
union
|
||||
{
|
||||
void *obj;
|
||||
|
||||
struct
|
||||
{
|
||||
const char *value;
|
||||
size_t length;
|
||||
} s;
|
||||
|
||||
char c;
|
||||
bool b;
|
||||
|
||||
int8_t i8;
|
||||
uint8_t u8;
|
||||
|
||||
int16_t i16;
|
||||
uint16_t u16;
|
||||
|
||||
int32_t i32;
|
||||
uint32_t u32;
|
||||
|
||||
int64_t i64;
|
||||
uint64_t u64;
|
||||
|
||||
intptr_t i;
|
||||
uintptr_t u;
|
||||
} as;
|
||||
} fzl_value_t;
|
||||
```
|
||||
|
||||
Стек используется во время работы программы виртуальной машиной, чтобы писать и читать промежуточные вычисления.
|
||||
К концу работы программы, стек должен быть пуст.
|
||||
|
||||
## Стек окружений
|
||||
|
||||
Чтобы определять лексическое окружение, в котором на данный момент производятся вычисления и выполняются команды, используется стек окружений.
|
||||
Стек окружений - это массив из 256 элементов типа `fzl_vm_env_t`.
|
||||
|
||||
```c
|
||||
typedef struct
|
||||
{
|
||||
size_t size;
|
||||
size_t remainder;
|
||||
|
||||
fzl_vm_env_entry_t *table;
|
||||
|
||||
fzl_vm_object_t **owned_objects;
|
||||
size_t owned_objects_remainder;
|
||||
size_t owned_objects_count;
|
||||
} fzl_vm_env_t;
|
||||
```
|
||||
|
||||
Каждый элемент стека хранит таблицу локальных имён `fzl_vm_env_entry_t`, а также объекты, которые принадлежат текущему окружению.
|
||||
Локальное имя - это контейнер, оборачивающий `fzl_value_t` и дающий ему имя:
|
||||
|
||||
```c
|
||||
typedef struct
|
||||
{
|
||||
char *name;
|
||||
fzl_value_t value;
|
||||
} fzl_vm_env_entry_t;
|
||||
```
|
||||
|
||||
Объект же представляет из себя массив полей, хранимых в этом объекте.
|
||||
Количество элементов определется при аллокации объекта (опкод `FZL_OPCODE_ALLOC_OBJECT N`, подробнее - в [описании опкодов](./opcodes.md)).
|
||||
|
||||
```c
|
||||
typedef struct
|
||||
{
|
||||
fzl_value_t *values;
|
||||
size_t values_count;
|
||||
} fzl_vm_object_t;
|
||||
```
|
||||
|
||||
Создание окружения или выход из него происходит при опкодах `FZL_OPCODE_MK_ENVTABLE` и `FZL_OPCODE_RM_ENVTABLE` соответственно.
|
||||
При этом, если компилятор видит возвращаемое из функции значение типа `FZL_TYPE_OBJECT`, то перед опкодами `FZL_OPCODE_RET` и `FZL_OPCODE_RM_ENVTABLE` он вставляет `FZL_OPCODE_ESCAPE_OBJECT`,
|
||||
которое позволяет переместить владение объектом в родительское окружение.
|
||||
|
||||
## Стек возврата
|
||||
|
||||
Стек возврата хранит адреса инструкций при входе в функцию (через опкод `FZL_OPCODE_CALL`).
|
||||
Опкод `FZL_OPCODE_RET` снимает адрес со стека вызовов и прыгает на снятый адрес.
|
||||
|
||||
Сам по себе стек представляет из себя 256 элементов типа `size_t`.
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Философия языка
|
||||
|
||||
Язык FZLang преследует две цели при разработке: "быть удобным, как инструмент, но всегда оставаться предсказуемым".
|
||||
|
||||
Вот, какими путями язык пытается достичь этой философии:
|
||||
|
||||
## В отношении к памяти и ресурсам
|
||||
|
||||
В средах с GC программист точно не знает, в какой момент сборщик мусора решит произвести очистку:
|
||||
объект может висеть секундами после завершения метода.
|
||||
А в момент очистки рантайм может устроить микропаузу, что критично для аудио, игр, сети или встроенных систем.
|
||||
|
||||
В модели scope-based RAII, которая используется в том числе в FZLang, память детерминирована.
|
||||
Любой объект имеет область, которая им владеет, а как только область уничтожается - объект уничтожается вместе с ней.
|
||||
Время отклика таком подходе равное и предсказуемое.
|
||||
|
||||
Ещё один плюс, который предоставляет модель scope-based RAII - это работа с ресурсами (сокеты, файлы).
|
||||
Проблема освобождения ресурсов решается чистой семантикой области видимости: умирает скоуп - очищается всё, что к нему привязано.
|
||||
|
||||
И последним аргументом в сторону scope-based RAII стало отстутствие оверхеда на рантайм.
|
||||
Полноценному GC для работы нужны карты памяти, фоновые потоки, барьеры чтения и записи.
|
||||
В FZLang виртуальная машина весит очень мало, а вся память освобождается по ходу чтения инструкций.
|
||||
|
||||
## В отношении синтаксиса
|
||||
|
||||
Язык FZLang намеренно не упрощается некоторые детали синтаксиса.
|
||||
Например, при обращении к экземпляру класса в его методах, использование `this` является обязательным, как и указание типов при декларациях, - это строгая часть.
|
||||
Однако, объявление функции без возвращаемого типа позволяет опускать тип, так как писать на месте типа постоянно `void` было бы избыточно.
|
||||
|
||||
Таким образом, FZLang старается балансировать между синтаксисом, позволяющим оставаться языку детерминированным и упрощениями, которые действительно уместны.
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# Вывод ошибок
|
||||
|
||||
Если во время работы программы происходит ошибка рантайма, то ВМ выводит своё состояние на момент остановки.
|
||||
Среди выводимой информации есть:
|
||||
|
||||
1. Указатель на адрес инструкции;
|
||||
2. Стек данных;
|
||||
3. Стек окружений;
|
||||
4. Стек вызовов.
|
||||
|
||||
## Указатель на адрес инструкции (IP)
|
||||
|
||||
Он может быть полезен для отладки, в паре с выводом опкодов программы.
|
||||
Если подать в ВМ флаг `--debug` (`-d`) вместе с путём к скомпилированной программой, то ВМ выведет список опкодов программы без её запуска.
|
||||
|
||||
Выглядит вывод программы примерно так:
|
||||
|
||||
```
|
||||
0000: MK_ENVTABLE
|
||||
0001: JMP IP: 53
|
||||
0003: MK_ENVTABLE
|
||||
0004: LOADCONST #0 ('a')
|
||||
0006: STORE
|
||||
0007: LOADCONST #0 ('a')
|
||||
0009: WRITE
|
||||
0010: MK_ENVTABLE
|
||||
0011: LOADCONST #0 ('a')
|
||||
0013: LOOKUP_ENV
|
||||
0014: GET_FIELD #0
|
||||
...
|
||||
```
|
||||
|
||||
Порядковый номер позволяет быстро найти инструкцию, на которой произошло исключение.
|
||||
Полный список опкодов и их описание можно найти в документе [списка опкодов](../internals/opcodes.md).
|
||||
|
||||
## Стек данных
|
||||
|
||||
Стек данных выводится как список значений в стиле `[T] V`, где `T` - номер типа, `V` - значение.
|
||||
Если значение является строкой или объектом, то в начале дописывается `str: V` и `ref: V` соответственно.
|
||||
|
||||
Вот, как выглядит пример вывода стека:
|
||||
|
||||
```
|
||||
> Data stack:
|
||||
> [2] str: 'this'
|
||||
```
|
||||
|
||||
Важно учесть, что значения выводятся сверху вниз. То есть, первыми будут выведены самые свежие значения.
|
||||
|
||||
## Стек окружения
|
||||
|
||||
Стек окружения выводится как два списка: объекты во владении и локальные для окружения имена.
|
||||
|
||||
Пример окружения:
|
||||
|
||||
```
|
||||
> Environments:
|
||||
> [1] 0x55a9d8196a20:
|
||||
> Owned objects (0/2):
|
||||
> Local entries (3/4):
|
||||
> [14] b = 4
|
||||
> [1] a = ref 0x55a9d81969e0
|
||||
> [0] this = 0
|
||||
> [0] 0x55a9d8196930:
|
||||
> Owned objects (1/2):
|
||||
> ref: 0x55a9d81969e0 (entries: 1)
|
||||
> Local entries (1/2):
|
||||
> [1] sum = ref 0x55a9d81969e0
|
||||
```
|
||||
|
||||
`> [N] A` - это сама таблица окружения.
|
||||
`N` - это уровень таблицы, который тем выше, чем глубже само окружение.
|
||||
`A` - это адрес таблицы в памяти.
|
||||
|
||||
Внутри идёт первый список - "Owned objects", с указанием количества элементов и аллоцированным размером массива `(<кол-во>/<размер>)`.
|
||||
Перечисление объектов идёт форматом `ref: A (entries: N)`, где `A` - адрес объекта в памяти, `N` - количество хранимых полей.
|
||||
|
||||
Далее идёт список локальных имён - "Local entries", с таким же указанием количества элементов к аллоцированному размеру.
|
||||
Формат элемента - `[T] N = V`, где `T` - номер типа, `N` - имя элемента, а `V` - его значение.
|
||||
|
||||
## Стек вызовов
|
||||
|
||||
На стеке вызовов лежат точки возврата, которые попадают на стек при инструкции `CALL` и съедаются инструкцией `RET`.
|
||||
|
||||
```
|
||||
> Call stack:
|
||||
> [0] 53
|
||||
```
|
||||
|
||||
Формат записи простой: `[L] IP`, где `L` - уровень вложенности (больше = глубже), а `IP` - указатель на номер инструкции, на которую нужно прыгнуть после возврата.
|
||||
|
||||
## Полный вывод
|
||||
|
||||
Итого, полный вывод ошибки выглядит следующим образом:
|
||||
|
||||
```
|
||||
===== ERROR =====
|
||||
> Caused on IP: 21
|
||||
|
||||
> Data stack:
|
||||
> [2] str: 'this'
|
||||
|
||||
> Environments:
|
||||
> [1] 0x55a9d8196a20:
|
||||
> Owned objects (0/2):
|
||||
> Local entries (3/4):
|
||||
> [14] b = 4
|
||||
> [1] a = ref 0x55a9d81969e0
|
||||
> [0] this = 0
|
||||
> [0] 0x55a9d8196930:
|
||||
> Owned objects (1/2):
|
||||
> ref: 0x55a9d81969e0 (entries: 1)
|
||||
> Local entries (1/2):
|
||||
> [1] sum = ref 0x55a9d81969e0
|
||||
|
||||
> Call stack:
|
||||
> [0] 53
|
||||
Error: runtime error - expected 2 values in data stack
|
||||
```
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/* FZL (FromZero Language) syntax */
|
||||
/* Version 1 */
|
||||
/* Authors: */
|
||||
/* - Samsonium (https://git.samsonium.dev/Samsonium) */
|
||||
|
||||
<program> ::= <_> <statement>+
|
||||
|
||||
<statement> ::= <class_stmt>
|
||||
| <fn_stmt>
|
||||
| <if_stmt>
|
||||
| <block_stmt>
|
||||
| <ret_stmt>
|
||||
| <expr_stmt>
|
||||
|
||||
<_> ::= " "*
|
||||
<ws> ::= " "+
|
||||
|
||||
<expr_stmt> ::= <expr> <_> ";" <_>
|
||||
<block_stmt> ::= "{" <_> <statement>* "}" <_>
|
||||
<ret_stmt> ::= "return" (<ws> <expr>)? <_> ";" <_>
|
||||
|
||||
<class_stmt> ::= "class" <ws> <name> <_> "{" <_> (<declaration> | <fn_stmt>)* "}" <_>
|
||||
<fn_stmt> ::= "fn" <ws> <name> <_> "(" <_> (<param> (<_> "," <_> <param>)*)? ")" <_> <block_stmt>
|
||||
<param> ::= <type> <ws> <name>
|
||||
|
||||
<if_stmt> ::= "if" <_> "(" <_> <expr> <_> ")" <_> <statement> (<_> "else" <ws> <statement>)?
|
||||
|
||||
/* Assign (lowest epxression priority, right-associative) */
|
||||
<expr> ::= <assign_expr>
|
||||
<assign_expr> ::= <new_expr> (<_> "=" <_> <assign_expr>)?
|
||||
<new_expr> ::= ("new" <ws> <name>) | <or_expr>
|
||||
<or_expr> ::= <and_expr> (<_> "||" <_> <and_expr>)*
|
||||
<and_expr> ::= <cond_expr> (<_> "&&" <_> <cond_expr>)*
|
||||
<cond_expr> ::= <term_expr> (<_> <cond_op> <_> <term_expr>)*
|
||||
<cond_op> ::= "==" | "!=" | "<=" | ">=" | "<" | ">"
|
||||
|
||||
<term_expr> ::= <factor_expr> (<_> ("+" | "-") <_> <factor_expr>)*
|
||||
<factor_expr> ::= <power_expr> (<_> ("*" | "/" | "%") <_> <power_expr>)*
|
||||
<power_expr> ::= <unary_expr> (<_> "^" <_> <power_expr>)?
|
||||
<unary_expr> ::= (("!" | "-" | "+") <_>)* <postfix_expr>
|
||||
|
||||
/* Postfix: chain of calls and dots */
|
||||
<postfix_expr> ::= <primary_expr> (<call_suffix> | <member_suffix>)*
|
||||
<call_suffix> ::= <_> "(" <_> (<expr> (<_> "," <_> <expr>)*)? <_> ")"
|
||||
<member_suffix> ::= <_> "." <_> <name>
|
||||
|
||||
<primary_expr> ::= <literal>
|
||||
| <declaration>
|
||||
| "(" <_> <expr> <_> ")"
|
||||
| <name>
|
||||
|
||||
<declaration> ::= <type> <ws> <name>
|
||||
|
||||
<type> ::= "int" | "uint" | "bool" | "char" | "str"
|
||||
| "i8" | "i16" | "i32" | "i64"
|
||||
| "u8" | "u16" | "u32" | "u64"
|
||||
|
||||
<literal> ::= <number> | <string_literal> | "true" | "false" | "null"
|
||||
<name> ::= ([A-z] | "_") | ([A-z] | [0-9] | "_")*
|
||||
<string_literal> ::= ('"' [^"]* '"') | ("'" [^']* "'")
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Грамматика (EBNF)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Справочник
|
||||
|
||||
Справочные материалы по языку и среде выполнения FZLang.
|
||||
|
||||
- [Операторы](./operators.md)
|
||||
- [Система типов](./types_system.md)
|
||||
- [Грамматика (EBNF)](./grammar_ebnf.md)
|
||||
- [Вывод ошибок](./errors_print.md)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Операторы
|
||||
|
||||
FZLang поддерживает арифметические, логические и унарные операции.
|
||||
Во время работы бинарные операции достают со стека два значения, и обратно кладут результат на стек.
|
||||
Унарные операции достают одно значение со стека, а затем кладут результат обратно на стек.
|
||||
|
||||
## Арифметические операции
|
||||
|
||||
| Операция | Допустимые типы | Описание |
|
||||
| -------- | --------------- | -------- |
|
||||
| `N + N` | *все числовые* | Сложение двух операндов |
|
||||
| `N - N` | *все числовые* | Вычитание правого операнда из левого |
|
||||
| `N * N` | *все числовые* | Переменожение двух операндов |
|
||||
| `N / N` | *все числовые* | Деление левого операнда на правый |
|
||||
| `N % N` | *все числовые* | Получение остатка от деления левого операнда на правый |
|
||||
| `N ^ N` | *все числовые* | Возведение левого операнда в степень правого |
|
||||
| `-N` | *все числовые* | Инверсия операнда |
|
||||
|
||||
## Операции сравнения
|
||||
|
||||
| Операция | Допустимые типы | Описание |
|
||||
| -------- | --------------- | -------- |
|
||||
| `N == N` | *все числовые*, булевы, объекты, строки | Сравнить равенство операндов |
|
||||
| `N != N` | *все числовые*, булевы, объекты, строки | Сравнить неравенство операндов |
|
||||
| `N > N` | *все числовые* | Проверить, что левый операнд больше правого |
|
||||
| `N >= N` | *все числовые* | Проверить, что левый операнд больше или равен правому |
|
||||
| `N < N` | *все числовые* | Проверить, что левый операнд меньше правого |
|
||||
| `N <= N` | *все числовые* | Проверить, что левый операнд меньше или равен правому |
|
||||
| `!N` | булевы | Инвертировать операнд |
|
||||
|
||||
> Важно:
|
||||
> В случае сравнения объектов (`==`, `!=`), сравниваются адреса, а не значения!
|
||||
|
||||
## Логические операции
|
||||
|
||||
| Операция | Допустимые типы | Описание |
|
||||
| -------- | --------------- | -------- |
|
||||
| `N && N` | булевы | Логическое **И** двух операндов |
|
||||
| `N || N` | булевы | Логическое **ИЛИ** двух операндов |
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Система типов
|
||||
|
||||
FZLang имеет две основы для типов: примитивы и объекты.
|
||||
|
||||
Примитивами являются строки, числовые типы, булевы и скрытый тип - "объект".
|
||||
Объектами на данный момент являются экземпляры классов, а также литерал `null`.
|
||||
|
||||
## Таблица типов
|
||||
|
||||
| Внутреннее имя | Синтаксическое имя | Описание |
|
||||
| --------------- | ------------------ | -------- |
|
||||
| FZL_TYPE_NULL | *отстутсвует* | *на данный момент не используется* |
|
||||
| FZL_TYPE_OBJECT | *отстутсвует* | экземпляр класса или литерал `null` |
|
||||
| FZL_TYPE_STRING | `str` | строка |
|
||||
| FZL_TYPE_CHAR | `char` | *на данный момент не используется* |
|
||||
| FZL_TYPE_BOOL | `bool` | булева |
|
||||
| FZL_TYPE_INT8 | `i8` | 8-битное знаковое число |
|
||||
| FZL_TYPE_UINT8 | `u8` | 8-битное беззнаковое число |
|
||||
| FZL_TYPE_INT16 | `i16` | 16-битное знаковое число |
|
||||
| FZL_TYPE_UINT16 | `u16` | 16-битное беззнаковое число |
|
||||
| FZL_TYPE_INT32 | `i32` | 32-битное знаковое число |
|
||||
| FZL_TYPE_UINT32 | `u32` | 32-битное беззнаковое число |
|
||||
| FZL_TYPE_INT64 | `i64` | 64-битное знаковое число |
|
||||
| FZL_TYPE_UINT64 | `u64` | 64-битное беззнаковое число |
|
||||
| FZL_TYPE_INT | `int` | знаковое число размером с машинное слово |
|
||||
| FZL_TYPE_UINT | `uint` | беззнаковое число размером с машинное слово |
|
||||
|
||||
|
||||
## Совместимость
|
||||
|
||||
При проверке совместимости типов работают следующие правила:
|
||||
|
||||
1. Если оба типа - объекты (`FZL_TYPE_OBJECT`), то:
|
||||
1. Если один из типов имеет имя класса `<nullptr>`, проверка **пройдена**;
|
||||
2. Если оба типа имеют одинаковое имя класса, проверка **пройдена**;
|
||||
3. В ином случае проверка **не пройдена**.
|
||||
2. Если типы равны, проверка **пройдена**;
|
||||
3. Если оба типа - числовые (`u8`..`uint`, `i8`..`int`), проверка **пройдена**;
|
||||
4. Если ни одно правило из вышеперечисленных не сработало, то проверка **не пройдена**.
|
||||
|
||||
**пройдена** = типы совместимы, **не пройдена** - не совместимы.
|
||||
|
||||
## Работа в рантайме
|
||||
|
||||
Для примитивов (т.е., не `FZL_TYPE_OBJECT`), значения используются прямо.
|
||||
Это значит, что они все операции, работающие со значениями используют для работы то значение, что лежит в контейнере на стеке.
|
||||
|
||||
Объектные типы хранятся на стеке как **ссылки на объект**.
|
||||
Для них валидные такие операции, как:
|
||||
|
||||
- `FZL_OPCODE_ESCAPE_OBJECT` (эвакуация объекта в родительское окружение)
|
||||
- `FZL_OPCODE_GET_FIELD N` (чтение поля с индексом `N` из объекта)
|
||||
- `FZL_OPCODE_SET_FIELD N` (запись значения в поле объекта с индексом `N`)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Встроенные функции
|
||||
|
||||
В языке FZLang предусмотрены встроеные функции, доступные для использования в любой программе.
|
||||
|
||||
## Выход `exit`
|
||||
|
||||
```
|
||||
fn exit(uint code)
|
||||
```
|
||||
|
||||
Функция принимает аргументом беззнаковое число и выходит из программы с указанным кодом.
|
||||
|
||||
## Вывод в stdout `printline`
|
||||
|
||||
```
|
||||
fn printline(value)
|
||||
```
|
||||
|
||||
Принимает аргументом любое значение и выводит в стандартный вывод указанное значение.
|
||||
Для примитивы выводятся чисто, на основе типа.
|
||||
Объекты выводятся как `obj<...>`, где `...` - перечисление значений полей.
|
||||
Поля выводятся по типу и значению в формате: `T<V>`, где `T` - название типа, `V` - текущее значение.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Стандартная библиотека
|
||||
|
||||
Описание функций, доступных в программах FZLang без дополнительных подключений.
|
||||
|
||||
- [Встроенные функции](./builtins.md)
|
||||
|
||||
@@ -27,6 +27,7 @@ fzl_options_t *fzl_mkoptions(int argc, char **argv)
|
||||
}
|
||||
|
||||
memset(opts, 0, sizeof(fzl_options_t));
|
||||
opts->should_continue = true;
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
@@ -81,24 +82,27 @@ fzl_options_t *fzl_mkoptions(int argc, char **argv)
|
||||
size_t fcount = 0;
|
||||
char **files = malloc(fremainder * sizeof(char *));
|
||||
|
||||
for (; i < argc; i++)
|
||||
if (opts->should_continue)
|
||||
{
|
||||
if (argv[i][0] == '-')
|
||||
for (; i < argc; i++)
|
||||
{
|
||||
printf(
|
||||
"%sWarning%s: unhandled flag '%s' (should be defined before sources "
|
||||
"list)\n",
|
||||
FZL_CWARNING, FZL_CRST, argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (argv[i][0] == '-')
|
||||
{
|
||||
printf("%sWarning%s: unhandled flag '%s' (should be defined before "
|
||||
"sources "
|
||||
"list)\n",
|
||||
FZL_CWARNING, FZL_CRST, argv[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fremainder <= fcount)
|
||||
{
|
||||
fremainder *= 2;
|
||||
files = realloc(files, fremainder * sizeof(char *));
|
||||
}
|
||||
if (fremainder <= fcount)
|
||||
{
|
||||
fremainder *= 2;
|
||||
files = realloc(files, fremainder * sizeof(char *));
|
||||
}
|
||||
|
||||
files[fcount++] = argv[i];
|
||||
files[fcount++] = argv[i];
|
||||
}
|
||||
}
|
||||
|
||||
opts->files = files;
|
||||
@@ -115,7 +119,6 @@ fzl_options_t *fzl_mkoptions(int argc, char **argv)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
opts->should_continue = true;
|
||||
return opts;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ void print_hint()
|
||||
printf("Usage: fz [flags] <file path>\n");
|
||||
printf("Flags:\n");
|
||||
printf("\t-h\t--help\tprint usage information\n");
|
||||
printf("\t-d\t--debug\tdisplay program opcodes\n");
|
||||
}
|
||||
|
||||
// Parse passed arguments and build args setup structure
|
||||
|
||||
Reference in New Issue
Block a user