lumen

Dynamicznie typowany język skryptowy w Kotlinie. Źródło jest skanowane, parsowane do AST, kompilowane do bytecode'u i wykonywane na stosowej maszynie wirtualnej.

lumen
TL;DR

Dynamicznie typowany język skryptowy w Kotlinie, który naprawdę wykonuje kod: skanowanie i parsowanie do AST, kompilacja do bytecode'u i uruchomienie na własnej stosowej maszynie wirtualnej.

Wprowadzenie

lumen to dynamicznie typowany język skryptowy napisany w Kotlinie, który naprawdę wykonuje kod. Źródło jest skanowane, parsowane do AST, kompilowane do bytecode'u, a na końcu uruchamiane na własnej stosowej maszynie wirtualnej. Wygląda jak zwykły język skryptowy, ale pod spodem każda linijka przechodzi pełną drogę od tekstu do instrukcji maszyny.

Różnica między lumen a typowym ćwiczeniem z pisania języka leży w tym jednym słowie: bytecode. Łatwo zbudować interpreter, który chodzi po drzewie i liczy je na bieżąco. lumen idzie o krok dalej i kompiluje, co jest zarazem trudniejsze i o wiele ciekawsze.

Interpreter chodzący po drzewie składniowym jest prosty do napisania, ale szybko trafia na sufit wydajności. Każde wykonanie tej samej pętli oznacza ponowne przechodzenie tych samych węzłów drzewa, z całym narzutem, jaki to niesie. Dla małego skryptu to niewidoczne, dla czegokolwiek większego - odczuwalne.

lumen wybiera trudniejszą, ale lepszą drogę: zamiast wykonywać drzewo, kompiluje je do bytecode'u, czyli płaskiej listy prostych instrukcji. Te instrukcje chodzą potem na maszynie wirtualnej, która nie musi już nic rozumieć ze składni - dostaje gotowe rozkazy i po prostu je wykonuje.

Od tekstu do bytecode'u

Cała droga rozkłada się na cztery etapy. Skaner tnie tekst na tokeny, parser układa je w drzewo, kompilator zamienia drzewo w bytecode, a maszyna wirtualna wykonuje ten bytecode instrukcja po instrukcji. Wygląda to jak zwykły kod, ale każda linijka przechodzi tę samą pełną ścieżkę.

example.lm · javascript
fun fib(n) {
  if (n < 2) return n;
  return fib(n - 1) + fib(n - 2);
}

print(fib(10));
1
Skaner

tekst źródłowy staje się tokenami.

2
Parser

tokeny układają się w drzewo składniowe (AST).

3
Kompilator

drzewo zamienia się w bytecode.

4
Maszyna wirtualna

bytecode wykonuje się na stosie, instrukcja po instrukcji.

Maszyna stosowa od środka

Maszyna stosowa nie ma rejestrów jak prawdziwy procesor - operandy odkłada i zdejmuje ze stosu. Dodawanie sprowadza się do trzech ruchów: odłóż dwie liczby, zdejmij je i dodaj, odłóż wynik. Ten sam model napędza pod spodem między innymi Pythona i Javę, więc pisanie go samemu to zajrzenie w to, jak działają języki, których używa się na co dzień.

Dodawanie na maszynie stosowej

KrokInstrukcjaStos po operacji
1odłóż 22
2odłóż 32, 3
3dodaj5
i
Informacja

Maszyna stosowa działa bez rejestrów, a mimo to potrafi wykonać dowolny program. Cała arytmetyka i przekazywanie wartości odbywają się przez jeden stos - to zaskakująco proste, a zarazem dokładnie ten sam pomysł, który napędza duże języki produkcyjne.

Efekt: słowo "skompilowane" przestaje być abstrakcją

Na wyjściu jest język, który naprawdę wykonuje kod - nie interpretuje drzewa na bieżąco, tylko kompiluje je i uruchamia na własnej maszynie. To projekt, po którym słowo "skompilowane" przestaje być abstrakcją, bo samemu przeszło się całą drogę od znaku w pliku do instrukcji, która coś robi. Mały język, ale kompletny aż do samego wykonania.

Więcej projektów

Inne realizacje z tej samej kategorii - zobacz, jak podchodzimy do podobnych wyzwań.

Masz podobny projekt?

Napisz do nas - wycena jest bezpłatna i wraca w godzinę.