Przejdź do treści

Elementy leksykalne

Lekser rozpoznaje tokeny, parser składa je w konstrukcje, a budowanie AST nadaje literałom reprezentację. Są to odrębne etapy: poprawny token nie gwarantuje poprawnej konwersji wartości ani działania wygenerowanego programu. Pełne definicje znajdują się w gramatyce.

Identyfikatory i słowa kluczowe

L01 — nazwy

IDENTIFIER ma postać [a-zA-Z_][a-zA-Z0-9_]*: litery ASCII, cyfry i podkreślenie, bez cyfry na początku. Nazwy z polskimi literami nie należą do tego wzorca. Samotne _ jest wcześniejszym, odrębnym tokenem UNDERSCORE; nie jest zwykłą nazwą zmiennej. _value może być identyfikatorem.

Tokeny słów kluczowych rozróżniają wielkość liter: task i Task nie są tym samym tokenem, a Int i int nie są alternatywną pisownią tokenu INT_TYPE. Odrębne zachowanie porównywania nazw w analizatorze opisano w typach.

L02 — dopasowanie tokenów i kontekst

Lekser wybiera najdłuższe pasujące dopasowanie; przy równej długości rozstrzyga kolejność reguł. Dlatego słowo kluczowe nie dzieli automatycznie dłuższej nazwy, a _ przy równym dopasowaniu otrzymuje własny token. Dłuższe operatory, np. ==, -> i ::, są pojedynczymi tokenami.

Nie każde słowo z własnym tokenem jest zakazane we wszystkich pozycjach nazwy. Reguła anyIdentifier dopuszcza wybrane tokeny typów i storage:

anyIdentifier
    : IDENTIFIER
    | INT_TYPE | FLOAT_TYPE | BOOL_TYPE | TEXT_TYPE | SYMBOL_TYPE | TIME_TYPE | DURATION_TYPE
    | STORAGE | ENTITY | QUERY | FILTER | PROJECT | SORT | LIMIT | OFFSET | KEY | ASC | DESC
    | MUTATION | INSERT | UPDATE | DELETE | SINGLE | MANY | SET | WHERE
    ;

Dotyczy to wyłącznie miejsc używających tej reguły. Deklaracja wymagająca IDENTIFIER nie przyjmuje automatycznie całego zbioru anyIdentifier. Nazwy rest, grpc, mqtt nie są osobnymi tokenami protokołu.

Literały

literal
    : HEX_LITERAL
    | INTEGER_LITERAL
    | FLOAT_LITERAL
    | STRING_LITERAL
    | RAW_STRING
    | TRIPLE_STRING
    | DURATION_LITERAL
    | MEMORY_UNIT_LITERAL
    | TRUE
    | FALSE
    ;

L03 — liczby

Token Dokładne ograniczenia leksykalne
INTEGER_LITERAL Cyfry dziesiętne; podkreślenia tylko pomiędzy pierwszą i ostatnią cyfrą. Wzorzec dopuszcza również kilka kolejnych podkreśleń.
HEX_LITERAL Prefiks 0x albo 0X, co najmniej jedna cyfra szesnastkowa; wewnętrzne podkreślenia są dopuszczone przez wzorzec.
FLOAT_LITERAL Co najmniej jedna cyfra po obu stronach kropki; opcjonalny wykładnik e/E ze znakiem i cyframi. Bez podkreśleń.

Znak minus jest osobnym operatorem: -7 nie jest jednym tokenem liczbowym. .5, 1. i 1e3 nie są pojedynczym tokenem FLOAT_LITERAL; poprawną postacią wykładniczą jest np. 1.0e3. Nie oznacza to, że każdy ciąg nierozpoznany jako jeden literał musi zostać odrzucony jako cały program: może zostać podzielony na kilka tokenów i konstrukcji.

L04 — czas i pamięć

DURATION_LITERAL to cyfry bez znaku bezpośrednio połączone z jedną z jednostek ns, us, ms, s, m, h, d. MEMORY_UNIT_LITERAL używa jednostek KB, MB, GB, TB, PB, z podaną wielkością liter. Te tokeny nie przyjmują ułamka ani podkreśleń w liczbie.

5ms jest jednym tokenem; 5 ms nim nie jest. Próba całego programu z 5 ms przeszła check, ale nie dowodzi to równoważności tych zapisów — parser mógł przyjąć osobne wyrażenia. Jednostka nie ustanawia dokładności zegara, planowania wątków, sposobu przydziału pamięci ani jej trwałości.

L05 — teksty

Postać Reguła leksykalna
"tekst" lub 'tekst' Zwykły tekst; bez niepoprzedzonego ukośnikiem własnego separatora, ukośnika odwrotnego i surowego CR/LF. Sekwencja \ oraz kolejny znak jest dopuszczona szeroko.
"""tekst""" Tekst do pierwszego kolejnego potrójnego separatora; dopuszcza nowe wiersze.
@"tekst" Tekst surowy; podwójne "" jest dopuszczone wewnątrz tokenu. Wzorzec dopuszcza nowe wiersze.

Dopuszczenie sekwencji po ukośniku nie jest listą semantycznie obsługiwanych escape'ów. "a\q" przechodzi próbę check; nie ustanawia to przenośnego znaczenia \q. Token Symbol istnieje jako nazwa typu, ale :ok nie należy do reguły literal. Null również nie ma tutaj odrębnego tokenu literału.

Białe znaki, komentarze i granice

L06 — pomijane tokeny

Lekser pomija spację, tabulator, CR i LF oraz komentarze:

  • // do CR/LF albo końca wejścia;
  • /* do pierwszego */, bez reguły zagnieżdżania komentarzy blokowych.

Komentarz może oddzielać tokeny, ale nie zastępuje wymaganego wyrażenia. Nowy wiersz nie jest samodzielnym tokenem kończącym instrukcję. Średnik jest opcjonalny tylko tam, gdzie odpowiednia reguła ma SEMICOLON?. Lekser nie ma w tej regule białych znaków ogólnego zbioru separatorów Unicode.

Rozpoznanie tokenu a wartość AST

L07 — obecne ograniczenia konwersji

AstBuilder.BuildLiteral zachowuje również surowy zapis, lecz buduje wartość następującymi ścieżkami:

  • liczba dziesiętna: long.TryParse; przy niepowodzeniu wartością pozostaje tekst;
  • liczba szesnastkowa: Convert.ToInt64(raw, 16);
  • liczba zmiennoprzecinkowa: double.TryParse z kulturą niezmienną;
  • zwykły i potrójny tekst: przycinanie znaków cudzysłowu;
  • tekst surowy: usuwanie początkowego @ i przycinanie cudzysłowów;
  • czas i pamięć: zachowanie surowego tekstu do dalszej interpretacji.

Nie ma w tych gałęziach pełnego dekodowania escape'ów, normalizacji pojedynczych apostrofów ani zamiany podwojonych cudzysłowów tekstu surowego na jeden znak. Nie należy utożsamiać leksykalnie dopuszczonego tekstu z poprawnie zdekodowaną wartością we wszystkich generatorach.

W próbach 1__000 przechodzi check, lecz bieżąca ścieżka long.TryParse nie usuwa podkreśleń. 0xF_F pasuje do wzorca tokenu, ale kończy się błędem P0002 podczas budowania AST. To luka między lekserem a konwersją, nie zakaz podkreśleń wynikający z gramatyki.

Sprawdzone przykłady

Pary są sprawdzane przez CLI w osobnych projektach. Potwierdzają wyniki sprawdzania źródła, nie wartości po wykonaniu programu.

Przykład poprawny

task Main() @server {
    let reading_1: Float = 21.0
}

Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.

Przykład błędny

task Main() @server { let 1reading: Float = 21.0 }

Powód: Nazwa zmiennej nie może zaczynać się cyfrą.

Kontrola: przykład odrzucony przez check; kody: P0001.

Pełny zakres weryfikacji przykładów.

Przykład: Samotne podkreślenie

Poprawny zapis

task Main() @server { let _value = 1 }

Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.

Błędny zapis

task Main() @server { let _ = 1 }

Powód: Samotne _ jest osobnym tokenem, nie identyfikatorem zmiennej.

Kontrola: przykład odrzucony przez check; kody: P0001.

Pełny zakres weryfikacji przykładów.

Przykład: Cyfry po obu stronach kropki

Poprawny zapis

task Main() @server { let value = 0.5 }

Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.

Błędny zapis

task Main() @server { let value = .5 }

Powód: FLOAT_LITERAL wymaga cyfr przed kropką i po niej.

Kontrola: przykład odrzucony przez check; kody: P0001.

Pełny zakres weryfikacji przykładów.

Przykład: Komentarze pomiędzy tokenami

Poprawny zapis

task /* opis */ Main() @server { let value = 1 // komentarz
 }

Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.

Błędny zapis

task /* opis */ Main() @server { let value = }

Powód: Komentarz jest pomijany, ale nie zastępuje wymaganego wyrażenia.

Kontrola: przykład odrzucony przez check; kody: P0001.

Pełny zakres weryfikacji przykładów.

Dodatkowe próby przyjęły 1.0e3, 5ms, 4MB, tekst surowy i wielowierszowy potrójny. Odrzucone zostały .5, nazwa z polską literą i samotne _ jako nazwa. Przyjęcie 1e3 w całym programie nie oznacza rozpoznania jednego literału float.

Jawne luki

  • Spójność separatorów cyfr z konwersją liczby oraz kontrolowane błędy zakresu.
  • Pełne dekodowanie zwykłych, surowych i potrójnych tekstów, escape'ów i separatorów.
  • Przenośne zakresy liczb oraz reprezentacja wartości po kolejnych etapach.
  • Jednoznaczne diagnostyki przypadkowo sąsiadujących wyrażeń, które autor mógł zamierzać jako jeden literał.
  • Kontrakt jednostek na poziomie wykonania, niezależny od rozpoznania tokenu.

Podstawa w repozytorium

  • grammar/JunexisLang.g4: tokeny literałów, operatorów, identyfikatorów, białych znaków i komentarzy oraz reguły literal i anyIdentifier.
  • compiler/JunexisLang.Compiler.Core/Parsing/AstBuilder.cs: BuildLiteral.
  • Przykłady tego rozdziału i próby graniczne weryfikowane rzeczywistym CLI.