Skip to content

Спецификация синтаксиса

В данном документе определяется спецификация синтаксиса языка программирования YaoXiang, включая лексическую структуру, синтаксические правила и приоритет операторов.


Глава 1: Лексическая структура

1.1 Исходные файлы

Исходные файлы YaoXiang должны использовать кодировку UTF-8. Исходные файлы обычно имеют расширение .yx.

1.2 Классификация лексем

КатегорияОписаниеПример
ИдентификаторНачинается с буквы или подчёркиванияx, _private, my_var
Ключевое словоЗарезервированное слово языкаType, pub, use
ЛитералФиксированное значение42, "hello", true
ОператорЗнак операции+, -, *, /
РазделительСинтаксический разделитель(, ), {, }, ,

1.3 Ключевые слова

YaoXiang определяет минимальное количество ключевых слов:

pub    use    spawn
ref    mut    if     else
else   match  while  for    return
break  continue as     in     unsafe

Эти ключевые слова имеют особое значение в любом контексте и не могут использоваться как идентификаторы.

1.4 Зарезервированные слова

«Зарезервированные слова» YaoXiang делятся на три уровня, распознаваемые парсером и проверщиком типов на разных этапах:

1.4.1 Литеральные зарезервированные слова

Литеральные идентификаторы с отдельными токенами в парсере не могут использоваться как обычные идентификаторы:

ИдентификаторПринадлежащий типОписание
TypeКлючевое слово метатипа
trueBoolБулево истинное значение
falseBoolБулево ложное значение
voidVoidЛитерал Void (значение Unit). Строчная void — это значение-литерал; заглавная Void — имя типа (см. §1.4.3).

1.4.2 Конструкторные выражения

Следующие конструкторы распознаются парсером в контексте сопоставления с образцом и выражений:

КонструкторПринадлежащий типОписание
some(T)OptionКонструктор варианта значения Option
ok(T)ResultВариант успеха Result
err(E)ResultВариант ошибки Result

1.4.3 Встроенные имена типов

Следующие имена типов предварительно зарегистрированы проверщиком типов и могут использоваться в позиции типа без импорта. Парсер рассматривает их как обычные идентификаторы — не как зарезервированные слова, могут быть перекрыты локальными привязками (не рекомендуется).

Имя типаЛогическое соответствиеОписание
Void⊤ (истина/Unit)Тип-произведение с нулевым количеством полей, ровно один обитатель (литерал void, см. §1.4.1)
Never⊥ (ложь/пустой тип)Тип с нулевым количеством вариантов, ноль обитателей. Никакое выражение не может произвести значение типа Never. Never <: T выполняется для всех T (принцип взрыва).
IntЗнаковое целое число
FloatЧисло с плавающей точкой
BoolБулево значение: true / false
CharСимвол Unicode
StringСтрока

1.5 Идентификаторы

Идентификаторы начинаются с буквы или подчёркивания, последующие символы могут быть буквами, цифрами или подчёркиваниями. Идентификаторы чувствительны к регистру.

Специальные идентификаторы:

  • _ используется как заполнитель, означающий игнорирование значения
  • Идентификаторы, начинающиеся с подчёркивания, обозначают приватные члены

1.6 Литералы

1.6.1 Целые числа

Decimal     ::= [0-9][0-9_]*
Octal       ::= 0o[0-7][0-7_]*
Hex         ::= 0x[0-9a-fA-F][0-9a-fA-F_]*
Binary      ::= 0b[01][01_]*

1.6.2 Числа с плавающей точкой

Float       ::= [0-9][0-9_]* '.' [0-9][0-9_]* ([eE][+-]?[0-9][0-9_]*)?

1.6.3 Строки

String      ::= '"' ([^"\\] | EscapeSequence)* '"'
Escape      ::= '\\' ([nrt'"\\] | UnicodeEscape)
Unicode     ::= 'u' '{' HexDigit+ '}'

1.6.4 Коллекции

List        ::= '[' Expr (',' Expr)* ']'
Dict        ::= '{' String ':' Expr (',' String ':' Expr)* '}'
Set         ::= '{' Expr (',' Expr)* '}'

1.6.5 Генератор списка

ListComp    ::= '[' Expr 'for' Identifier 'in' Expr (',' Expr)* ('if' Expr)? ']'

1.6.6 Проверка вхождения

Membership  ::= Expr 'in' Expr

1.7 Комментарии

// Однострочный комментарий

/* Многострочный комментарий
   может занимать несколько строк */

1.8 Правила отступов

Код должен использовать отступ в 4 пробела, использование символа Tab запрещено. Это обязательное синтаксическое правило.


Глава 2: Синтаксические правила

2.1 Классификация выражений

Expr        ::= Literal
              | Identifier
              | FnCall
              | MemberAccess
              | IndexAccess
              | UnaryOp
              | BinaryOp
              | TypeCast
              | RangeExpr
              | ErrorPropagate
              | RefExpr
              | IfExpr
              | MatchExpr
              | Block
              | Lambda

2.2 Приоритет операторов

ПриоритетОператорАссоциативность
1() [] . ?слева направо
2asслева направо
3* / %слева направо
4+ -слева направо
5..слева направо
6<< >>слева направо
7& | ^слева направо
8== != < > <= >=слева направо
9notсправа налево
10and orслева направо
11if...elseсправа налево
12= += -= *= /=справа налево

2.3 Вызов функции

FnCall      ::= Expr '(' ArgList? ')'
ArgList     ::= Expr (',' Expr)* (',' NamedArg)* | NamedArg (',' NamedArg)*
NamedArg    ::= Identifier ':' Expr

2.4 Доступ к члену

MemberAccess::= Expr '.' Identifier

2.5 Доступ по индексу

IndexAccess ::= Expr '[' Expr ']'

2.6 Преобразование типа

TypeCast    ::= Expr 'as' TypeExpr

2.7 Условное выражение

IfExpr      ::= 'if' Expr Block ('else' 'if' Expr Block)* ('else' Block)?

2.8 Сопоставление с образцом

MatchExpr   ::= 'match' Expr '{' MatchArm+ '}'
MatchArm    ::= Pattern ('|' Pattern)* ('if' Expr)? '=>' Expr ','
Pattern     ::= Literal
              | Identifier
              | Wildcard
              | StructPattern
              | TuplePattern
              | EnumPattern
              | OrPattern

2.9 Блочное выражение

Block       ::= '{' Stmt* Expr? '}'

Единая семантика: Семантика return для всех блоков {} одинакова:

Тип блокаСемантика returnВозвращаемое значение по умолчанию
Обычный {}Возвращает значениеVoid
unsafe {}Возвращает определение типаVoid
spawn {}Возвращает результатVoid

Основной принцип:

  • return в {} всегда возвращает содержимое во внешнюю область видимости
  • По умолчанию без return возвращается Void
  • Форма выражения = expr возвращает значение напрямую
yaoxiang
// Обычный блок {}: return возвращает значение
result = {
    x = compute()
    return x  // Возвращает значение во внешнюю область видимости
}

// Блок unsafe {}: return возвращает определение типа
SqliteDb = unsafe {
    SqliteDb: Type = {
        handle: *Void
    }
    return SqliteDb  // Возвращает определение типа во внешнюю область видимости
}

// Блок spawn {}: return возвращает результат
(a, b) = spawn {
    result1 = fetch("url1"),
    result2 = fetch("url2")
    return (result1, result2)  // Возвращает результат во внешнюю область видимости
}

2.10 Lambda-выражение

Lambda      ::= '(' ParamList? ')' '=>' Expr
            |  '(' ParamList? ')' '=>' Block

2.11 Оператор распространения ошибок

ErrorPropagate ::= Expr '?'

Оператор ? является постфиксным оператором с приоритетом того же уровня, что и .. Для типа Result(T, E):

  • При Ok(v) извлекает значение v и продолжает выполнение
  • При Err(e) распространяет ошибку вверх (return Err(e))
yaoxiang
process: (data: Data) -> Result(Data, Error) = {
    validated = validate(data)?     // При успехе извлекает значение, при неудаче распространяет вверх
    transform(validated)
}

2.12 Выражение диапазона

RangeExpr   ::= Expr '..' Expr

.. создаёт тип диапазона, используемый в циклах for и срезах.

yaoxiang
for i in 0..10 { print(i) }
slice = array[0..5]

2.13 Выражение ref

RefExpr     ::= 'ref' Expr

ref создаёт разделяемое владение. Компилятор автоматически выбирает Rc (однозадачность) или Arc (межзадачность), пользователю не нужно заботиться о деталях реализации.

yaoxiang
data = ref heavy_data
spawn { use(data) }   // Межзадачность: компилятор автоматически выбирает Arc

2.14 Выражение unsafe

UnsafeExpr  ::= 'unsafe' Block

Блок unsafe используется для определения непрозрачных типов и операций с сырыми указателями. Используйте return для возврата определения типа во внешнюю область видимости.

Семантика:

  • Внутри unsafe {} можно определять типы и работать с сырыми указателями
  • Возвращённые типы доступны за пределами unsafe {}
  • Доступ к полям типа требует права unsafe
yaoxiang
// Определение непрозрачного типа в блоке unsafe
SqliteDb = unsafe {
    SqliteDb: Type = {
        handle: *Void  // Сырой указатель
    }
    return SqliteDb
}

// SqliteDb доступен за пределами блока unsafe
db = sqlite3_open("test.db")

2.15 Область видимости

Основные правила:

  • Каждый блок {} создаёт область видимости
  • Внутренняя область видимости может обращаться к переменным внешней области видимости
  • Внешняя область видимости не может обращаться к переменным внутренней области видимости
  • Объявление переменных следует принципу «присваивание имеет приоритет»
yaoxiang
// Блочная область видимости
{
    x = 10
    // x видим в этой области видимости
}
// x невидим за пределами этой области видимости

// Область видимости функции
add: (a: Int, b: Int) -> Int = {
    result = a + b
    return result
}
// result невидим за пределами функции

Объявление переменных и перекрытие:

  • x = value: ищет x по цепочке областей видимости наружу, если найден — присваивает, если нет — объявляет заново
  • mut x = value: явное новое изменяемое объявление, запрещено перекрытие с одноимённой во внешней области
  • В одной области видимости имя может быть объявлено только один раз

Подробное определение: Полные правила области видимости, объявления переменных и механизм перекрытия описаны в Спецификация модульной системы.


Глава 3: Операторы

3.1 Классификация операторов

Stmt        ::= LetStmt
              | ExprStmt
              | ReturnStmt
              | BreakStmt
              | ContinueStmt
              | IfStmt
              | MatchStmt
              | WhileStmt
              | ForStmt
              | SpawnStmt

3.2 Объявление переменной

LetStmt     ::= ('mut')? Identifier (':' TypeExpr)? '=' Expr

3.3 Оператор return

ReturnStmt  ::= 'return' Expr?

Семантика: return используется для возврата значения из блока кода. Если return отсутствует, блок кода по умолчанию возвращает Void.

3.4 Оператор break

BreakStmt   ::= 'break' Identifier?

3.5 Оператор continue

ContinueStmt::= 'continue'

3.6 Оператор if

IfStmt      ::= 'if' Expr Block ('else' 'if' Expr Block)* ('else' Block)?

3.7 Оператор match

MatchStmt   ::= 'match' Expr '{' MatchArm+ '}'

3.8 Оператор while

WhileStmt   ::= 'while' Expr Block

3.9 Оператор for

ForStmt     ::= 'for' 'mut'? Identifier 'in' Expr Block

3.9.1 Семантика: каждая итерация создаёт новую привязку

Семантика цикла for в YaoXiang отличается от традиционных языков: каждая итерация создаёт новую привязку, а не модифицирует одну и ту же переменную.

yaoxiang
// Пример: for i in 1..5
for i in 1..5 {
    print(i)
}

Процесс выполнения:

ИтерацияПоведение переменной цикла
1-яСоздаётся новая привязка i = 1, тело цикла выполняется, печатается 1
2-яСоздаётся новая привязка i = 2 (предыдущая привязка уничтожена), тело цикла выполняется, печатается 2
3-яСоздаётся новая привязка i = 3, тело цикла выполняется, печатается 3
4-яСоздаётся новая привязка i = 4, тело цикла выполняется, печатается 4
КонецТело цикла завершается, привязка уничтожается

Ключевой момент: После завершения каждой итерации привязка, созданная в этой итерации, уничтожается. Следующая итерация — это совершенно новая привязка, не имеющая никакой связи с привязкой предыдущей итерации.

3.9.2 Разница между for и for mut

СинтаксисИзменяемость переменной циклаОписание
for i in 1..5НеизменяемаяВ теле цикла нельзя модифицировать привязку
for mut i in 1..5ИзменяемаяВ теле цикла можно модифицировать привязку
yaoxiang
// Допустимо: каждая итерация создаёт новую привязку, модификация не нужна
for i in 1..5 {
    print(i)  // Читает значение i
}

// Ошибка: неизменяемая привязка, нельзя модифицировать
for i in 1..5 {
    i = i + 1  // Ошибка: нельзя модифицировать неизменяемую привязку
}

// Допустимо: использование for mut позволяет модифицировать привязку
for mut i in 1..5 {
    i = i + 1  // Допускается модификация
}

3.9.3 Проверка перекрытия

YaoXiang запрещает перекрытие переменных. Переменная цикла for не может иметь то же имя, что и переменная во внешней области видимости:

yaoxiang
// Ошибка: i уже объявлена внешне
i = 10
for i in 1..5 {
    print(i)
}

// Правильно: использовать другое имя переменной
i = 10
for j in 1..5 {
    print(j)
}

Это правило применяется ко всем блокам кода, подробнее см. 4.3 Правила перекрытия.

3.9.4 Сравнение с другими языками

ЯзыкСемантика переменной цикла for
YaoXiangКаждая итерация создаёт новую привязку
RustМодифицирует одну и ту же переменную (нужен mut)
PythonМодифицирует одну и ту же переменную (mut не нужен)
C/C++Модифицирует одну и ту же переменную (нужен указатель или ссылка)

Обоснование дизайна: YaoXiang использует семантику привязки потому, что:

  1. Более соответствует естественной семантике В естественном языке «для каждого элемента x из множества» означает, что каждый x — независимый элемент. YaoXiang for i in 1..5 читается как «для каждого i от 1 до 5», и i в каждой итерации — совершенно новая привязка, что соответствует человеческой интуиции.

  2. Предотвращает случайные модификации Семантика неизменяемой привязки по умолчанию означает, что внутри тела цикла невозможно случайно модифицировать переменную цикла. Не нужно беспокоиться, что где-то в сложном теле цикла случайно написано i = ..., что приведёт к трудноотслеживаемым ошибкам.

  3. Высокопроизводительные решения всегда под рукой Когда действительно нужно повторно использовать переменную между итерациями (например, аккумулятор, кэш), можно просто использовать for mut для переключения в режим изменяемой привязки. Это яснее неявного совместного состояния — намерение выражается синтаксически, а не скрывается в рантаймовом поведении.

3.10 Оператор spawn

SpawnBlock  ::= '(' Pattern (',' Pattern)* ')' '=' 'spawn' '{' Expr (',' Expr)* '}'
SpawnFor    ::= Identifier '=' 'spawn' 'for' 'mut'? Identifier 'in' Expr '{' Expr '}'
SpawnStmt   ::= SpawnBlock | SpawnFor

Блок spawn: Явно объявляет границы параллелизма, выражения внутри блока выполняются параллельно.

yaoxiang
(result_a, result_b) = spawn {
    parse(fetch("url1")),
    parse(fetch("url2"))
}

Цикл spawn: Параллельный цикл по данным.

yaoxiang
results = spawn for item in items {
    process(item)
}

Приложение: Шпаргалка по синтаксису

А.1 Управление потоком выполнения

if Expr Block (else if Expr Block)* (else Block)?
match Expr { MatchArm+ }
while Expr Block
for 'mut'? Identifier 'in' Expr Block

А.2 Обработка ошибок

Expr '?'              // Распространение ошибки (тип Result)

А.3 Синтаксис match

match value {
    pattern1 => expr1,
    pattern2 if guard => expr2,
    _ => default_expr,
}