Wyrażenia i instrukcje
Rozdział opisuje strukturę wyrażeń oraz zakres obecnej kontroli semantycznej. Poprawne parsowanie i brak diagnostyk nie dowodzą, że operandy mają poprawne typy ani że wygenerowany kod zachowa przenośne znaczenie.
Pierwszeństwo i łączenie operatorów
Od najsilniejszego do najsłabszego wiązania:
| Poziom | Zapis |
|---|---|
| Postfiksowy | indeks a[i], pole a.b, człon A::B, wywołanie f(...) |
| Unarny | -, not / !, & |
| Konwersja | as Typ |
| Mnożenie | *, /, % |
| Dodawanie | +, - |
| Relacje | <, >, <=, >= |
| Równość | ==, != |
| Koniunkcja | and / && |
| Alternatywa | or / || |
| Warunkowy | warunek ? a : b |
| Przypisanie | =, +=, -=, *=, /=, %= |
W01 — struktura drzewa
Nawiasy grupują wyrażenie. Poziomy binarne od mnożenia do alternatywy są
budowane przez AstBuilder.BuildLeftAssoc od lewej: a-b-c tworzy (a-b)-c.
Nie oznacza to przenośnego kontraktu kolejności wykonywania wszystkich efektów.
Powtarzane porównania są składniowo łączone na tym poziomie, nie ustanawiają
matematycznej koniunkcji w rodzaju a<b and b<c.
Operator unarny jest rekurencyjny; alternatywa po dwukropku operatora warunkowego
również jest rekurencyjna. Reguła przypisania dopuszcza najwyżej jeden operator
na tym poziomie. a=b=c zostało odrzucone, zamiast tworzyć domyślny łańcuch
przypisań prawostronnych. as występuje opcjonalnie raz na poziomie castExpression.
expression
: assignmentExpression
;
assignmentExpression
: conditionalExpression (compoundOperator conditionalExpression)?
;
compoundOperator
: ASSIGN
| PLUS_ASSIGN | MINUS_ASSIGN | MULT_ASSIGN | DIV_ASSIGN | MOD_ASSIGN
;
conditionalExpression
: logicalOrExpression (QUESTION expression COLON conditionalExpression)?
;
unaryExpression
: (MINUS | NOT | AMPERSAND) unaryExpression
| postfixExpression
;
castExpression
: unaryExpression (AS typeReference)?
;
postfixExpression
: primaryExpression
( LBRACKET expression RBRACKET // index
| DOT anyIdentifier // field access
| DOUBLE_COLON anyIdentifier // enum variant access
| LPAREN argumentList? RPAREN // function call
)*
;
primaryExpression
: literal
| anyIdentifier
| CONFIG // dostęp do bloku config: config.pole
| LPAREN expression RPAREN
| structLiteral
| arrayLiteral
| jsonLiteral
| communicationExpression
;
W02 — znaczenie nazw i konstrukcji postfiksowych
Gramatyka pozwala składać kolejne indeksy, pola, człony :: i wywołania.
Sama postać x(...) nie dowodzi, że x jest wywoływalne, a a.b nie dowodzi
istnienia pola. :: obsługuje syntaktycznie człony kwalifikowane, w tym warianty
enum i pewne wywołania przez alias modułu; nie daje pełnej kwalifikacji typów.
& jest parsowane jako operator unarny, ale nie ustanawia kompletnego kontraktu
pożyczania, własności ani czasu życia referencji. Konwersja as jest konstrukcją
składniową; przyjęcie 1 as Float nie dowodzi kompletnej walidacji konwersji.
Wywołania i wartości złożone
argumentList
: expression (COMMA expression)*
| namedArgument (COMMA namedArgument)*
;
namedArgument
: IDENTIFIER COLON expression
;
structLiteral
: IDENTIFIER LBRACE structFieldInit (COMMA structFieldInit)* COMMA? RBRACE
;
structFieldInit
: IDENTIFIER COLON expression
;
arrayLiteral
: LBRACKET (expression (COMMA expression)* COMMA?)? RBRACKET
;
jsonLiteral
: LBRACE (jsonField (COMMA jsonField)*)? RBRACE
;
jsonField
: STRING_LITERAL COLON expression
;
W03 — argumenty
Lista wywołania jest albo pozycyjna, albo nazwana; gramatyka nie dopuszcza
mieszania, np. Add(1,b:2). Nazwa argumentu w tej regule jest IDENTIFIER.
Końcowy przecinek nie jest przewidziany przez argumentList.
Rozpoznanie argumentów nazwanych nie stanowi pełnej gwarancji ich wiązania
z każdym rodzajem funkcji. Query i mutation storage mają osobne kontrole nazw,
liczby i typów. Nie przenosimy tych gwarancji na wszystkie zwykłe funkcje.
Próba zwykłego Twice("text") przy parametrze value: Int przeszła check.
W04 — literały złożone
Literał struktury wymaga co najmniej jednego przypisania pola i dopuszcza końcowy
przecinek. Literał listy w nawiasach kwadratowych może być pusty i również dopuszcza
końcowy przecinek. JSON ma klucze będące tokenami STRING_LITERAL, może być pusty,
ale jego reguła nie przewiduje końcowego przecinka.
Kontrola reprezentacji i dopuszczalnych typów nie wynika z samych klamer.
Obecne wnioskowanie listy wybiera pierwszy element o ustalonym typie i buduje
List<T>. Nie sprawdza w tej funkcji jednorodności całej listy.
Próba let value: List<Int> = [1,"text"] przeszła bez diagnostyk;
nie uznajemy przez to mieszanej listy za semantycznie poprawną.
Lokalne deklaracje i sterowanie
declarationStatement
: (LET | CONST | VAR) IDENTIFIER (COLON typeReference)? ASSIGN expression SEMICOLON?
;
returnStatement
: RETURN expression? SEMICOLON?
;
ifStatement
: IF expression LBRACE statement* RBRACE
(ELSE (ifStatement | LBRACE statement* RBRACE))?
;
forStatement
: FOR anyIdentifier IN expression LBRACE statement* RBRACE
;
matchStatement
: MATCH expression LBRACE matchArm+ RBRACE
;
matchArm
: matchPattern ARROW (LBRACE statement* RBRACE | expression) COMMA?
;
matchPattern
: UNDERSCORE // wildcard
| literal // literal pattern
| IDENTIFIER // binding
| IDENTIFIER DOUBLE_COLON anyIdentifier // enum variant
| IDENTIFIER DOUBLE_COLON anyIdentifier
LPAREN matchPattern (COMMA matchPattern)* RPAREN // enum variant with data
;
W05 — deklaracje
let, const i var wymagają nazwy oraz inicjalizatora; typ jest opcjonalny.
Są projektowane odpowiednio jako wiązanie niezmienne, stała i zmienna, ale
próba przypisania do const przeszła check bez diagnostyk. Nie przedstawiamy
więc niezmienności jako w pełni egzekwowanej własności obecnego analizatora.
Sprawdzanie jawnego typu let opisano w typach.
W06 — if, for i match
Gałąź if wymaga bloku w klamrach; else dopuszcza blok albo kolejne if.
Intencją jest wybór na podstawie warunku logicznego, lecz if 1 {} przechodzi
obecną próbę bez diagnostyk. Brak błędu nie jest regułą niejawnej konwersji do Bool.
for nazwa in wyrażenie ma ciało w klamrach. Iteracja po komunikacji ma
odrębne ograniczenia opisane w komunikacji; nie utożsamiamy
jej z iteracją po dowolnej kolekcji ani pojedynczym odbiorem wiadomości.
match jest instrukcją i wymaga co najmniej jednej gałęzi. Wzorce obejmują _,
literał, nazwę wiązania i wariant enum, również z danymi. Gałąź po -> zawiera
wyrażenie albo blok; przecinek jest opcjonalny. Wzorzec x: Int nie należy do
tej gramatyki. Sama składnia nie dowodzi kontroli wyczerpujących wzorców,
osiągalności gałęzi ani kompletnego zawężania unii.
Wnioskowanie typu nie zastępuje walidacji operatora
InferUnaryType nadaje Bool operatorowi not/!, a pozostałym operatorom unarnym
przekazuje typ operandu. InferBinaryType nadaje Bool porównaniom i logice;
dla innych operacji wybiera Float przy operandzie rozpoznanym jako Float,
a w pozostałych przypadkach dostępny typ lewej albo prawej strony.
Dla operatora warunkowego bierze zgodny typ gałęzi, jeśli go ustalił;
w przeciwnym razie może zwrócić pierwszy dostępny typ gałęzi. Nie stanowi to
pełnej kontroli zgodności gałęzi ani wyprowadzenia ich unii.
InferFieldAccessType może zwrócić brak typu dla nieznanego pola.
Wykonane próby przyjęły bez diagnostyk:
| Próba | Luka, której brak błędu nie rozstrzyga |
|---|---|
not 1 |
Wymaganie logicznego operandu. |
1 + "text" |
Zgodność operandów operatora dodawania. |
if 1 {} |
Typ warunku. |
| Przypisanie do const | Niezmienność i poprawność celu zapisu. |
Odczyt r.missing ze znanej struktury |
Istnienie pola i pełne typowanie dostępu. |
| Tekst przekazany do zwykłej funkcji z parametrem Int | Zgodność typu argumentu. |
Sprawdzone przykłady
Pary są sprawdzane przez rzeczywiste CLI w osobnych projektach. Potwierdzają składnię i wybrane kontrole, nie wynik obliczeń po wykonaniu.
Przykład poprawny
task Decide() @server {
let x: Int = 2 + 3 * 4
if x > 10 {
log("high")
} else {
log("low")
}
}
Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.
Przykład błędny
task Decide() @server { if true log("yes") }
Powód: Instrukcja if wymaga bloku w nawiasach klamrowych.
Kontrola: przykład odrzucony przez check; kody: P0001.
Pełny zakres weryfikacji przykładów.
Przykład: Postacie argumentów wywołania
Poprawny zapis
fn Add(a: Int,b: Int)->Int { return a+b } task Main() @server { let x=Add(a:1,b:2) }
Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.
Błędny zapis
fn Add(a: Int,b: Int)->Int { return a+b } task Main() @server { let x=Add(1,b:2) }
Powód: Lista argumentów dopuszcza postać pozycyjną albo nazwaną; nie ich mieszanie.
Kontrola: przykład odrzucony przez check; kody: P0001.
Pełny zakres weryfikacji przykładów.
Przykład: Blok instrukcji if
Poprawny zapis
task Main() @server { if true { let x = 1 } }
Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.
Błędny zapis
task Main() @server { if true let x = 1 }
Powód: Gałąź if wymaga nawiasów klamrowych.
Kontrola: przykład odrzucony przez check; kody: P0001.
Pełny zakres weryfikacji przykładów.
Przykład: Grupowanie nawiasami
Poprawny zapis
task Main() @server { let x = (1+2)*3 }
Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.
Błędny zapis
task Main() @server { let x = (1+2)* }
Powód: Operator mnożenia wymaga prawego operandu.
Kontrola: przykład odrzucony przez check; kody: P0001.
Pełny zakres weryfikacji przykładów.
Jawne luki
- Pełne typowanie operandów, warunków, indeksów, pól i wszystkich rodzajów wywołań.
- Walidacja celu przypisania, niezmienności let/const i operacji złożonych.
- Jednorodność kolekcji, zgodność gałęzi i zawężanie unii.
- Wyliczony kontrakt dopuszczalnych konwersji i operatora referencji
&. - Kolejność efektów w operandach i argumentach oraz pełny kontrakt krótkiego spięcia niezależny od języka hosta.
- Dzielenie przez zero, przepełnienia, reszta z dzielenia i konwersje numeryczne na wszystkich targetach.
- Wyczerpywanie i kolejność dopasowań match jako zweryfikowana semantyka wykonania.
Nie wypełniamy tych luk domyślnym zachowaniem Go, C++ lub JavaScript.
Podstawa w repozytorium
grammar/JunexisLang.g4: poziomy wyrażeń, argumenty, literały i instrukcje.compiler/JunexisLang.Compiler.Core/Parsing/AstBuilder.cs: budowanie AST,BuildLeftAssoci normalizacja operatorów logicznych.compiler/JunexisLang.Compiler.Core/Semantics/SemanticAnalyzer.cs:ValidateExpr,ValidateCall,InferExprType,InferUnaryType,InferBinaryType,InferArrayLiteralTypeiInferFieldAccessType.compiler/JunexisLang.Compiler.Tests/Parser/ParserExpressionTests.cs: testy aliasów operatorów i pierwszeństwa and przed or.compiler/JunexisLang.Compiler.Tests/Semantics/SemanticAnalyzerTests.cs: wybrane przypadki wnioskowania typów i walidacji.