Переклад на код С - це дуже усталена звичка. Оригінальний C з класами (і ранніми C ++ реалізаціями, тоді званими Cfront ) зробив це успішно. Деякі реалізації Lisp або Scheme роблять це, наприклад , Chicken Scheme , scheme48 , Bigloo . Деякі люди в перекладі Пролог до C . Так само з'явилися деякі версії Моцарта (і були спроби скласти байт-код Ocaml до C ). Система CAIA зі штучним інтелектом Дж. Пітрата також завантажується і генерує весь свій C код. Vala також перекладається на C для коду, пов'язаного з GTK. Книга Квінека Ліп у невеликих шматочках мати деякий розділ про переклад C.
Одне з питань при перекладі на C - це хвостово-рекурсивні дзвінки . Стандарт C не гарантує, що компілятор C перекладає їх належним чином (до "стрибка з аргументами", тобто без їжі стека викликів), навіть якщо в деяких випадках останні версії GCC (або Clang / LLVM) роблять цю оптимізацію .
Ще одне питання - вивезення сміття . Декілька реалізацій просто використовують консервативний сміттєзбірник Boehm (який сприятливий для C ...). Якщо ви хотіли зібрати код для збору сміття (як це роблять кілька реалізацій Lisp, наприклад SBCL), це може бути кошмаром (ви хотіли б dlclose
на Posix).
Ще одне питання стосується першокласного продовження та виклику / куб . Але розумні хитрощі можливі (дивіться всередині схеми з куркою). Доступ до стеку викликів може зажадати численних хитрощів (але див. Зворотній зв'язок GNU тощо). Ортогональна стійкість продовження (тобто стеків або ниток) буде важкою для C.
Поводження з винятками часто викликає спритні дзвінки в longjmp тощо.
Ви можете створити (у своєму випромінюваному коді С) відповідні #line
директиви. Це нудно і вимагає багато роботи (ви хочете, щоб, наприклад, створити gdb
простіший код, який легко відрегулювати).
Мій MELT Lispy мову домен специфічний (для настройки або розширень GCC ) переводяться на C ( на насправді поганий C ++ в даний час). У ньому є власне поколінне копіювальне сміття. (Можливо, вас зацікавить MPS Qish або Ravenbrook ). Насправді, покоління GC легше в машиногенерованому коді С, ніж у рукописному коді С (адже ви підганяєте генератор коду С для вашого бар'єру запису та обладнання GC).
Я не знаю жодної мовної реалізації, що перекладається на справжній код C ++, тобто використовуючи деяку техніку "збирання сміття під час компіляції", щоб випромінювати код C ++, використовуючи багато шаблонів STL та поважаючи ідіому RAII . (скажіть, будь ласка, чи знаєте ви її).
Сьогодні смішно - це те, що (на сучасних робочих столах Linux) компілятори C можуть бути досить швидкими, щоб реалізувати інтерактивний цикл читання-друку верхнього рівня, перекладений на C: ви видаватимете код C (кілька сотень рядків) у кожного користувача Взаємодія, ви будете fork
компіляцією цього об'єкта в спільний об'єкт, який ви б тоді dlopen
. (MELT робить це все готово, і зазвичай це досить швидко). Все це може зайняти кілька десятих частин секунди і бути прийнятним для кінцевих користувачів.
Коли це можливо, я б рекомендував перекладати на C, а не на C ++, зокрема, тому що компіляція на C ++ відбувається повільно.
Якщо ви реалізуєте свою мову, ви можете також розглянути (замість того, щоб видавати код C) деякі бібліотеки JIT, такі як libjit , GNU lightning , asmjit або навіть LLVM або GCCJIT . Якщо ви хочете перевести на C, ви можете іноді використовувати tinycc : він дуже швидко збирає згенерований код C (навіть у пам'яті) для уповільнення машинного коду. Але в цілому ви хочете скористатися оптимізаціями, виконаними справжнім компілятором C, таким як GCC
Якщо ви перекладете на мову С, переконайтеся, що спочатку побудуйте весь AST згенерованого коду С у пам'яті (це також полегшить спочатку генерувати всі декларації, потім усі визначення та код функції). Ви могли б зробити деякі оптимізації / нормалізації таким чином. Також вас можуть зацікавити кілька розширень GCC (наприклад, обчислені готи). Ймовірно, ви хочете уникнути генерації величезних функцій C - наприклад, сто тисяч ліній згенерованих C - (вам краще розділити їх на більш дрібні шматки), оскільки оптимізація компіляторів C дуже незадоволена дуже великими функціями C (на практиці, і експериментально,gcc -O
час складання великих функцій пропорційний квадрату розміру коду функції). Отже, обмежте розмір створених функцій C декількома тисячами рядків кожен.
Зауважте, що і Clang (через LLVM ), і GCC (через libgccjit ) компілятори C & C ++ пропонують певний спосіб випромінювати деякі внутрішні представлення, що підходять для цих компіляторів, але це може бути (чи ні) складніше, ніж випускати код C (або C ++), і є специфічним для кожного компілятора.
Якщо ви розробляєте мову для перекладу на C, ви, мабуть, хочете мати кілька хитрощів (або конструкцій), щоб створити суміш C зі своєю мовою. Мій документ DSL2011 MELT : Мова для перекладеного домену, вбудований у компілятор GCC, повинен дати вам корисні підказки.