Обновить

Переписал ядро языка целиком. Ни один из 444 эталонов не сдвинулся

Уровень сложностиСложный
Время на прочтение10 мин
Охват и читатели6.6K
Всего голосов 4: ↑3 и ↓1+4
Комментарии4

Комментарии 4

Целочисленного типа — всё числа с плавающей точкой, как в JavaScript

Это не совсем правда. Можно вспомнить трюк из asm.js и приклеить позвать число как number|0 (или с любой иной побитовой операцией) и оно магическим образом превратится в полноценное целочисленное. Ну, и помимо этого есть штуки типа BigInt и Uint8Array. Ну и в wasm явно добавили экспозицию знаковых целых 32 и 64 бит.

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

Запуск скриптов довольно стрёмный. Почему не завести компилятор в тот же wasm, например? Потом написать собственный маленький рантайм и не зависеть непосредственно от ноды. То бишь есть ли планы на self-hosted/bootstrapped язык?

Спасибо, по первому пункту разберу подробно — там смешаны три разные вещи.

number|0 — это не целочисленный тип, а приведение к int32. И для языка с числами-double это шаг назад, а не вперёд: double даёт точные целые до 9007199254740991, а |0 — до 2147483647. На большем оно не «магически превращается», а ломается:

231 | 0 → -2147483648 (253 - 1) | 0 → -1

В asm.js |0 работал не как тип, а как аннотация для AOT-компилятора: разметка статически типизированного подмножества, чтобы движок скомпилировал его заранее. В динамическом интерпретаторе аннотировать нечего — значение всё равно останется боксированным, а диапазон сузится в четыре миллиона раз.

Uint8Array — это буфер байтов, а не скалярный числовов языке целые» отношения не имеет.

BigInt — единственный реальный кандидат, и я его расс, и вот чем платят:

Number: 20 мс BigInt: 110 мс → в 5,5 раза медленн 1n + 1 → TypeError: Cannot mix BigInt and other types 7n / 2n → 3n (а 7 / 2 = 3.5) JSON.stringify(1n) → TypeError

То есть это не «добавить тип», а решить заново, что в языке значит /, что происходит при смешивании, и как это сериализуетсцелочисленный тип у меня в открытых вопросах, а не в атье он честно назван отсутствующим, а не «почтиесть».
Про мотивацию — вопрос справедливый. Язык здесь не цель, а предмет: статья про систему проверок, которая позволила переписацеликом и не сдвинуть ни одного эталона. Но принцип уательный — ошибаться громко и рано, впротивоположность JavaScript:

  • выход за границы списка и отсутствующий ключ — ошибка, а не nil; - переполнение — ошибка: ни бесконечности, ни NaN в я

  • "сумма: " + 5 — ошибка, а не склейка; - у каждой ошибки позиция, строка исходника с кареткоа про опечатку. Молчаливый nil и тихий inf — источник багов, которые троен так, чтобы они всплывали в момент написания. Про запуск — согласен, зависимость от Node реальна. Эсборки, нет зависимостей в рантайме, весь тулчейн —node src/cli.ts file.sable. Проект в первую очередь читают, а не устанавливают, и здесь важнее, чтобы между исходником и застояло ничего. wasm со своим рантаймом — это другой проект: понадобира, либо ждать wasm-gc. Разумный путь, если цельсменится на «раздавать бинарник». Self-hosted — честно: пока нет. Чтобы компилятор написать на самом Sable, языку нужны вещи, которых у него нет на 0.2. Это классическая веха, и она интереснее wasm, но сначала работа со строками, иначе получится компилятор,который стыдно показать.

Попробовать без установки, если интересно: https://botiroff-d.github.io/sable/

В asm.js |0 работал не как тип, а как аннотация для AOT-компилятора:

Во-первых - для JIT, а не AOT: у AOT аннотация бы растворилась. Во-вторых оно намеренно сужало тип для экономии памяти. Т.к. оно компилировалось из C/C++ оно пыталось имитировать хотя бы сколько-нибудь их производительности, и на многих задачах даже выигрывала борьбу в производительности в сравнении с ваниальной имплементацией.

wasm со своим рантаймом — это другой проект: понадобира, либо ждать wasm-gc.

не знаю зачем вам wasm-gc. выглядит что вы могли бы делать немало статических проверок и жить без gc вовсе. Ну или пойти по пути blazor и сделайть несколько аллокаторов и собрать из них небольшой рантайм с GC. Хотя тут вопрос в первую очередь про модель языка, про которую вы толком ничего не рассказали. Ждём следующих статей

По asm.js вы правы, и я сказал неточно: в V8 AOT для asm.js не было никогда — там он шёл обычным JIT. AOT делал Firefox в OdinMonkey: модуль с “use asm” валидировался и компилировался до исполнения. Отсюда и разночтение.

А вот «у AOT аннотация бы растворилась» — это как раз то, что AOT с ней и делает. Валидатор по |0 доказывает, что значение помещается в int32, и генерирует целочисленную арифметику; сама аннотация в машинный код не попадает именно потому, что её потребили на этапе компиляции. Растворение — признак того, что она сработала, а не того, что её там не было.

И вы точно описали мотив: сужали намеренно, ради памяти и скорости, компилируя из C/C++. Это ровно моя мысль, только сказанная лучше — |0 был инструментом производительности в статически типизированном подмножестве, а не типом в языке. Поэтому перенести его в динамический интерпретатор нельзя: аннотировать нечего, а диапазон сузится.

Про GC вы правы, и это самый сильный пункт: модель языка я действительно не описал. Отвечу конкретно, потому что от неё всё и зависит.

Без сборщика Sable не обойдётся, и вот почему — три свойства, каждое из которых у него уже есть:

  1. Замыкание переживает кадр, в котором создано fn счётчик() { let n = 0; return fn() { n += 1; retur

  2. Взаимные ссылки — обычная структура, не экзотик struct Узел { имя, сосед = nil } a.сосед = b; b.сосед = a

  3. Цикл замыкается и через функцию h.дай = fn() { return h }

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

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

Путь Blazor вы назвали верно: собственный сборщик в леалистично и не требует ждать wasm-gc — просто этоотдельная работа размером с сам интерпретатор.

Про модель языка напишу отдельно — вопрос справедливый, и в статье про тесты ей места не нашлось. Спасибо за разбор, он оказался полезнее, чем половина того, что я закладывал в текст

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации