Пишем компилятор на 1с (парсинг кода 1С)
-6Занимательное программирование
Описание
Теперь я решил подойти более грамотно к написанию разбора кода 1с.
Он разбирает текст программы на токены, или их еще называют, иероглифы.
В 1С простейшие токены - это константы строк, дат, чисел, разделители, знак конца оператора, знаки операций, идентификаторы.
Код сканер я уже реализовал: http://infostart.ru/projects/2082/
Он работает быстро, но как-то самопально.
Сегодня я решил набросать грамматику языка 1С, которую можно использовать в компиляторе.
Пока не до самого верхнего уровня, а только для уровня сканера.
Тогда можно написать код компилятора, из которого исключить сканер, он будет сразу разбирать код по грамматике. Тогда один и тот же код, но с разными грамматиками, можно будет использовать для разбора кода модуля и кода запроса.
Я использую упрощенный язык описания грамматики, т.к. грамматику мне тоже придется разбирать на символы и закачивать в код.
В нем для каждого элемента описывается через символ равно какие последовательности элементов может принимать данный элемент.
Элемент "Цель" - описывает токен, который нужно получить, может быть несколько целей.
Используются операторы:
* - значение на месте подстановки может присутствовать или отсутствовать
"" - строка, содержащая один или несколько символов, по умолчанию регистро-независимая
#"" - указывает регистро-зависимую строку
, - через запятую указываются допустимые символы подстановки
\ - обозначает что в подстановке не может использоваться данный вид
Для упрощения разбора используются предопределенные символы:
СимволТаб - символ табуляции
СимволПС - символ переводы строки
ЛюбойСимвол - любой символ
Вот код грамматики, который может получить результат, аналогичный сканеру:
Цель = Токены
Токены = Токен, *Токены
Токен = Идентификатор
Токен = СтроковаяКонстанта
Токен = ПустойРазделитель
Токен = ПрочийЗнак
Токен = ЧисловаяКонстанта
Токен = КонстантаДаты
Токен = КонецОператора
Идентификатор = ПерваяБукваИдентификатора
Идентификатор = ХвостИдентификатора
ПерваяБукваИдентификатора = Буква
ПерваяБукваИдентификатора = "_"
БукваХвостаИдентификатора = Цифра
БукваХвостаИдентификатора = ПерваяБукваИдентификатора
ХвостИдентификатора = БукваХвостаИдентификатора, *ХвостИдентификатора
СтроковаяКонстанта = СимволКавычка, ТелоСтроковойКонстанты, СимволКавычка
СимволТелаСтроковойКонстанты = СимволКавычка, СимволКавычка
СимволТелаСтроковойКонстанты = ЛюбойСимвол\СимволКавычка
ТелоСтроковойКонстанты = СимволТелаСтроковойКонстанты, *ТелоСтроковойКонстанты
ПустойРазделитель = СимволПустогоРазделителя, *ПустойРазделитель
СимволПустогоРазделителя = " "
СимволПустогоРазделителя = СимволТаб
СимволПустогоРазделителя = СимволПС
ПрочийЗнак = "+"
ПрочийЗнак = "-"
ПрочийЗнак = "/"
ПрочийЗнак = "*"
ПрочийЗнак = "-"
ПрочийЗнак = "%"
ПрочийЗнак = "("
ПрочийЗнак = ")"
ПрочийЗнак = "["
ПрочийЗнак = "]"
ПрочийЗнак = "?"
ПрочийЗнак = ","
ЧисловаяКонстанта = ЦелаяЧастьЧисловойКонстанты, ".", ДробнаяЧастьЧисловойКонстанты
ЧисловаяКонстанта = ЦелаяЧастьЧисловойКонстанты
ЦелаяЧастьЧисловойКонстанты = Цифра, *ЦелаяЧастьЧисловойКонстанты
ДробнаяЧастьЧисловойКонстанты = Цифра, *ДробнаяЧастьЧисловойКонстанты
КонстантаДаты = СимволАпостроф, ТелоКонстантыДаты, СимволАпостроф
ТелоКонстантыДаты = Цифра, Цифра, Цифра, Цифра, Цифра, Цифра, Цифра, Цифра
ТелоКонстантыДаты = Цифра, Цифра, Цифра, Цифра, Цифра, Цифра, Цифра, Цифра, Цифра, Цифра, Цифра, Цифра
КонецОператора = ";"
Буква = РусскаяБуква
Буква = ЛатинскаяБуква
Цифра = "0"
Цифра = "1"
Цифра = "2"
Цифра = "3"
Цифра = "4"
Цифра = "5"
Цифра = "6"
Цифра = "7"
Цифра = "8"
Цифра = "9"
РусскаяБуква = "а"
РусскаяБуква = "б"
РусскаяБуква = "в"
РусскаяБуква = "г"
РусскаяБуква = "д"
РусскаяБуква = "е"
РусскаяБуква = "ё"
РусскаяБуква = "ж"
РусскаяБуква = "з"
РусскаяБуква = "и"
РусскаяБуква = "й"
РусскаяБуква = "к"
РусскаяБуква = "л"
РусскаяБуква = "м"
РусскаяБуква = "н"
РусскаяБуква = "о"
РусскаяБуква = "п"
РусскаяБуква = "р"
РусскаяБуква = "с"
РусскаяБуква = "т"
РусскаяБуква = "у"
РусскаяБуква = "ф"
РусскаяБуква = "х"
РусскаяБуква = "ц"
РусскаяБуква = "ч"
РусскаяБуква = "ш"
РусскаяБуква = "щ"
РусскаяБуква = "ъ"
РусскаяБуква = "ы"
РусскаяБуква = "ь"
РусскаяБуква = "э"
РусскаяБуква = "ю"
РусскаяБуква = "я"
ЛатинскаяБуква = "a"
ЛатинскаяБуква = "b"
ЛатинскаяБуква = "c"
ЛатинскаяБуква = "d"
ЛатинскаяБуква = "e"
ЛатинскаяБуква = "f"
ЛатинскаяБуква = "g"
ЛатинскаяБуква = "h"
ЛатинскаяБуква = "i"
ЛатинскаяБуква = "j"
ЛатинскаяБуква = "k"
ЛатинскаяБуква = "l"
ЛатинскаяБуква = "m"
ЛатинскаяБуква = "n"
ЛатинскаяБуква = "o"
ЛатинскаяБуква = "p"
ЛатинскаяБуква = "q"
ЛатинскаяБуква = "r"
ЛатинскаяБуква = "s"
ЛатинскаяБуква = "t"
ЛатинскаяБуква = "u"
ЛатинскаяБуква = "v"
ЛатинскаяБуква = "w"
ЛатинскаяБуква = "x"
ЛатинскаяБуква = "y"
ЛатинскаяБуква = "z"
Оценка сообщества
|
Плюсы (+2):
, |
Минусы (– 8):
, , , , , , , |
Добавить плюс Добавить минус
Комментарии (45)
(1) Я думаю, это будет единственная статья, если компилятор получится.
(2) Разобрать код на структуру в виде дерева, чтобы можно было его проанализировать, поменять и собрать обратно. Т.е. в целях более продвинутого парсинга. Кстати, такой парсинг сильно пригодится для умного парсинга операции присваивания в эмуляторе 77. ;-)
нифига не понял конечной цели! Распиши зачем это вообще надо, может и дельная мысль.
Разбор структуры программы в дерево нифига не новая вещь, я в 94г писал для языка Lisp до сих пор в инете валяеться. (если распишишь идею, может дельных советов дам)
ps
есть такое понятие "лексемы" и "термы" может их будешь использовать? народу будет более понятно
(4) да. дай дельных советов. Потому что я пока только накидал грамматику, а как ее применять для разбора, кроме как тупого перебора вариантов не знаю. ;-)
Цель ясна - чтобы разобрать код 1с.
Ну например, чтобы заменить вызовы вида А = Б на вызовы _set(А, Б) или другие обработки кода модуля.
Можно также добавлять функции в модуль и многое-многое другое.
Под термами ты очевидно понимаешь токены? У нас они назывались токены в универе, если я ничего не путаю.
Кстати в данной грамматике есть ошибка.
Например текст "МойКод" может быть разложен на Мой (Идентификатор) + Код (Идентификатор), затем в Токен + Токен и затем в Токены, но это неправильно. ;-)
МойКод должен быть воспринят как неделимый идентификатор. Поэтому грамматику надо поправить.
для начала надо научиться разбирать отдельный код, метода здесь простая:
1. выделяем коменты
2. Определяем директивы
3. делим на строки (по знаку ";")
4. для каждой строки определяем знаки и приоритет выполнения
(здесь начинаються сложности, ведь изменения переменных могут быть в неявном виде) тут стоит остановиться и подумать как связывать дальше
5. определяем типы термов (переменная, проц, функция, рез. слово)
(это уже вообще фиг сделаешь простым решением)
6. необходимость учета работы кода не связаного с текущим (нпримар формула на форме, или подписка на событие)
ИХМО
можно сделать только для небольшого кода
(8)+
еще тебе вопрос: как ты представляешь загрузить рекурсию в дерево?
то-есть мы возвращаемся к первому вопросу: "а чего надо"? или ты все хочешь эмулятор клюшки собрать?
(8) Вде, сканнер, как я и говорил, я уже написал. Он разбивает поток на токены. Можешь посмотреть, как работает.
Кстати, в грамматике я про комменты забыл, надо дописать, спасибо. Насчет деления по знаку ; не совсем верно, как тебе код:
A = \";\" ;
Вот, поэтому подход с грамматикой языка мне нравится больше - он универсальнее.
Как я представляю себе загрузку рекурсии в дерево?
Элементарно, для сканера "Перем Х;" пример:
Цель
- Токен
-- Идентификатор
--- ПерваяБукваИдентификатора
----- Буква
------ П
--- БукваИдентификатора
----- Буква
------ е
--- БукваИдентификатора
----- Буква
------ р
--- БукваИдентификатора
----- Буква
------ е
--- БукваИдентификатора
----- Буква
------ м
- Токен
-- Разделитель
--- Пробел
-- Идентификатор
--- ПерваяБукваИдентификатора
----- Буква
------ Х
(10) мы о разных вещах говорим, например код
Док = НайтиПоКоду("766", 1+Флаг);
я делал в дерево вот так
док
-НайтиПоКоду
--"776"
--+
---1
---флаг
(11) у тебя теряется информация о том, что такое Док, что такое НайтиПоКоду и т.п. Хотя если ты добавишь колонку, то может быть у тебя и получится. но это все же более узкое решение. ;-) Не такое универсальное, как юзание грамматики.
(11) свойства обьектов конечно должны быть, просто анализатор одычно на втором проходе это заполняет, почти все компиляторы двухпроходные
зато это решений проще в дальнейшем обрабатывать
С ходу... Вот это:
Цифра = "0"
Цифра = "1"
Цифра = "2"
Цифра = "3"
Цифра = "4"
Цифра = "5"
Цифра = "6"
Цифра = "7"
Цифра = "8"
Цифра = "9"
- неправильно. Ты уж определись: или ты грамматику описываешь, или предикаты. Правильно будет: Цифра = 0,1,2,3,4,5,6,7,8,9 Ты ж сам написал, что через запятую допустимые символы подстановки. Кстати, сам ты запятую используешь тоже неправильно, как разделитель лексем. С чего вдруг? забыл вставить квадратные скобки, как необязательный участок. А вообще задачка - ниачем: типовая лаба за 3-ий курс мехмата, которая решается за 15 мин. Задача имела бы смысл, если бы была прога, которая парсила, а поскольку такой проги у тебя нет, то это обычное бумагоморание.
(14) У меня все правильно, видишь ли мне лень в языке описания грамматики поддерживать:
Цифра = 1 или 2 или 3....
Если у меня описание Цифра встречается несколько раз, это и обозначает или.
Цифра = 0,1,2,3,4,5,6,7,8,9 срабатывает только если встретится последовательность "0123456789"
По моему это прозрачно следует из правил.
(14) Прогу я хочу написать на следующем этапе. А стандартных опенсорсных нет?
(13) я не хочу разбивать на два этапа - сканер и сборщик. Я хочу, чтобы она разобрала все на основе грамматики.
(14) Необязательные участки можно расписать как варианты, зачем усложнять язык описания грамматики. ;-)
Учись, студент:
ЗПТ=","
Цифра=0,1,..,9
Буква=а,..,я,А,..,Я,a,..,z,A,..,Z
Подчеркивание=_
НачальныйСимволИмени=Подчеркивание,Буква
СимволИмени=Подчеркивание, Буква,Цифра
Имя=НачальныйСимволИмени [*СимволИмени]
СписокПеременных = Имя, Имя ЗПТ СписокПеременных
ЗаголовокПроцедуры= Процедура ( [СписокПеременных] )
и т.д.



Это первая статья цикла?
Жаль, проект по эмуляции 7.7 забросил.