Как перестать беспокоиться и начать портировать

Пример портирования Java => JavaScript на примере токенизаторов из Lucene.

lucene-tokenizers.es6 | lucene-tokenizers.babel.js | Тесты | Исходники

TL;DR

Процесс можно условно разделить на несколько этапов:

  • Зависимости от внешних библиотек дожны быть представленны в виде исходников, а незадействованный код по возможности удалён т.к. размер таки имеет значение и чем меньше весит портированный скрипт тем он быстрее отработает в браузере

  • Точно так же системные классы среды исполнения Java, например Character.java, необходимо переопределить скопировав в проект

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

  • Собственно сама трансляция добытых Java исходников в ES6

  • Точечная адаптация - например реализовать логику метода System.arraycopy, который переопределили ранее, но уже для JavaScript

Как видно на демке и несложно догадаться по названию токенайзер UAX29URLEmailTokenizer чуть более православный по вебу. Оба StandardTokenizer и UAX29URLEmailTokenizer вполне себе дружат с юникодом.

Теперь чуть подробней о процессе портирования.

ESJava

Итоговая структура папок Java проекта (Eclipse):

~$ tree
.
├── src
│   ├── org
│   │   └── apache
│   │       └── lucene
│   │           └── analysis
│   │               ├── standard
│   │               │   ├── Character.java
│   │               │   ├── Exception.java
│   │               │   ├── IndexOutOfBoundsException.java
│   │               │   ├── IOException.java
│   │               │   ├── Reader.java
│   │               │   ├── StandardAnalyzer.java
│   │               │   ├── StandardTokenizerImpl.java
│   │               │   ├── StandardTokenizer.java
│   │               │   ├── StringReader.java
│   │               │   ├── System.java
│   │               │   ├── Tokenizer.java
│   │               │   ├── TokenModel.java
│   │               │   ├── UAX29URLEmailTokenizerImpl.java
│   │               │   └── UAX29URLEmailTokenizer.java
│   │               └── tokenattributes
│   │                   └── CharTermAttribute.java
│   └── Test.java
└── tests
    └── org
        └── apache
            └── lucene
                └── analysis
                    └── standard
                        ├── BaseTokenStreamTestCase.java
                        ├── Slow.java
                        ├── TestStandardAnalyzer.java
                        ├── TestUAX29URLEmailTokenizer.java
                        ├── TestUtil.java
                        └── WordBreakTestUnicode_6_3_0.java

13 directories, 22 files

Для удобства работы с исходниками среды выполнения Java:

~$ apt-get install openjdk-7-source

И так выглядит один из переопределённых в проекте системных файлов:

src/org/apache/lucene/analysis/standardSystem.java

public final class System {
  public static void arraycopy(Object s, int sp, Object d, int dp, int l) {
    // :es6:
    // int[] elements_to_add = s.slice(sp, sp + l);
    // Array.prototype.splice.apply(d, new int[] {dp,
    // elements_to_add.length}.concat(elements_to_add));
    java.lang.System.arraycopy(s, sp, d, dp, l);
    // :end:
  }
}

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

merge.sh

# find -name '*.java' | grep './src/org/apache/lucene/analysis/'
cat \
./src/org/apache/lucene/analysis/standard/Exception.java \
./src/org/apache/lucene/analysis/standard/IOException.java \
./src/org/apache/lucene/analysis/standard/IndexOutOfBoundsException.java \
./src/org/apache/lucene/analysis/standard/Reader.java \
./src/org/apache/lucene/analysis/standard/StringReader.java \
./src/org/apache/lucene/analysis/standard/Character.java \
./src/org/apache/lucene/analysis/tokenattributes/CharTermAttribute.java \
./src/org/apache/lucene/analysis/standard/TokenModel.java \
./src/org/apache/lucene/analysis/standard/Tokenizer.java \
./src/org/apache/lucene/analysis/standard/System.java \
./src/org/apache/lucene/analysis/standard/StandardAnalyzer.java \
./src/org/apache/lucene/analysis/standard/StandardTokenizerImpl.java \
./src/org/apache/lucene/analysis/standard/StandardTokenizer.java \
./src/org/apache/lucene/analysis/standard/UAX29URLEmailTokenizerImpl.java \
./src/org/apache/lucene/analysis/standard/UAX29URLEmailTokenizer.java \
| sed '/^package\s/d' | sed '/^import\s/d' \
> lucene-tokenizers.java

И просмотреть список точечных адаптаций для JavaScript:

~$ awk '/\:es6\:/,/\:end\:/' lucene-tokenizers.java

Трансляция:

~$ npm install -g esjava
~$ alias esjava10000="node --stack-size=10000 `which esjava`"
~$ esjava10000 lucene-tokenizers.java > out/lucene-tokenizers.es6

Осталось указать на export классы StringReader, StandardTokenizer, UAX29URLEmailTokenizer и добавить поддержку старых JavaScript движков:

out/js.sh

ES6FILE='lucene-tokenizers.es6'

for cls in 'StringReader' 'StandardTokenizer' 'UAX29URLEmailTokenizer'; do
  sed -i "s/^class\s\+${cls}\s\+/export class ${cls} /" ${ES6FILE}
done

sed 's/\\u/\\\\u/g' "$ES6FILE" |                     \
node --stack-size=10000                              \
"`which babel`"                                      \
--compact=false                                      \
--presets es2015                                     \
--plugins transform-es2015-modules-umd               \
--module-id luceneTokenizers |                       \
sed 's/\\\\u/\\u/g' > lucene-tokenizers.babel.js

Скрипт на выходе lucene-tokenizers.babel.js крутится в демке.

links

social