Перейти к содержанию

Исполняемое ядро 0.0.4

Это спецификация текущего прототипа, не полного языка Liza.

Типы и литералы

Доступны u8, u16, u32, u64, i8, i16, i32, i64 и bool. Для N бит unsigned имеет диапазон 0…2^N−1, signed — −2^(N−1)…2^(N−1)−1. Знаковые битовые операции определены через дополнение до двух. bool содержит только true и false; неявных преобразований между типами нет.

Литерал хранится компилятором как математическое целое и получает тип из аннотации привязки, результата функции, параметра вызова или типизированного операнда. Контекст распространяется через скобки и арифметические операции в обе стороны. Это вывод типов, а не изменение порядка исполнения. Если контекста нет, выбирается u32, независимо от величины литерала. Литерал вне диапазона выбранного типа — ошибка компиляции.

fn offset(x: i16) -> i16 {
    y = (1 + 2) + x       // все операнды имеют тип i16
    return y
}

fn main() -> u32 {
    minimum: i8 = -128
    positive: u8 = 42
    if minimum < 0 && positive == 42 { return 0 }
    return 1
}

Минус непосредственно перед числовым литералом учитывается до проверки диапазона: -128 допустим для i8. Унарный минус перед выражением требует знакового типа и проверяет переполнение: -MIN вызывает trap. -(128) в контексте i8 отклоняется, поскольку внутренний литерал 128 не представим; для минимального значения используется запись -128. Отрицательный литерал без знакового контекста отклоняется как непредставимый в u32. Десятичные и 0x-литералы подчиняются одинаковым правилам диапазона.

Синтаксис

program   := function*
function  := "fn" name "(" parameters? ")" "->" type (":" "pure")? block
type      := "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "bool"
parameters := name ":" type ("," name ":" type)*
block     := "{" statements "}"
statement := name (":" type)? "=" expression | "return" expression | conditional
conditional := "if" expression block ("else" (block | conditional))?
expression := literal | "true" | "false" | name | name "(" arguments? ")"
            | "(" expression ")" | unary expression | expression binary expression
unary     := "!" | "~" | "-"
binary    := "+" | "-" | "*" | "/" | "%" | "+%" | "-%" | "*%"
            | "==" | "!=" | "<" | "<=" | ">" | ">="
            | "&" | "|" | "^" | "<<" | ">>" | "&&" | "||"

Привязки и return разделяются newline или ;, перед } разделитель необязателен. После блока if/else разделитель также необязателен. else допускается на той же или следующей строке; ; перед else недопустим. Заголовки функций и выражения пока однострочные. Открывающая { следует на той же строке, что заголовок функции, условие или else. Комментарий // продолжается до конца строки.

Идентификаторы ASCII: [A-Za-z_][A-Za-z_0-9]*. Имена типов, fn, return, pure, true, false, if, else зарезервированы.

Имена, функции и ветвления

Привязки неизменяемые: повторное объявление и затенение видимого имени запрещены. Имя доступно после объявления. Ветка видит внешние имена, но её локальные имена не доступны соседней ветке и коду после if. Новое имя можно независимо использовать в соседних ветках или после завершения его области.

Функции видимы во всём модуле, включая forward calls. Рекурсия разрешена, завершение не доказывается. Имена функций и локальных значений находятся в разных пространствах: f(...) ищет функцию, простое f — значение. Типы параметров и результата обязательны.

if — инструкция. Условие вычисляется один раз и имеет тип bool; выполняется только выбранная ветка. else необязательно; доступны вложенные ветвления и else if. Каждый путь функции должен закончиться return объявленного типа. Возврат из обеих веток достаточен; код после него отклоняется. Проверка структурная: if true {return 1} без else требует последующего возврата. Обе ветки проверяются статически, даже если условие известно заранее.

Ровно одна main() -> u32 без параметров обязательна. : pure необязательно: все доступные конструкции входят в чистое ядро. Это ещё не полная система эффектов.

Порядок вычислений и приоритеты

Операнды и аргументы вычисляются слева направо, инструкции — в порядке исходника. && вычисляет правую часть только если левая истинна; || — только если ложна. Обе части имеют тип bool и всегда проверяются статически.

Приоритеты от высокого к низкому:

Операции Операнды Результат
!, ~, унарный - bool / целое / знаковое целое соответственно Тип операнда
*, *%, /, % Одинаковый целый тип Тот же тип
+, -, +%, -% Одинаковый целый тип Тот же тип
<<, >> Целое слева, u32 справа Тип слева
<, <=, >, >= Одинаковый целый тип bool
==, != Одинаковый скалярный тип bool
& Одинаковый целый тип Тот же тип
^ Одинаковый целый тип Тот же тип
\| Одинаковый целый тип Тот же тип
&& bool, bool bool
\|\| bool, bool bool

Бинарные операции одного приоритета ассоциируются слева. Для проверки маски нужны скобки: (flags & mask) != 0. Числовые операнды сравнения не получают тип bool из результата: их тип выводится независимо, либо выбирается u32. Сравнения учитывают знаковость: для i8 −1 < 1, для u8 255 > 1.

Арифметика и биты

+, -, * и унарное отрицание signed проверяют представимость результата; при переполнении выполняется trap. +%, -%, *% считают по модулю 2^N; для signed результат интерпретируется в дополнительном коде. Промежуточное переполнение не исчезает при свёртке выражения: (x+1)-1 вызывает trap для MAX даже при неиспользуемом результате.

/ и % требуют одинакового целого типа операндов и возвращают тот же тип. Частное округляется к нулю; ненулевой остаток имеет знак делимого. Например, для i32: -7 / 3 == -2, -7 % 3 == -1, 7 % -3 == 1. Математически a = q*b + r, где q — частное, r — остаток. Обе операции вызывают trap при нулевом делителе. Для signed MIN / -1 вызывает trap, а MIN % -1 возвращает ноль. Остаток вычисляется самостоятельно, без промежуточного checked-частного. Эти правила действуют и для констант: 1 / 0 типизируется, но вызывает trap только при исполнении.

&, |, ^, ~ работают с N битами без арифметического переполнения. << отбрасывает выходящие биты, затем интерпретирует результат в исходном типе. >> заполняет старшие биты нулями для unsigned и знаковым битом для signed. Счётчик — u32; n >= N вызывает trap до машинного сдвига. Его значение не маскируется и не усекается перед проверкой. Signed-счётчик и отрицательный литерал счётчика отклоняются. Ноль — допустимый сдвиг.

Trap — наблюдаемое аварийное завершение. Оптимизация должна сохранять наблюдаемый результат, включая обязательные trap и отсутствие исполнения невыбранных ветвей/операндов. Перенос проверки допускается только с доказательством эквивалентности. Конкретный сигнал и сообщение не гарантируются. Исчерпание памяти/стека не моделируется ядром. Чистота не доказывает отсутствие trap.

main передаёт результат host ABI как код завершения. POSIX shell видит младшие 8 бит. Для проверки значений шире 8 бит нужно сравнение полных значений, а не только кода завершения процесса.

Liza IR и backend

IR версии 5 содержит типизированные значения, параметры, базовые блоки, return, branch, jump и phi для результатов короткого замыкания. Типы сохраняют ширину и знаковость. Арифметическая политика задаётся overflow, политика неправильного счётчика — shift_policy: trap. div и rem имеют division_policy: trap: нулевой делитель вызывает trap; для signed div дополнительно проверяется MIN / -1. Частное округляется к нулю, signed rem возвращает ноль для MIN % -1.

may_trap у операций и функций распространяется через вызовы, включая рекурсию. Это консервативная оценка: она не доказывает достижимость trap, завершение функции или отсутствие исчерпания стека. После изменения IR сведения пересчитываются. Порядок операций и граф блоков сохраняют порядок потенциальных trap.

Проверки типов, имён, диапазонов и возврата выполняются до backend. JSON доступен через emit-ir; формат нестабилен, внешнего ввода IR пока нет. LLVM backend использует i1 для bool и iN для N-битных целых, signed/unsigned сравнения и overflow intrinsics, обычную модульную арифметику без nsw/nuw, условный переход к trap перед сдвигом. Проверка счётчика остаётся 32-битной, даже если сдвигаемое значение уже. Деление проверяет ноль и signed-переполнение до LLVM-инструкции. Для signed-остатка делитель −1 заменяется на +1 до srem, сохраняя нулевой результат и исключая неопределённый случай MIN % -1. Оптимизацией занимается Clang/LLVM.

Ещё не реализовано

Насыщение, числовые преобразования, массивы, изменяемые привязки, циклы, модули и I/O. Проекты будущих числовых операций находятся в числовой модели.