LINUX.ORG.RU

Язык для трансляции на другие языки

 transpiler


0

2

Есть ряд алгоритмов над массивами float64 чисел. Эти алгоритмы нужно применять в нескольких проектах на разных языках: JS, Go, Java. Копипастить не очень хочется. Также очень не хочется иметь нетривиальный рантайм.

Пока текущий вариант: написать алгоритмы на С. Для JS скомпилить в wasm, для остального штатными средствами.

В идеале хотелось бы иметь некий язык, с которого кто-то написал трансляторы на вышеперечисленные.

Нет ли такого в природе? Как искать не придумал.

★★★★
Haxe предназначен для транспилирования во Flash, JavaScript и Neko.
Со временем Haxe разросся до набора инструментов, поддерживающих транспиляцию на разные языки иплатформы, включая
JavaScript, C++, C#, Java, JVM, Python, Lua, PHP и Flash.

https://ifreeapps.ru/programma/18/kak-perevesti-programmu-s-odnogo-jazyka Как перевести программу с одного языка программирования на другой

Смотрел исходники Haxe.
В нём транспилирование безусловно имеется, но поддержано лишь как бы это сказать «типичное».
Может быть вас оно вполне устроит.

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 6)

Пока текущий вариант: написать алгоритмы на С. Для JS скомпилить в wasm, для остального штатными средствами.

только так. То есть реализовать библиотекой на C/CPP. Из всех остальных доступ - именно как к библиотеке, со своими обёртками. Про ЯвкийСкрипт: в node.js можно обойтись без wasm, а броузерный фронт может наверное пока и подождать ;-)

В идеале хотелось бы иметь некий язык, с которого кто-то написал трансляторы на вышеперечисленные.

это идеал секса мозго-мазохиста :-) вместо одной отлаженной вещи получите 3-4 слабосвязанные кривулины, и каждый чих придётся править в нескольких местах туда-и-обратно

трансятор в данном случае это худшее что вы можете для себя придумать.

PS/ трасляторы используется если у вас есть 100500 проектов на старом языке А и вы хотите полностью перейти на модно-молодёжный Б. Вот там да

MKuznetsov ★★★★★
()
Последнее исправление: MKuznetsov (всего исправлений: 1)

Как вариант, можно написать интерпретатор байт-кодов. Хорошо по теме написано в этой статье.

Я ради интереса делал подобный интерпретатор, но не на стеке или регистрах, а на словаре переменных. Например, для вывода 10 значений факториала код получался следующий:

var res 1
var n 0

@main
    print n
    print @->
    call @factorial
    print res
    endl
    + n 1
    jump_not n 11 @main
    return

@factorial
    jump_not n @n_is_zero
    var i n
    = res 1
@repeat
    * res i
    - i 1
    jump_if i @repeat
    return
@n_is_zero
    = res 1
    return

В каждой строке команда и имена переменных, либо литералы. После собачки идёт строка (без пробелов и экранированных символов для простоты парсинга). Строки используются и для меток, на которые прыгаем, как в ассемблере. Отступы тут только для красоты. В принципе, не сильно многословнее получается, чем на нормальных языках.

Фибоначчи (вывод первых десяти):

var res 1
var n 1

@main
    print n
    print @->
    call @fibonacci
    print res
    endl
    + n 1
    jump_not n 11 @main
    return
    
@fibonacci
    var x 1
    = res 0
    var i 0
@repeat
    var temp res
    = res x
    + x temp
    + i 1
    jump_not i n @repeat
    return

Интерпретатор пишется за несколько дней. Или даже быстрее, если не проверять типы перед запуском.

Kogrom
()
Ответ на: комментарий от Kogrom

Интерпретатор пишется за несколько дней.

Можно за один написать Forth на ассемблере в 500 строк, на высокоуровневом языке наверное еще быстрее выйдет.

Фибоначи

: fib for dup rot + dup . next ;
0 1 10 fib
1 2 3 5 8 13 21 34 55 89  ok

Факториал

: fac 1 swap for i * next . ;
17 fac
355687428096000  ok

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 4)
Ответ на: комментарий от Kogrom

Так придётся этот интерпретатор потом реализовывать на всех целевых ЯП?

Такие штуки хороши когда арифметическое выражение задаётся в рантайме, у ТС вроде другая задача.

AntonI ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Можно за один написать Forth на ассемблере в 500 строк

Ну не за один и не на ассемблере. На Питоне можно в 500 строк уложиться. На ассемблере, вероятно, только какая-то часть влезет.

Forth прикольный язык для маленьких задач. А если есть десяток переменных, то их туда-сюда замучаешься по стеку передвигать. Удобнее всё-таки произвольный доступ.

Kogrom
()
Ответ на: комментарий от AntonI

Так придётся этот интерпретатор потом реализовывать на всех целевых ЯП?

Да. Я делал транслятор своего языка в C++. Теоретически, не сложно его переделать в транслятор C#, возможно, в Java. Но с JavaScript и Go могут возникнуть проблемы (например, ООП другое или его нет, отсутствие шаблонов, перегрузки функций и т.д.). Интерпретатор - более универсальное решение.

С другой стороны, если сделать очень примитивный язык, то может и получится транслятор. С переходом по метке будут проблемы. Проще использовать бесконечный цикл с брейком по условию (так как конечные циклы в языках могут различаться). Будет типа такого:

proc inc_all data:array
    var counter 0
    var endIndex 0
    size endIndex data
    - endIndex 1
    loop
        if counter endIndex
            break
        end
        var value 0
        get value data counter
        + value 1
        set data counter value
        + counter 1
    end
end

proc fact data:array n:float
    var result 0
    get res 
    = result 1
    var counter n
    loop
        * result counter
        - counter 1
        if counter -1
            break
        end
    end
    set data 0 value
 end

Первая функция прибавляет по единице элементам массива, раз уж автору темы нужны массивы. Во втором для единообразия результат тоже в массив положил. Тем более, так будет проще с JavaScript.

Понятно, что всё то извращение и правильно просто подключать сишную библиотеку. Но мы не ищем лёгких путей.

Kogrom
()
Ответ на: комментарий от Kogrom

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

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

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

В обратной польской нотации

Этого у меня нет. Тут формат ещё тупее, в каждой строке просто пишется три токена, почти как в ассемблере:

<команда> <переменная> <константа/ы>

при этом переменной или констант может не быть.

Это разновидность «промежуточного представления». Вроде даже как-то она называется, но ссылку не могу найти.

Kogrom
()

Пока текущий вариант: написать алгоритмы на С …

Затем сделать это сетевой службой, к которой обращаться ХТТП-запросом и получать результат вычислений в ХТТП-ответе из проектов на Джаваскрипте, Гоу и Джавы.

Enthusiast ★★★
()
Ответ на: комментарий от bo4ok

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

vbr ★★★★
() автор топика
Ответ на: комментарий от Kogrom

Мне кажется неудачный вариант. Обратная запись позволяет уйти от скобок, стековые машины очень простые и позволяют поддерживать довольно сложные конструкции.

AntonI ★★★★★
()
Ответ на: комментарий от Kogrom

Ну не за один и не на ассемблере. На Питоне можно в 500 строк уложиться. На ассемблере, вероятно, только какая-то часть влезет.

Ну я описываю то что сделал я, эти примеры как раз мой компилятор/интерпретатор выполняет, у меня немного другая логика в for next, отличная от ANS. Ну и совместимость с ANS не нужна, половина слов бесполезные, другие хочется сделать по другому, и словарь можно заменить на dq(2) для имени + пару бит на флаги, и еще один dq(1) для адреса, не знаю зачем нужны переключаемые словари, их можно не делать, итд.

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

Forth прикольный язык для маленьких задач. А если есть десяток переменных, то их туда-сюда замучаешься по стеку передвигать. Удобнее всё-таки произвольный доступ.

В Forth есть переменные, но наверное все же речь про большое количество значений на стеке, так вот, их не должно быть больше ~16, а значений с которыми работают слова в текущий момент, не должно быть больше 3-4, как так сделать советов много но универсального нету, например если мы работаем с математикой то хорошо бы завести отдельный стек для векторов что бы они не мешали обычным данным, к тому же операции со стеком векторов можно отдельно написать оптимизированными, и получится даже удобнее, ну и устройство x87 так и просит вынести float-числа в отдельный стек, хотя тут спорно, стоит ли его использовать. Ну и слова типа PICK лучше вообще не добавлять.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 7)
Ответ на: комментарий от AntonI

Обратная запись позволяет уйти от скобок

Позволяет. Кроме того, ей не нужны приоритеты. Только она непривычная и заставляет напрягаться при составлении длинного выражения, переводить взгляд вперёд назад и обратно. Рассмотрим примеры из Википедии.

  1. Обычная запись: a = 7 − 2 * 3. Дошли до умножения, вернулись к двойке, дошли до конца, вернулись к семёрке, вычли, держа шестёрку в уме. Кошмар.
  2. Обратная польская: a = 7 2 3 * −. Дошли до умножения, вернулись к двойке, дошли до минуса, вернулись к семёрке и держим шестёрку в уме.
  3. Альтернатива: a = 3 * 2 * -1 + 7. Тут вычисления идут строго слева направо, просто изменяя переменную. Легко понять, легко транслировать. Минусом является необходимость в дополнительных явных переменных для длинных формул. Альтернатива - это то, что я приводил выше, плюс чейнинг.

Но к нашей теме это не имеет отношения. Автор может тупо использовать привычную запись из пункта 1. Потому что JS, Go, Java очень близкие языки и такие выражения можно переносить напрямую без анализа и изменения. Различаться же будут заголовки функций, циклов и создание переменных. Их и надо помечать для анализа и преобразования.

Kogrom
()
Ответ на: комментарий от MOPKOBKA

Я тут нашёл тетрис на Forth-е. Так что в принципе он годится и для средних задач. Но очень непривычно, конечно.

Есть ещё одна область, которую не могут осилить нормальные языки - программирование на смартфоне. Вроде бы Forth для этого годится лучше - нашёл пару интерпретаторов в Google Store. Хотя больше они на игрушки похожи.

Kogrom
()
Ответ на: комментарий от Kogrom

А не надо напрягаться - понятно что обратная нотация для компьютера а не для человека. Человеку удобно писать в синтаксисе питона например, а уже это тривиально транслируется питоном же в обратную нотацию.

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

понятно что обратная нотация для компьютера а не для человека

Нет. Для компьютера - ассемблер. Всё остальное - для человека. И обратная польская нотация, и какая угодно.

Привычные математические выражения с приоритетами и скобками кажутся удобными, потому что мы к ним привыкли с детства. Но на самом деле удобства там нет, потому что надо пройтись слева направо и назад несколько раз, чтобы вычислить. Компьютер в школе не учился, поэтому для него мы эти выражения преобразуем в набор трёхадресных кодов:

x = (-b + sqrt(b^2 - 4*a*c)) / (2*a)

преобразуется в:

t1 := b * b
t2 := 4 * a
t3 := t2 * c
t4 := t1 - t3
t5 := sqrt(t4)
t6 := 0 - b
t7 := t5 + t6
t8 := 2 * a
t9 := t7 / t8
x := t9
Kogrom
()
Ответ на: комментарий от Kogrom

Нет, для копмпьютера не только ассемблер (если конечно нет желания писать свой компилятор иди дёргать gcc на каждый чих в рантайме).

Есть целый класс задач, когда юзер в рантайме задаёт достаточно произвольное арифметическое выражение, и это выражение надо быстро вычислить много раз. Естественно юзер задает это выражение в своей привычной нотации, ему так удобно. А компьютер проще всего научить читать и считать такое выражение из байткода в обратной польской нотации. Что бы перевести одно в другое достаточно просто использовать питон.

На питоне это порядка 100 строк кода, байт машина на сях занимает десятки строк кода. То что Вы предлагаете очевидно будет толще и медленнее работать (больше ветвлений).

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от AntonI

То что Вы предлагаете очевидно будет толще и медленнее работать (больше ветвлений).

ровно до наоборот..стековая машина медленнее и хуже переводится в целевой asm. Внутренние машины всякой скриптоты и интерпретаторов стремительно смигрировали с а-ля форт именно в подобие 3-х адресных.

MKuznetsov ★★★★★
()
Ответ на: комментарий от AntonI

Как я понял, речь идёт о том, какая машина лучше: стековая или регистровая. По ссылке на статью, которую я давал в первом сообщении пишут:

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

Как уже упоминалось выше, у этой эффективности есть вполне осязаемая цена: компилятор должен сам аллоцировать регистры и дополнительно желательно наличие развитого оптимизатора.

Как я понимаю, наивная регистровая машина всё-таки может быть медленнее стековой. В данном случае Вы будете правы. У меня же машина не на стеке и не на регистрах, а на словаре. То есть ещё медленнее. Но зато реализация и код простые.

Kogrom
()
Ответ на: комментарий от Kogrom

Вот консольный Pong, в 186 строк, без определения системных констант и сисколов будет где то 150 строк. Есть простой AI, работает на любом разрешении экрана, можно выбрать пункт игры без AI с другом. Игра идет до 10, потом показывает кто выиграл.

0x5413 constant TIOCGWINSZ
0x5401 constant TCGETS  
0x5404 constant TCSETSF
10 constant ECHO
1 constant POOLIN
: ioctl 3 16 syscall ;
: nanosleep 2 35 syscall ;
: pool 3 7 syscall ;
: 1ms 0 pad ! 1000000 pad cell+ ! pad 0 nanosleep drop ;
: ms for 1ms next ;
: -1/1 -? if 1 else -1 then ;
: xxyy ( x y x y -- x x y y ) rot swap ;

here constant termio 64 allot
1 TCGETS termio ioctl drop 
termio 12 + dup l@ ECHO not and swap l!
1 TCSETSF termio ioctl drop 
27 emit ." [?25l"
27 emit ." [H" 27 emit ." [J"

: quit 
  27 emit ." [?25h"
  termio 12 + dup l@ ECHO or swap l!
  1 TCSETSF termio ioctl drop 
  bye ;

1 TIOCGWINSZ pad ioctl drop
pad w@         constant height 
pad word+ w@   constant width
width height * constant size 
here           constant screen size allot
0   constant  MENU
1   constant  COMPUTER
2   constant  FRIEND
3   constant  WIN
0   variable  mode
0   variable  cursor
9   variable  player1
9   variable  player2
0   variable  score1
0   variable  score2
0 0 variable2 velocity
0 0 variable2 ball

: new-round
  score1 @ 10 >= 
  score2 @ 10 >= 
  or if WIN mode ! then
  score1 @ score2 @ > if -1 else 1 then 
  -1 velocity !2 
  width 2 / height 2 / ball !2 ;

: new-game
  0 score1 !
  0 score2 !
  new-round ;

: target ( -- x y ) ball @2 velocity @2 xxyy + -rot + swap ;  
: flight target ball !2 ;
: collision? ( y -- f ) dup height >= swap 0 < or ;
: rebound ( -- ) velocity @2 negate velocity !2 ;

: walls 
  target swap drop 
  collision? if rebound then ;

: racket-x? ( x px -- f ) if width >= else 0 <= then ;
: racket-y? ( y py -- f ) 2 + - abs 2 <= ;
: racket? ( x y px py -- f ) xxyy racket-y? -rot racket-x? and ;
: power* ( n -- n ) player1 @ player2 @ + 4 mod 1 max * -3 max 3 min ;
: hit velocity @2 swap power* negate swap velocity !2 ;

: players 
  target 0 player1 @ racket? 
  target 1 player2 @ racket? 
  or if hit then ; 
  
: check-goal 
  target drop dup 
  width >= if drop 1 score1 !+ new-round leave then 
  0 <= if 1 score2 !+ new-round then ;

: physics walls players check-goal flight ;

: key ( -- n )
  0 pad
  pad long+ POOLIN swap w!
  pad 1 0 pool drop
  pad long+ word+ w@ POOLIN and if 
    0 pad 100 accept drop pad c@
  else 0 then ;

: moving ( n player -- ) dup @ rot + 0 max height 4 - min swap ! ;

: input 
  key 
  [char] q case -3 player1 moving end
  [char] a case 3 player1 moving end
  [char] p case -3 player2 moving end
  [char] l case 3 player2 moving end
  [char] e case quit end
  drop ;

: visible? 
  velocity @ 0 >
  ball @ width 2 / > and 
  mode @ COMPUTER = and ;
  
: ai 
  visible? if 
    ball cell+ @ player2 @ - negate 
    -1/1 player2 moving 
  then ;

: clear screen size 32 fill ;
: flush screen size .s ;
: point ( x y c -- ) -rot width * + screen + c! ;
: draw-ball ball @2 [char] @ point ;

: draw-player ( x y -- ) 
  4 for 
    dup2 [char] # point 1+ 
  next drop2 ;

: draw-players 
  0 player1 @ draw-player 
  width 1- player2 @ draw-player ;

: draw-border 
  width 2 / 0 height for 
    dup2 [char] | point 1+ 
  next drop2 ;

: draw-score ( score x -- ) 
  3 width * + screen + swap <# #u #> rot swap move ;

: draw-scores 
  score1 @ width 2 / 7 - draw-score 
  score2 @ width 2 / 7 + draw-score ;

: draw draw-border draw-scores draw-ball draw-players ;

: label ( str u )
  dup 2 / width 2 / swap -
  cursor @ width * + screen + swap move
  1 cursor !+ ;

: win 
  height 2 / 4 - cursor !
  score1 @ score2 @ > if
    " Player1 WIN!"
  else
    " Player2 WIN!"
  then
  label
  " Press C to continue" label
  key 
  [char] c case 0 mode ! end
  [char] e case quit end
  drop ;

: menu 
  height 2 / 4 - cursor !
  " PONG" label
  " Q - Player 1 Up, A - Player 1 Down" label
  " P - Player 2 Up, L - Player 2 Down" label
  " E - Exit" label
  " " label
  " " label
  " 1. Play with computer" label
  " 2. Play with friend" label
  key 
  [char] 1 case new-game COMPUTER mode ! end
  [char] 2 case new-game FRIEND mode ! end
  [char] e case quit end
  drop ;
 
: game input ai physics draw ;

: pong 
  @:> clear mode @
    dup MENU = if menu then
    dup dup COMPUTER = swap FRIEND = or if game then
    WIN = if win then
    60 ms
  flush <@ ; pong

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от Kogrom

Еще пример на Forth. Мне обратная польская запись удобнее, если есть стек, нравится работать особенно с ней в режиме консоли, результат всегда сохраняется, причем все результаты, и их можно всегда увидеть.

: + parse-name eval + ;
: - parse-name eval - ;
: * parse-name eval * ;
: / parse-name eval / ;
: ( @:> parse-name dup2 " )" compare invert if drop2 unloop then eval <@ ;
: print ['] . >r@ ;
: ; r@> execute ;

print 10 + 20 * 30 / 5 ;      
print 2 + ( 2 * 4 ) ; 

MOPKOBKA ★★★★★
()
Ответ на: комментарий от Kogrom

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

Да и оптимизации возможны далеко не всегда, это от выражения зависит. Скажем выражение a*(b+c) - что тут оптимизировать? В любом случае надо заводить временную переменную под результат b+c.

На самом деле это все неважно, важно какой из этих подходов лучше ляжет на конвейер (что лучше воспримет предсказатель ветвлений). Этого я не знаю, боюсь что оба хуже.

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от Kogrom

Но вообще использование «регистров» открывает некоторые новые возможности, я всерьёз в ту сторону не думал.

Спасибо!

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)