nemerleweb

Официальный русскоязычный блог проекта NemerleWeb www.nemerleweb.com

Типизация JS

_NN_ _NN_
Как оказалось тема не освещена совсем.
А между тем вопрос был поднят давно.

Предисловие

Итак простой способ вызова JS это макрос с очевидным названием 'js'.
Используется очень просто:

[Unit]
class MyPage
{
  public F() : void
  {
    js <# window.alert("abc"); #>
  }
}


Очевидно, что никакой типизацией и проверками ошибок не пахнет.
Создать юнит с именем window и методом alert не пройдет, т.к. NemerleWeb не обязан (и не генерирует) такой же код как должен быть для вызова JS.
Будет что-то вроде: window.alert["System.String"]("abc").

Типизация JS

Для этой цели используется простой макрос JSApi:
К примеру возьмем тот же window.alert:
[JSApi]
class window
{
  public static alert(s : string) : void {}
}

Проще некуда.
Теперь мы можем писать в юните код на немерле и быть уверенным , что позовем правильно функцию.
[Unit]
class MyPage
{
  public F() : void
  {
    window.alert("abc");
    // window.alert(1) - нельзя, типизация однако !
  }
}


Улучшения типизации

Проблемы:
Если метод возвращает не void, то нужно писать тупой код, который что-то возвращает.
Потом, мы не хотим вызывать этот код на стороне сервера, ведь он бессмыслен для него.
Ну и наконец у нас предупреждение о неиспользуемых переменных.

Итого надо писать что-то вроде:
[JSApi]
class window
{
  public static alert(s : string) : void
  {
    IgnoreParams();
    throw ClientCodeCalledInServerException();
  }
}


Но можно упростить еще одним макросом JSApiImpl
[JSApiImpl]
module window
{
  public alert(s : string) : void;
}


Описываем методы, все остальное генерируется макросом.

JSApiRoot

Чтобы не захламлять глобальное пространство имен, однако иметь возможность все же генерировать JS в глобальном пространстве есть еще один макрос.
Используется следующим образом:
[assembly: JSApiRoot(“MyNamespace”)]

namespace MyNamespace
{
  module window
  {
    public alert(s : string) : void;
  }
}

...
F() : void
{
  MyNamespace.window.alert("abc"); // в JS будет: window.alert("abc");
}


Примеры ручного описания:
Типизация самих себя: nweb.js
Типизация 'undefined': undefined

Да, можно использовать обобщения в типизации

Генерация из файлов описания TypeScript

Ну а теперь феерическое завершение.
Отступление: парсеры я никогда не писал, однако это было несложно, что еще раз доказывает простоту и выразительность Nemerle.Peg.
Парсер создавался по спецификации и как оказалось в самом компиляторе TypeScript были баги, а все потому что они не пользовались генератором, а писали все вручную

У нас есть парсер для файлов описания JS (.d.ts): TSDeclarationParser.
По нему создаем AST и генерируем типы и переменные для JS.
Использовать совсем несложно:
[assembly: GenerateTypedJS(
  Root = "пространство имен куда все класть",
  Lib = "Пусть к lib.d.ts, опционален”,

  // Список кортежей
  // Первый элемент папка откуда читать файлы.
  // Второый элемент регулярное выражение, которое указывает, что нужно игнорировать
  Files = [ ("TS", @"TS\\(_infrastructure|i18next\\lib)\\") ]
)]

Вот и все
Всю работу делает макрос.

Примеры использования сгенерированной типизации:
TreeNode.n
        if(window.@event.ctrlKey)
          _ = window.open("http://www.rsdn.ru" + this.Href, "_blank", null, false);
        else
          MainPage.Instance.SelectNode(this);


jscall и jsnew

Так уж получилось, что в JS система типов отличается от Nemerle.
Скажем в JS можно писать такой код:
function F(a) {
  if(this == window)
    if(typeof a === "number" ) return 1;
    else return "a";
  else {
   this.X = 1;
  }
}


С одной стороны есть конструктор, с другой стороны можно вызвать просто F(0) и F("") и получить разные типы !
Как это типизировать ?

Чтобы покрыть этот случай используются специальные атрибуты и макросы.
Вот примерный код для типизации этого JS.
[JSApiImpl]
public interface F_X
{
  public X : int { get; set; }
}

[JSApiImpl]
public interface F_Type
{
  [JSCall]
  public static @_(a : int) : int;

  [JSCall]
  public static @_(a : object) : String;

  [JSNew]
  public static @_() : F_X;

  [JSNew]
  public static @_(a : object) : F_X;
}

[assembly: JSApiRoot("M")]

module M
{
  public F : F_Type;
}

Использование
using M;

[Unit]
class MyUnit
{
  public X() : void
  {
    def a = jscall F(1);   // var a = F(1);
    def b = jscall F("x"); // var b = F(x);
    def c = jsnew F();     // var c = new F();
    def d = jsnew F(1);    // var d = new F(1);
  }
}


Заметки

  • Проект Nemerle.TypedJS предоставляет нужные библиотеки в типизированном виде.
    Сейчас там не все возможные библиотеки, но список пополняется по мере надобностей.

  • На данный момент парсер не поддерживает TypeScript 0.9 с генериками, постараемся доделать, чтобы не отставать от моды.

  • Теоретически можно убрать множество jsnew, jscall при генерации кода из TypeScript-а.
    Ведь большинство людей не пишут такой вычурный код.
    Но на это рук не хватает.

  • В TypeScript присутствует структурная типизация, которой конечно нет в Nemerle.
    Очевидно нужно для одинаковых структурно типов генерировать одно и тоже имя, чтобы сделать код похожим на TypeScript.
    Возможно это будет сделано в будущем.

    Высказывайтесь, не стесняйтесь.

    25.06.13 23:21: Ветка выделена из темы Типизация JS — VladD2