Спецификация синтаксиса
В данном документе определяется спецификация синтаксиса языка программирования 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 | — | Ключевое слово метатипа |
true | Bool | Булево истинное значение |
false | Bool | Булево ложное значение |
void | Void | Литерал 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' Expr1.7 Комментарии
// Однострочный комментарий
/* Многострочный комментарий
может занимать несколько строк */1.8 Правила отступов
Код должен использовать отступ в 4 пробела, использование символа Tab запрещено. Это обязательное синтаксическое правило.
Глава 2: Синтаксические правила
2.1 Классификация выражений
Expr ::= Literal
| Identifier
| FnCall
| MemberAccess
| IndexAccess
| UnaryOp
| BinaryOp
| TypeCast
| RangeExpr
| ErrorPropagate
| RefExpr
| IfExpr
| MatchExpr
| Block
| Lambda2.2 Приоритет операторов
| Приоритет | Оператор | Ассоциативность |
|---|---|---|
| 1 | () [] . ? | слева направо |
| 2 | as | слева направо |
| 3 | * / % | слева направо |
| 4 | + - | слева направо |
| 5 | .. | слева направо |
| 6 | << >> | слева направо |
| 7 | & | ^ | слева направо |
| 8 | == != < > <= >= | слева направо |
| 9 | not | справа налево |
| 10 | and or | слева направо |
| 11 | if...else | справа налево |
| 12 | = += -= *= /= | справа налево |
2.3 Вызов функции
FnCall ::= Expr '(' ArgList? ')'
ArgList ::= Expr (',' Expr)* (',' NamedArg)* | NamedArg (',' NamedArg)*
NamedArg ::= Identifier ':' Expr2.4 Доступ к члену
MemberAccess::= Expr '.' Identifier2.5 Доступ по индексу
IndexAccess ::= Expr '[' Expr ']'2.6 Преобразование типа
TypeCast ::= Expr 'as' TypeExpr2.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
| OrPattern2.9 Блочное выражение
Block ::= '{' Stmt* Expr? '}'Единая семантика: Семантика return для всех блоков {} одинакова:
| Тип блока | Семантика return | Возвращаемое значение по умолчанию |
|---|---|---|
Обычный {} | Возвращает значение | Void |
unsafe {} | Возвращает определение типа | Void |
spawn {} | Возвращает результат | Void |
Основной принцип:
returnв{}всегда возвращает содержимое во внешнюю область видимости- По умолчанию без
returnвозвращаетсяVoid - Форма выражения
= exprвозвращает значение напрямую
// Обычный блок {}: 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? ')' '=>' Block2.11 Оператор распространения ошибок
ErrorPropagate ::= Expr '?'Оператор ? является постфиксным оператором с приоритетом того же уровня, что и .. Для типа Result(T, E):
- При
Ok(v)извлекает значениеvи продолжает выполнение - При
Err(e)распространяет ошибку вверх (return Err(e))
process: (data: Data) -> Result(Data, Error) = {
validated = validate(data)? // При успехе извлекает значение, при неудаче распространяет вверх
transform(validated)
}2.12 Выражение диапазона
RangeExpr ::= Expr '..' Expr.. создаёт тип диапазона, используемый в циклах for и срезах.
for i in 0..10 { print(i) }
slice = array[0..5]2.13 Выражение ref
RefExpr ::= 'ref' Exprref создаёт разделяемое владение. Компилятор автоматически выбирает Rc (однозадачность) или Arc (межзадачность), пользователю не нужно заботиться о деталях реализации.
data = ref heavy_data
spawn { use(data) } // Межзадачность: компилятор автоматически выбирает Arc2.14 Выражение unsafe
UnsafeExpr ::= 'unsafe' BlockБлок unsafe используется для определения непрозрачных типов и операций с сырыми указателями. Используйте return для возврата определения типа во внешнюю область видимости.
Семантика:
- Внутри
unsafe {}можно определять типы и работать с сырыми указателями - Возвращённые типы доступны за пределами
unsafe {} - Доступ к полям типа требует права unsafe
// Определение непрозрачного типа в блоке unsafe
SqliteDb = unsafe {
SqliteDb: Type = {
handle: *Void // Сырой указатель
}
return SqliteDb
}
// SqliteDb доступен за пределами блока unsafe
db = sqlite3_open("test.db")2.15 Область видимости
Основные правила:
- Каждый блок
{}создаёт область видимости - Внутренняя область видимости может обращаться к переменным внешней области видимости
- Внешняя область видимости не может обращаться к переменным внутренней области видимости
- Объявление переменных следует принципу «присваивание имеет приоритет»
// Блочная область видимости
{
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
| SpawnStmt3.2 Объявление переменной
LetStmt ::= ('mut')? Identifier (':' TypeExpr)? '=' Expr3.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 Block3.9 Оператор for
ForStmt ::= 'for' 'mut'? Identifier 'in' Expr Block3.9.1 Семантика: каждая итерация создаёт новую привязку
Семантика цикла for в 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 | Изменяемая | В теле цикла можно модифицировать привязку |
// Допустимо: каждая итерация создаёт новую привязку, модификация не нужна
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 не может иметь то же имя, что и переменная во внешней области видимости:
// Ошибка: 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 использует семантику привязки потому, что:
Более соответствует естественной семантике В естественном языке «для каждого элемента x из множества» означает, что каждый x — независимый элемент. YaoXiang
for i in 1..5читается как «для каждого i от 1 до 5», и i в каждой итерации — совершенно новая привязка, что соответствует человеческой интуиции.Предотвращает случайные модификации Семантика неизменяемой привязки по умолчанию означает, что внутри тела цикла невозможно случайно модифицировать переменную цикла. Не нужно беспокоиться, что где-то в сложном теле цикла случайно написано
i = ..., что приведёт к трудноотслеживаемым ошибкам.Высокопроизводительные решения всегда под рукой Когда действительно нужно повторно использовать переменную между итерациями (например, аккумулятор, кэш), можно просто использовать
for mutдля переключения в режим изменяемой привязки. Это яснее неявного совместного состояния — намерение выражается синтаксически, а не скрывается в рантаймовом поведении.
3.10 Оператор spawn
SpawnBlock ::= '(' Pattern (',' Pattern)* ')' '=' 'spawn' '{' Expr (',' Expr)* '}'
SpawnFor ::= Identifier '=' 'spawn' 'for' 'mut'? Identifier 'in' Expr '{' Expr '}'
SpawnStmt ::= SpawnBlock | SpawnForБлок spawn: Явно объявляет границы параллелизма, выражения внутри блока выполняются параллельно.
(result_a, result_b) = spawn {
parse(fetch("url1")),
parse(fetch("url2"))
}Цикл spawn: Параллельный цикл по данным.
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,
}