Введение


Спасибо всем за критику в комменте под первым постом, где я хотел попробовать написать про MPS, не затрагивая важные темы, чтобы можно было потом более качественно начать писать по порядку.


Зачем нам нужен язык Weather?


В комментариях к 1 посту было следующее высказывание


С этой точки зрения, DSL — это как фреймворк, только с более удобным интерфейсом. Ясное дело, под один проект фреймворк делать никто не будет, за исключением совсем уж монструозных случаев. А сделать его под конкретную предметную область — почему бы и нет?..

В принципе, так оно все и работает. Хорошие языки похожи по сути на хорошие фреймворки: они позволяют писать что-то важное, не заморачиваясь о том, что мы не хотим писать. По ходу повествования я буду периодически обращаться к другим языкам для аналогий и сравнений.


Синтаксис


Язык Weather, который мы хотим реализовать, должен выполнять следующую задачу: мы должны уметь лаконично выражать условия (погода сегодня, например) и следствия (погода завтра, послезавтра...).
В языке Weather мы будем делать наши прогнозы отталкиваясь от 1 фактора: от температуры на сегодняшний день(массив объектов время + погодные условия).


Пример массива входных данных на JS
const weatherInput = [
  {
    time: 1501179579253,
    temperature: {
        unit: "celsius",
        value: 23.7
     }
  },
 {
    time: 1501185944759,
    temperature: {
        unit: "fahrenheit",
        value: 15.3
     }
  }
]

Думаю сойдет.


Реализация на Weather
Weather prediction rules for Saint Petersburg
data Today:
  [21:23]{
    temperature = 23.7 °C
  }
  [23:06]{
    temperature = 15.3 °F
  }

У нас очень простые данные — время + температура в единицах измерения. Создадим абстрактный концепт WeatherTimedData — он нам нужен для хранения времени измерения и самой температуры.


image


Теперь нужно определить, что такое Temperature и Time.


image


Time реализован очень просто — у нас есть время в часах и минутах, а отображается оно как hh : mm.


image


Если с Time все понятно, то с Temperature немного нет. Во-первых — value это какой-то _FPNumber_String. На самом деле это MPS'овский double, так что ничего страшного. Но вопрос — как из интерфейса Temperature сделать реализации температуры в разных единицах измерения, да так чтобы это еще и красиво было? И вообще, что такое интерфейс концепт?
У таких концептов не может быть реализации в AST. То есть, вообще никакой. Только если другой концепт расширяет его, и никак иначе. Делается это, как и в ООП, для того, чтобы обобщить несколько классов под одно общее начало.


Вот как я реализовал отображение в редакторе для Temperature:


image


Здесь у нас первая ячейка — double значение, величина температуры, а вторая — Read-Only model access. Здесь мы немного отдаляемся от практики и переходим к теории.


Теория


В MPS все строится на концептах, если проводить прямую параллель с ООП, то концепты — это классы. Они могут расширять другие концепты, реализовывать интерфейсы, но еще они могут иметь какую-то логику. Например, если мы описываем абстрактный класс температуры, то нужно предусмотреть возможность задания собственных единиц измерения.


abstract class Temperature{
  abstract double value;
  public abstract String getUnit();

  override String toString(){
    return value + this.getUnit();
  }
}

Можно было бы задать unit как переменную, а не писать абстрактный метод, но…
Есть аспект, называется Behavior. Все, что он может делать — добавлять новые методы к концепту. То есть добавить переменную мы не можем, поэтому будем использовать абстрактный метод.


image


И вот после этого мы можем у каждой реализации концепта Temperature вызывать этот метод. Но где же его вызывать? Как вообще кодить в этом MPS?..


Снова практика


Мы остановились на том, что у нас есть непонятная ячейка в Editor аспекте — ReadOnly model access. Все очень просто — если нужно как-то логически обработать proeprty/children/reference перед тем, как его показывать, и на это не хватает встроенных приколов, то мы можем сами получить нужную строку из контекста редактора и реализации концепта. Если просто — нам дают текущий объект концепта, то есть реализованный, и мы можем из него получить все, что мы там понапихали. В данном случае мы хотим получить единицу измерения, поэтому мы нажимаем на ячейку R/O model access и пишем


image


Кстати, в любом месте кода вы можете тыкнуть на штучку, что Вас интересует и нажать Ctrl + Shift + Enter и получить информацию о типе этой штучки. Например, если мы нажмем на node в скрине выше и узнаем его тип, то мы получим


image


node<Название Концепта> = какая-то реализация концепта
concept<Название Концепта> = класс концепта
Так! Мы уже умеем составлять температуру по значению и единице измерения, но откуда мы возьмем, какая единица измерения нам нужна? Из дочерних реализаций, естественно.
Создаем пустой CelsiusTemperature концепт, расширяем Temperature и создаем для него behavior.


image


image


Как видно на последнем скрине, мы переопределяем метод getUnit(он имеется в зоне видимости из-за того, что мы наследовали концепт от Temperature) и возвраещем "°C". Все просто!


Остается только собрать все вместе в WeatherTimedData:


image


Собираем язык и смотрим на результат:


image


Вроде похоже на правду. Еще, конечно, нет самих предсказаний погоды, нет подсветки, к тому же часы у нас могут быть больше 24 и меньше нуля, минуты тоже не ограничены ничем, кроме размерности integer… В следующем посте ждите разъяснений по новому аспекту — constraints и еще чего-нибудь. А пока — пишите фидбек в комментариях, все как всегда, если вопрос простой — отвечаю там же, если он обширен и скорее как пожелание — то я постараюсь с каждым постом писать все качественнее. Спасибо за внимание!


//UPDATE\
Завел кривой репозиторий с проектом, где каждая ветка — новый туториал на Хабре. Это он!!!

Поделиться с друзьями
-->

Комментарии (16)


  1. BOOTor
    28.07.2017 06:21

    Самое интересное в погоде — ее предсказание. Расскажите, пожалуйста, будут ли у Вас свои модели, или данные будут браться из доступных «чужих» данных, NOAA, например?


    1. enchantinggg
      28.07.2017 12:09

      Я не хочу делать предсказания осмысленными — цель уроков задеть как можно больше актуальных тем в MPS, а не написать готовый продукт.


  1. isden
    28.07.2017 09:15

    Вы пишете в комменте под первым постом:

    > Например, потому что можно будет не переписывать код на другой ЯП в зависимости от платформы (на JavaScript, например, а писать на 1 языке и писать для него языки -расширения, которые будут переводить AST Weather в AST другого языка.

    Т.е. вот то, что мы написали, это можно автоматически транслировать в другой язык? Я правильно понимаю?


    1. enchantinggg
      28.07.2017 12:07

      Да, если модель языка описана в MPS.Например, уже описана Java, она называется baseLanguage


  1. elmal
    28.07.2017 12:26

    Мне вот одно интересно, я один считаю, что MPS в текущем виде пользоваться практически невозможно? Ибо текст программы по существу графический, хоть и выглядит текстовым. Банально невозможно скопипастить текст чтоб спросить на форуме. Даже в этой статье в качестве примеров идут картинки, хотя Java класс Temperature идет текстом.


    1. enchantinggg
      28.07.2017 12:37

      Скопировать текст можно без проблем, а вот со вставлением могут возникнуть небольшие проблемы: нужно будет вручную все dependencies зарезолвить. Я прикрепляю картинки чтобы читатель был в контексте MPS, а текстовая форма это как то… Отдаленно.


    1. GooRoo
      28.07.2017 21:43

      Пользоваться им и правда не всегда удобно. Только это проблема не MPS, а устаревших прочих инструментов.


      1. enchantinggg
        28.07.2017 23:17

        Вообще, если покодить в нормальном режиме(не в режиме разработки нового языка, а в режиме разработки программ на языке из MPS), то через недельку шпилишь уже как в обычном текстовом редакторе.


  1. elmal
    28.07.2017 13:02

    Можно спросить, как мне скопипастить? Только что я поставил MPS 2017.1.2. У меня при старте открылся класс Main с тестовым примером. Я мечтаю его текст вставить сюда. И я не могу даже выделение сделать. Единственное что я смог сделать — это нажать preview generated text, но это не совсем то, что мне хочется.


    1. enchantinggg
      28.07.2017 13:45
      +1

      Используйте хоткей ctrl + w для выделения. Каждое новое нажатие ctrl + w выделяет текущий узел AST и родительский.

      Weather prediction rules for Saint Petersburg
      [ 21 : 23 ] {
      temperature = 23.3 °C
      }
      [ 22 : 15 ] {
      temperature = 80.2 °F
      }


      1. elmal
        28.07.2017 14:27

        Да ужжж. Конечно радует, что это можно сделать. Но очень не интуитивно, без подсказки я б точно не догадался. А мышкой это никак нельзя сделать?


        1. enchantinggg
          28.07.2017 14:35
          +1

          Я думаю, это сделано специально в силу того, что при копировании куска AST и вставления в другой редактор не было «пропавших кусков AST». То есть можно выделить только узлы. Ну и еще есть такая штука как кастомная проекция(Например, в sample проекте multipleProjections)
          image
          Я думаю не все текстовые редакторы поддерживают таблицы, но вот, как выглядел бы текст этой программы, если бы Вы его скопировали:
          @export(namespace = fdfd)
          workflow container org.jetbrains.workflow
          tabular workflow simpleIssueTracking
          event Close; event Reopen;

          structural workflow advancedIssueTracking
          event Close; event Verify; event Reopen;

          state Open; -> Closed
          state Open; -> Open

          state Closed; -> Open
          state WaitingVerification; -> Closed
          -> Open

          state Closed; -> Open


        1. GooRoo
          28.07.2017 21:44

          Ctrl+^/v


          1. enchantinggg
            28.07.2017 23:16

            У меня работает только с ctrl + ^ :c


            1. GooRoo
              29.07.2017 00:32

              Вверх — расширение выделения, вниз — наоборот.


              1. enchantinggg
                29.07.2017 00:45

                И действительно, спасибо.