Files
sfera/rust/crates/bsl-parser/src/lib.rs
T
2026-05-16 19:03:49 +03:00

291 lines
9.9 KiB
Rust

pub mod calls;
pub mod keywords;
pub mod models;
pub mod parser;
pub mod queries;
pub mod writes;
use crate::calls::extract_calls;
use crate::models::ParsedSemanticUnit;
use crate::parser::extract_procedures;
use crate::queries::extract_queries;
use crate::writes::extract_writes;
pub fn parse_module(source_path: &str, source: &str) -> ParsedSemanticUnit {
ParsedSemanticUnit {
source_path: source_path.to_string(),
procedures: extract_procedures(source),
calls: extract_calls(source),
queries: extract_queries(source),
writes: extract_writes(source),
diagnostics: Vec::new(),
}
}
#[cfg(test)]
mod tests {
use super::parse_module;
#[test]
fn parses_procedure_calls_query_and_write() {
let source = r#"
Процедура Проведение()
ПроверитьОстатки();
Движения.ОстаткиТоваров.Записать();
КонецПроцедуры
Процедура ПроверитьОстатки()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
Остатки.Номенклатура
ИЗ
РегистрНакопления.ОстаткиТоваров КАК Остатки";
КонецПроцедуры
"#;
let unit = parse_module("module.bsl", source);
assert_eq!(unit.procedures.len(), 2);
assert_eq!(unit.calls.len(), 1);
assert_eq!(unit.queries.len(), 1);
assert_eq!(unit.writes.len(), 1);
assert_eq!(unit.calls[0].caller, "Проведение");
assert_eq!(unit.calls[0].callee, "ПроверитьОстатки");
assert_eq!(unit.writes[0].target, "ОстаткиТоваров");
}
#[test]
fn parses_english_function_query_call_and_write() {
let source = r#"
Procedure Posting()
CheckStock(); // inline comment
Movements.StockBalance.Write();
EndProcedure
Function CheckStock()
Query = New Query;
Query.Text =
"SELECT
Stock.Item
FROM
AccumulationRegister.StockBalance AS Stock";
EndFunction
"#;
let unit = parse_module("module.bsl", source);
assert_eq!(unit.procedures.len(), 2);
assert!(unit.procedures[1].is_function);
assert_eq!(unit.calls.len(), 1);
assert_eq!(unit.calls[0].callee, "CheckStock");
assert_eq!(unit.queries.len(), 1);
assert_eq!(
unit.queries[0].tables,
vec!["AccumulationRegister.StockBalance"]
);
assert!(!unit.queries[0].query_text.ends_with('"'));
assert_eq!(unit.writes.len(), 1);
assert_eq!(unit.writes[0].target, "StockBalance");
}
#[test]
fn parses_mixed_russian_and_english_keywords_in_one_module() {
let source = r#"
Procedure Posting()
ПроверитьОстатки();
Movements.StockBalance.Write();
EndProcedure
Процедура ПроверитьОстатки()
Query = New Query;
Query.Text =
"SELECT
Stock.Item
FROM
AccumulationRegister.StockBalance AS Stock";
КонецПроцедуры
"#;
let unit = parse_module("module.bsl", source);
assert_eq!(unit.procedures.len(), 2);
assert_eq!(unit.procedures[0].name, "Posting");
assert_eq!(unit.procedures[1].name, "ПроверитьОстатки");
assert_eq!(unit.calls.len(), 1);
assert_eq!(unit.calls[0].caller, "Posting");
assert_eq!(unit.calls[0].callee, "ПроверитьОстатки");
assert_eq!(unit.queries.len(), 1);
assert_eq!(
unit.queries[0].tables,
vec!["AccumulationRegister.StockBalance"]
);
assert_eq!(unit.writes.len(), 1);
assert_eq!(unit.writes[0].target, "StockBalance");
}
#[test]
fn parses_inline_query_assignment_with_pipe_prefixed_lines() {
let source = r#"
Процедура ПолучитьТовары()
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
|Товары.Ссылка
|ИЗ
|Справочник.Номенклатура КАК Товары";
КонецПроцедуры
"#;
let unit = parse_module("module.bsl", source);
assert_eq!(unit.queries.len(), 1);
assert_eq!(unit.queries[0].tables, vec!["Справочник.Номенклатура"]);
assert!(unit.queries[0].query_text.starts_with("ВЫБРАТЬ"));
assert!(!unit.queries[0].query_text.contains("|ИЗ"));
}
#[test]
fn parses_query_from_and_table_on_same_line() {
let source = r#"
Процедура ПолучитьКонтрагентов()
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
|Контрагенты.Ссылка
|ИЗ Справочник.Контрагенты КАК Контрагенты";
КонецПроцедуры
"#;
let unit = parse_module("module.bsl", source);
assert_eq!(unit.queries.len(), 1);
assert_eq!(unit.queries[0].tables, vec!["Справочник.Контрагенты"]);
}
#[test]
fn parses_assignment_function_calls() {
let source = r#"
Процедура Проведение()
МожноПроводить = ПроверитьОстатки();
КонецПроцедуры
Функция ПроверитьОстатки()
Возврат Истина;
КонецФункции
"#;
let unit = parse_module("module.bsl", source);
assert_eq!(unit.calls.len(), 1);
assert_eq!(unit.calls[0].caller, "Проведение");
assert_eq!(unit.calls[0].callee, "ПроверитьОстатки");
}
#[test]
fn parses_condition_function_calls() {
let source = r#"
Процедура Проведение()
Если ПроверитьОстатки() Тогда
Движения.ОстаткиТоваров.Записать();
КонецЕсли;
КонецПроцедуры
Функция ПроверитьОстатки()
Возврат Истина;
КонецФункции
"#;
let unit = parse_module("module.bsl", source);
assert_eq!(unit.calls.len(), 1);
assert_eq!(unit.calls[0].caller, "Проведение");
assert_eq!(unit.calls[0].callee, "ПроверитьОстатки");
}
#[test]
fn parses_join_tables_from_query_text() {
let source = r#"
Процедура ПолучитьЗаказы()
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
|Заказы.Ссылка,
|Контрагенты.Наименование
|ИЗ Документ.ЗаказПокупателя КАК Заказы
|ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты
|ПО Заказы.Контрагент = Контрагенты.Ссылка";
КонецПроцедуры
"#;
let unit = parse_module("module.bsl", source);
assert_eq!(
unit.queries[0].tables,
vec!["Документ.ЗаказПокупателя", "Справочник.Контрагенты"]
);
}
#[test]
fn parses_object_write_targets_from_create_assignments() {
let source = r#"
Процедура СоздатьНоменклатуру()
Элемент = Справочники.Номенклатура.СоздатьЭлемент();
Элемент.Записать();
КонецПроцедуры
Procedure CreateOrder()
Order = Documents.CustomerOrder.CreateDocument();
Order.Write();
EndProcedure
"#;
let unit = parse_module("module.bsl", source);
assert_eq!(unit.writes.len(), 2);
assert_eq!(unit.writes[0].target, "Справочник.Номенклатура");
assert_eq!(unit.writes[0].write_type, "OBJECT_WRITE");
assert_eq!(unit.writes[1].target, "Документ.CustomerOrder");
assert_eq!(unit.writes[1].write_type, "OBJECT_WRITE");
}
#[test]
fn parses_recordset_write_targets_from_create_assignments() {
let source = r#"
Процедура ЗаписатьЦены()
Набор = РегистрыСведений.Цены.СоздатьНаборЗаписей();
Набор.Записать();
КонецПроцедуры
Procedure WriteBalances()
Records = AccumulationRegisters.StockBalance.CreateRecordSet();
Records.Write();
EndProcedure
"#;
let unit = parse_module("module.bsl", source);
assert_eq!(unit.writes.len(), 2);
assert_eq!(unit.writes[0].target, "РегистрСведений.Цены");
assert_eq!(unit.writes[0].write_type, "REGISTER_WRITE");
assert_eq!(unit.writes[1].target, "РегистрНакопления.StockBalance");
assert_eq!(unit.writes[1].write_type, "REGISTER_WRITE");
}
#[test]
fn parses_calls_and_writes_inside_control_flow_blocks() {
let source = r#"
Процедура Проведение()
Для Каждого Строка Из Товары Цикл
ПроверитьСтроку(Строка);
КонецЦикла;
Попытка
Движения.ОстаткиТоваров.Записать();
Исключение
СообщитьОбОшибке();
КонецПопытки;
КонецПроцедуры
Процедура ПроверитьСтроку(Строка)
КонецПроцедуры
Процедура СообщитьОбОшибке()
КонецПроцедуры
"#;
let unit = parse_module("module.bsl", source);
assert_eq!(unit.calls.len(), 2);
assert_eq!(unit.calls[0].callee, "ПроверитьСтроку");
assert_eq!(unit.calls[1].callee, "СообщитьОбОшибке");
assert_eq!(unit.writes.len(), 1);
assert_eq!(unit.writes[0].target, "ОстаткиТоваров");
}
}