Перегрузка Autolisp-функций на базе .NET

Автор Тема: Перегрузка Autolisp-функций на базе .NET  (Прочитано 8758 раз)

0 Пользователей и 2 Гостей просматривают эту тему.

Оффлайн Дима_Автор темы

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Он написан на C++, но является весьма простым и понятным (на мой взгляд) в т.ч. и для тех, кто пишет на др. языках (например на C#).
Забавно здесь выглядит "но" - то есть как бы подчеркивает своего рода исключение :)
Все далее сугубо ИХМО - реализация транслятора (интерпритатора реже компилятора) какого-нибудь калькулятора, бейсика или упрощенного диалекта самого изучаемого языка действительно есть практически в каждой нормальной книжке по любому языку программирования - согласен оно полезно как практическое занятие охватывающее сразу несколько базовых аспектов программирования. К описательным свойствам реализации - "простым" и "понятным" - я бы еще добавил к рассмотрению свойство "удобным к применению" - то есть насколько представленную модель разбора (парсинга) можно применить в иных аспектах программирования - и  в этом свойстве, опять же ИХМО, однозначно выиграет лисп (не автолисп, а, например Racket) - там по сути любая функция - это не что иное как парсинг самой себя.
Если перейти к конкретике - приведу простую практическую задачу парсинга - ну и т.к. это тема на форуме программистов автокада автодеска, да еще и в разделе .Net - то и соответственно - допустим мы хотим реализовать на .Net некий набор автолисп функций, для расширения функционала последнего. Вне зависимости от их предназначения - во всех них будет содержаться парсинг передаваемых аргументов, сразу не будем ограничиваться простыми вариантами, а предположим, что действия одной и той-же функции могут отличаться в зависимости от количества и типа передаваемых аргументов (своего рода перегрузки). Предложите свой вариант "инструмента" - как это сделать максимально "комфортно" в плане расширения функционала - то есть создания новой "перегрузки" функции. Если для "показания" своего варианта не требуется (или просто лень) писать код - можно просто концептуально - на словах.
з.ы. Целью (моей) является посмотреть варианты и "взесить" для кого какой метод предпочтительней.
з.ы. то А.Ривилис - если посчитаете, что это лучше в свою тему - предлагаю вариант названия - Задача на парсинг ResultBuffer'а.
« Последнее редактирование: 12-12-2015, 03:10:41 от Дима_ »

Онлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Перегрузка Autolisp-функций на базе .NET
« Ответ #1 : 12-12-2015, 02:00:25 »
Тему отделил. По существу вопроса - универсального и обобщенного способа не знаю. Мне многократно приходилось писать функции (значительно больше на ObjectARX, чем на .NET, но это не принципиально, так как принцип тот же), которые вызывались из AutoLisp и были перегружены. Но общей стратегии не было. В каждом отдельном случае был свой вариант перегрузки, который в тот момент я считал оптимальным.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Андрей Бушман

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Re: Перегрузка Autolisp-функций на базе .NET
« Ответ #2 : 12-12-2015, 20:33:43 »
Забавно здесь выглядит "но" - то есть как бы подчеркивает своего рода исключение :)
Некоторых людей C++ отпугивает. В данном контексте "но" присутствует для того, чтобы показать, что пугаться кода в обозначенных главах не стоит.
з.ы. Целью (моей) является посмотреть варианты и "взесить" для кого какой метод предпочтительней.
Написание грамматики и реализация её в парсере - это проверенный годами способ, разработанный людьми не глупыми. Способ широко распространён. Я не вижу смысла изобретать велосипед в поисках какого-то нового, более "продвинутого" подхода, тем более, что способ с использованием грамматики мне понятен, удобен и, соответственно, вполне устраивает. На каком языке реализовывать логику, обозначенную в грамматике - это не столь существенно (имхо).

Я не имею существенного опыта разработки парсеров. Весь мой опыт сводится к тому, что на основе указанных мною двух глав книги Беарне Стровструпа я разобрался с тем, как писать грамматику для парсинга математических выражений и затем реализовывать её в программном коде на C++ и C# (я делал два варианта). Писать грамматику для чего-то более сложного, например для языков программирования, я не побовал, т.к. такой необходимости не возникало.

допустим мы хотим реализовать на .Net некий набор автолисп функций, для расширения функционала последнего. Вне зависимости от их предназначения - во всех них будет содержаться парсинг передаваемых аргументов, сразу не будем ограничиваться простыми вариантами, а предположим, что действия одной и той-же функции могут отличаться в зависимости от количества и типа передаваемых аргументов (своего рода перегрузки). Предложите свой вариант "инструмента" - как это сделать максимально "комфортно" в плане расширения функционала - то есть создания новой "перегрузки" функции. Если для "показания" своего варианта не требуется (или просто лень) писать код - можно просто концептуально - на словах.

Как и для любого языка программирования, необходимо корректно описать грамматику языка. Для LISP выражений, на мой взгляд, получится грамматика даже более простая, чем для описания математических выражений. Кроме того, я более чем уверен, что всё это уже написано и реализовано в полноценных LISP-языках и доступно для изучения в исходных кодах.

Оффлайн Дима_Автор темы

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Re: Перегрузка Autolisp-функций на базе .NET
« Ответ #3 : 15-12-2015, 02:44:06 »
Ну чтоб тема выглядела более законченной приведу вариант реализации обобщенного метода парсинга аргументов автолисп функции - вначале код:
Код - F# [Выбрать]
  1. module LispFun
  2. open System
  3. open Autodesk.AutoCAD.ApplicationServices
  4. open Autodesk.AutoCAD.DatabaseServices
  5. open Autodesk.AutoCAD.EditorInput
  6. open Autodesk.AutoCAD.Geometry
  7. open Autodesk.AutoCAD.Runtime
  8.  
  9. //функция преобразования переданных аргументов в список
  10. //список имеет более широкие возможности сопоставления чем массив
  11. //т.к. возможно сопоставить только "начальные" аргументы
  12. //реализованна с помощью шаблона сопоставления
  13. let Rb=function
  14.   |null->[] // если ничего нет - то пустой список
  15.   |(rb:ResultBuffer)->rb.AsArray()|>Array.toList //если что-то передали - список аргументов
  16.  
  17. //создадим полный шаблон на некоторые типы данных
  18. //возможно дополнение в этом либо другом шаблоне
  19. //перегрузка Unknow - для "необработанных" типов данных
  20. let (|Str|Num|T|Nil|Id|Point|Unknow|) (tv:TypedValue)=
  21.   enum<LispDataType>(tv.TypeCode|>int)|>function
  22.     |LispDataType.Text->Str(tv.Value:?>string) // если передан тип строка - преобразуем в строку
  23.     |LispDataType.Double->Num(tv.Value:?>float) // число в любом формате приведем к float
  24.     |LispDataType.Int16->Num(tv.Value:?>int16|>float)
  25.     |LispDataType.Int32->Num(tv.Value:?>int32|>float)
  26.     |LispDataType.T_atom->T // по аналогии остальные типы ...
  27.     |LispDataType.Nil->Nil
  28.     |LispDataType.ObjectId->Id(tv.Value:?>ObjectId)
  29.     |LispDataType.Point3d->Point(tv.Value:?>Point3d)
  30.     |_->Unknow
  31.  
  32. let (|LstB|LstE|Ss|Unknow|) (tv:TypedValue)= // шаблон на SelectionSet и начало/конец списка
  33.   enum<LispDataType>(tv.TypeCode|>int)|>function
  34.     |LispDataType.ListBegin->LstB
  35.     |LispDataType.ListEnd->LstE
  36.     |LispDataType.SelectionSet->Ss([for id in (tv.Value:?>SelectionSet)->id.ObjectId])
  37.     |_->Unknow
  38.  
  39. let (|AcList|_|)=function // неполный шаблон - на содержимое списка с учетом вложенных
  40.   |LstB::t->let rec fn=function // если начало списка - создаем функцию разбора списка
  41.               |lst,0,LstE::t->Some(lst|>List.rev,t) //уровень вложенности = 0 и получаем конец списка
  42.                                                     // - возвращаем список и данные идущие за ним
  43.               |lst,x,(LstB as s)::t->fn(s::lst,x+1,t) // начало вложенного списка - повышаем уровень
  44.               |lst,x,(LstE as e)::t->fn(e::lst,x-1,t) // конец вложенного списка - понижаем
  45.               |lst,x,a::b->fn(a::lst,x,b) // непосредственно данные
  46.               |_->failwith "Ошибка разбора" // при передачи из лисп сюда программа никак не должна попасть
  47.             fn([],0,t) // запуск функцию разбора
  48.   |_->None // не соответствие шаблону
  49.  
  50. let (|Dxf|_|) code=function // неполный шаблон на сопоставление dxf кода переданного Entity
  51.   |Id id when id.ObjectClass.DxfName=code->Some(id)
  52.   |_->None // другой вча код или другой тип аргумента
  53.  
  54. let (|Line|PLine|Arc|Circle|Unknow|)=function //Шаблон на некоторые примитивы
  55.                                               //с отложенным получением приведенных объектов
  56.   |Dxf "LINE" id->Line(lazy(id.GetObject(OpenMode.ForRead):?>Line))
  57.   |Dxf "LWPOLYLINE" id->PLine(lazy(id.GetObject(OpenMode.ForRead):?>Polyline))
  58.   |Dxf "ARC" id->Arc(lazy(id.GetObject(OpenMode.ForRead):?>Arc))
  59.   |Dxf "CIRCLE" id->Circle(lazy(id.GetObject(OpenMode.ForRead):?>Circle))
  60.   |_->Unknow
  61.  
  62. // функции преобразования данных для возврата из lisp функции
  63. let Str x=new TypedValue(int LispDataType.Text,x)
  64. let Num x=new TypedValue(int LispDataType.Double,x)
  65. let T=new TypedValue(int LispDataType.T_atom)
  66. let Nil=new TypedValue(int LispDataType.Nil)
  67.  
  68. [<LispFunction "XFun">]
  69. let XFun arg= //сама функция
  70.   Application.DocumentManager.MdiActiveDocument|>function
  71.     |null->Nil //если нет активного документа
  72.     |doc->let ed,db,trf=doc.Editor,doc.Database,doc.TransactionManager.StartTransaction
  73.           arg|>Rb|>function //переводим ResultBuffer в список и начинаем сопоставления
  74.             // первый шаблон - это два числа
  75.            |[Num a;Num b]->"\nСумма двух чисел "|>ed.WriteMessage
  76.                            a+b|>Num //возвращаем число из приведенных чисел
  77.             // аналогично - две строки
  78.            |[Str a;Str b]->"\nСцепление двух строк "|>ed.WriteMessage
  79.                            a+b|>Str
  80.             //Entity c DXF кодом "LINE"
  81.            |Dxf "LINE" line::t->String.Format("Получена линия с дескриптором {0}.",line.Handle)|>Str
  82.            |Str "Средняя точка"::AcList([Point pt1;Point pt2; Point pt3],[])->
  83.                "\nВычисляем из списка трех точек "|>ed.WriteMessage
  84.                (pt1.X+pt2.Y+pt3.Z)|>Num
  85.             |Num a::AcList(lst,[])->"\nЧисло и список "|>ed.WriteMessage
  86.                                     a+float(lst|>List.length)|>Num
  87.             // если окружность и дуга или в обратном порядке (дуга, окружность)
  88.             |Circle circ::Arc arc::t|Arc arc::Circle circ::t->
  89.                use tr=trf() //вызовем транзакцию внутри которой будут выполнены отложенные вычисления
  90.                let ret=circ.Value.Center.DistanceTo(arc.Value.Center)
  91.                tr.Commit()
  92.                "\nРассстояние между окружностью и дугой "|>ed.WriteMessage
  93.                ret|>Num
  94.             |Str "Сколько аргументов"::t->
  95.                let rec ArgCount=function
  96.                  |[]->0
  97.                  |AcList(lst,b)->1+ArgCount(b)
  98.                  |a::b->1+ArgCount(b)
  99.                String.Format("Количество аргументов={0}, кроме первого",ArgCount t)|>Str
  100.             |lst when lst|>List.forall (function |Ss _->true //проверка что все аргументы - имеют тип SelectionSet
  101.                                                  |_->false)->
  102.                 "\nКоличество уникальных объектов в наборах "|>ed.WriteMessage
  103.                 lst|>List.collect (function|Ss lst->lst
  104.                                            |_->[])
  105.                    |>Seq.distinct|>Seq.length|>Num
  106.             |_->"\nНеизвестный набор параметров "|>ed.WriteMessage
  107.                 Nil // если ни один шаблон не совпал
Теперь немного теории шаблон сопоставления это, что-то типа регулярных выражений только работающие с любыми данными (в том числе кодом) и с возможностью задания правила их поведения. Служат для распознания и преобразования данных заданного формата. Бывают двух типов - полные и не полные - то есть те в которых есть однозначный вариант для всех вариантов ожидаемых входных данных, либо которые обрабатывают только "свои" варианты - то есть будет идти обработка до шаблона который распознает данные как свои. 
В строках 20-30 представлен полный шаблон, его можно расширить (либо написать еще дополнительный) на необходимые типы данных - суть его очень проста - определяет тип передаваемых данных и преобразует результат  в них.
В строках 39-48 - шаблон посложнее он вернет результат только после того как пересчитает переданный список - который и вернет. Полученный список так-же можно сопосталять с имеющимеся шаблонами (что мы и будем делать в функции).
Далее идет неполный шаблон который вернет ObjectID только в случае совпадения DXF имени с аргументом шаблона code.
Ну а далее идут шаблоны который вернет приведенный объект соответствущего типа. Объект можно было получить и без использования транзакции - но специально, для демонстрации - использован классический для ФП метод - отложенное вычисление - то есть реальное получение объекта будет произведено не во время распознания - а во время выполнения шаблона когда будет открыта "своя" транзакция - для этого приведение  находиться внутри отложенного вычисления - lazy(...)
Для проверки я специально взял максимально "безумные" с практической точки зрения перегрузки - можете попробывать реализовать свои - уверен если этот материал понятен, то создать свою перегрузку лисп функции не займет время и главное, что логика очевидна из кода.
Что делает функция при передачи:
 - двух чисел - возращает их сумму;
 - двух строк - их "сцепку";
 - первого аргумента Entity LINE - выводит ее дескриптор (последующие аргументы игнорируются)
 - слова "Средняя точка" и списка из трех точек - сумму x1+y2+z3;
 - числа и списка - сумму из числа и кол-ва элементов списка;
 - окружности и дуги (или наоборот) + любые аргументы - расстояние между их центрами;
 - слова "Сколько аргументов" - количества аргументов (списки считает как один элемент)
 - любом кол-ве SelectionSet (ssget) - сумарное количество выбранных элементов (без дублей);
 - все остальное - nil...
При добавлении своей перегрузки ее надо добавлять руководствуясь правилом - вначале частное, потом общее - то есть вначале надо проверить что передан список начинающийся на слово "корова", а затем что передан список слов.
з.ы. Если стало интересно попробуйте, например,  создать свой шаблон autolisp списка который состоит из данный одинакового типа.
з.з.ы. Если кто-нибудь предложит более "читабельный" вариант (хотя-бы с его точки зрения) буду очень рад - может и поучусь чему.
« Последнее редактирование: 15-12-2015, 09:32:30 от Александр Ривилис »

Оффлайн Дима_Автор темы

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Re: Перегрузка Autolisp-функций на базе .NET
« Ответ #4 : 15-12-2015, 21:58:43 »
То Александр Ривилис - появление тега 'code=fsharp' записываю себе как подарок на НГ :)

Онлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Перегрузка Autolisp-функций на базе .NET
« Ответ #5 : 15-12-2015, 22:21:24 »
То Александр Ривилис - появление тега 'code=fsharp' записываю себе как подарок на НГ :)
Ага. Я пока его добавлял (а это совсем тривиальная операция) угробил совсем подсветку синтаксиса и с большим трудом её восстановил. 😉
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Дима_Автор темы

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Re: Перегрузка Autolisp-функций на базе .NET
« Ответ #6 : 15-12-2015, 22:24:14 »
Я хоть и дальтоник - но все равно приятно.

Онлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Перегрузка Autolisp-функций на базе .NET
« Ответ #7 : 15-12-2015, 22:33:06 »
Я хоть и дальтоник - но все равно приятно.
Там не только подсветка перестала работать, но и нумерация строк и возможность выделения кода....
Хорошо то, что хорошо кончается.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Дима_Автор темы

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Re: Перегрузка Autolisp-функций на базе .NET
« Ответ #8 : 15-12-2015, 22:36:23 »
Off-Topic: показать
Потом может будете писать - F# мне как-то сразу (вначале) не задался... :)