Deklaracje danych i funkcji
struct i enum deklarują nazwane typy, a fn — nazwaną funkcję.
Poniżej oddzielamy wymagania gramatyki od kontroli semantycznej i jej luk.
Przyjęcie deklaracji nie dowodzi poprawnego wykonania wszystkich jej użyć.
Struktury
structDeclaration
: STRUCT IDENTIFIER LBRACE structField* RBRACE
;
structField
: IDENTIFIER COLON typeReference (ASSIGN expression)? (SEMICOLON | COMMA)?
;
structLiteral
: IDENTIFIER LBRACE structFieldInit (COMMA structFieldInit)* COMMA? RBRACE
;
structFieldInit
: IDENTIFIER COLON expression
;
D01 — pola i inicjalizacja
Struktura ma nazwę będącą identyfikatorem oraz ciało, które może być puste.
Każde pole wymaga nazwy, dwukropka i typu. Po typie można podać = wyrażenie
jako deklarowaną wartość domyślną. Pole może kończyć się średnikiem albo przecinkiem;
separator jest opcjonalny. Nie ma tu parametrów generycznych struktury.
Literał Reading { value: 1 } jest inną konstrukcją niż deklaracja typu.
Wymaga co najmniej jednego przypisania pola; przypisania są rozdzielane
przecinkami, a przecinek końcowy jest dopuszczony. Pusta deklaracja struktury
nie dowodzi więc dostępności pustego literału Empty {} w każdej pozycji.
D02 — zakres obecnej walidacji
ValidateStructLiteral sprawdza, czy nazwa typu jest znana, i waliduje wyrażenia
wartości pól. Nie porównuje tam kompletu nazw pól z deklaracją ani nie sprawdza
ich typów względem pól struktury. Nieznana nazwa typu daje ostrzeżenie E0021.
W próbach przyjęte zostały bez diagnostyk:
- pominięte wymagane pole
note: Text; - pole
other, którego nie ma w strukturze; - tekst wpisany do pola zadeklarowanego jako
Int; - dwukrotnie zadeklarowane pole
value; - typ
Missingw deklaracji pola struktury.
Te wyniki są lukami kontroli, nie regułą strukturalnego rozszerzania rekordów, konwersji typów ani automatycznej wartości dla pominiętego pola. Nie ustanawiamy pełnej semantyki wartości domyślnych i kolejności ich obliczania na podstawie samej obecności wyrażenia w AST.
Wyliczenia
enumDeclaration
: ENUM IDENTIFIER (LT IDENTIFIER (COMMA IDENTIFIER)* GT)? LBRACE enumVariant (COMMA enumVariant)* COMMA? RBRACE
;
enumVariant
: IDENTIFIER (LPAREN typeReference (COMMA typeReference)* RPAREN)?
;
D03 — warianty i dane
Wyliczenie wymaga nazwy i co najmniej jednego wariantu. Warianty są rozdzielane
przecinkami; końcowy przecinek jest dozwolony. Wariant bez danych jest identyfikatorem.
Wariant z danymi ma co najmniej jeden typ w nawiasach, np. Pair(Int, Text);
nie deklaruje w tej regule nazw pól danych.
Gramatyka dopuszcza listę nazw parametrów typu, np.
enum Maybe<T> { Some(T), Empty }. Ten zapis przechodzi próbę check, lecz
nie potwierdza pełnej implementacji podstawiania argumentów i typowania
konstruktorów generycznych.
D04 — użycie i ograniczenia kontroli
Postać Mode::Idle jest dostępem kwalifikowanym w gramatyce wyrażeń.
Wzorce dopasowania wariantów opisano w wyrażeniach.
Sama postać Mode::Nazwa nie dowodzi istnienia wariantu: próba Mode::Missing
przy wyliczeniu zawierającym tylko Idle przeszła bez diagnostyk.
Powtórzony wariant enum Mode { Idle, Idle } również przeszedł check.
Nie wyprowadzamy z tych wyników dozwolenia na powtórzenia, niejawnego tworzenia wariantów, układu pamięci, numerycznych wartości tagów ani pełnego sprawdzania liczby i typów przenoszonych danych. Wyczerpujące dopasowanie i zawężanie wymagają osobnych reguł i dowodów.
Funkcje
fnDeclaration
: FN IDENTIFIER LPAREN parameterList? RPAREN (ARROW typeReference)? fnBody
;
fnBody
: LBRACE statement* RBRACE
;
parameterList
: parameter (COMMA parameter)*
;
parameter
: anyIdentifier COLON typeReference (ASSIGN expression)?
;
D05 — sygnatura i ciało
Funkcja wymaga identyfikatora, nawiasów parametrów i ciała w klamrach.
Lista parametrów może być pusta, podobnie ciało. Każdy parametr wymaga typu;
może zawierać = wyrażenie jako wartość domyślną. Adnotacja wyniku po ->
jest opcjonalna. Nie ma w tej regule adnotacji platform ani parametrów generycznych fn.
Deklarowana wartość domyślna parametru nie jest dowodem poprawnego uzupełniania
pominiętych argumentów przez wszystkie generatory. Argumenty i ich ograniczenia
opisano w wyrażeniach.
Funkcja bez ciała nie jest poprawnym fn; zewnętrzną sygnaturę deklaruje
się przez osobną konstrukcję extern.
D06 — nazwy i odwołania
Analizator zbiera nazwy funkcji przed sprawdzaniem ich ciał. Parametry tworzą lokalny zakres. Importowanie funkcji i aliasów podlega zasadom modułów. To rozwiązywanie nazw nie potwierdza kompletnej kontroli każdego wywołania. Wcześniejsza próba przekazania Text do zwykłej funkcji oczekującej Int przeszła check.
Dla powtórzonej deklaracji fn Value() {} fn Value() {} projektowa ścieżka CLI
w wykonanej próbie zakończyła się nieobsłużonym wyjątkiem przy budowaniu mapy
eksportów w ProjectSymbolBinder, zamiast zwrócić ustrukturyzowaną diagnostykę.
To błąd obsługi kolizji, nie wynik potwierdzający poprawne przeciążanie funkcji.
Nie traktujemy wyjątku narzędzia jako poprawnego negatywnego przykładu języka.
D07 — wynik i efekty
ValidateFnBody sprawdza parametry, nazwę typu wyniku i instrukcje ciała.
Gałąź obsługująca return w ValidateStmt waliduje wyrażenie, ale nie porównuje
w tym miejscu jego typu z deklarowanym wynikiem ani nie dowodzi powrotu
na każdej ścieżce. Próby fn Value() -> Int { return "text" } i
fn Value() -> Int {} przeszły bez diagnostyk.
Funkcja jest w opisie języka projektowana jako granica czystych obliczeń.
Jednak jej ciało jest listą ogólnych instrukcji, a próba fn Value() { log("effect") }
przeszła check. Nie przedstawiamy więc czystości jako w pełni egzekwowanej własności.
Nie zakładamy też automatycznie dozwolenia na wszystkie efekty tylko dlatego,
że gramatyka przyjęła instrukcję.
Sprawdzone przykłady
Pary są sprawdzane przez rzeczywiste CLI w osobnych projektach. Potwierdzają składnię i wybrane wyniki kontroli, nie realizację struktur i funkcji w kodzie wynikowym.
Przykład poprawny
struct Reading {
value: Float
}
enum Mode { Idle, Active }
fn Twice(value: Int) -> Int {
return value * 2
}
Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.
Przykład błędny
struct Reading { value Float }
Powód: Pomiędzy nazwą pola i typem jest wymagany dwukropek.
Kontrola: przykład odrzucony przez check; kody: P0001.
Pełny zakres weryfikacji przykładów.
Przykład: Typ pola struktury
Poprawny zapis
struct Reading { value: Int = 1 note: Text }
Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.
Błędny zapis
struct Reading { value = 1 }
Powód: Pole wymaga dwukropka i jawnego typu przed opcjonalną wartością domyślną.
Kontrola: przykład odrzucony przez check; kody: P0001.
Pełny zakres weryfikacji przykładów.
Przykład: Wyliczenie i dane wariantu
Poprawny zapis
enum Message { Ready, Value(Int), Pair(Int, Text), }
Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.
Błędny zapis
enum Empty {}
Powód: Wyliczenie musi mieć co najmniej jeden wariant.
Kontrola: przykład odrzucony przez check; kody: P0001.
Pełny zakres weryfikacji przykładów.
Przykład: Parametry funkcji
Poprawny zapis
fn Twice(value: Int = 2) -> Int { return value*2 }
Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.
Błędny zapis
fn Twice(value) -> Int { return value*2 }
Powód: Parametr wymaga jawnego typu.
Kontrola: przykład odrzucony przez check; kody: P0001.
Pełny zakres weryfikacji przykładów.
Przykład: Ciało funkcji
Poprawny zapis
fn Value() {}
Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.
Błędny zapis
fn Value()
Powód: Zwykła funkcja fn wymaga ciała; nie jest deklaracją extern.
Kontrola: przykład odrzucony przez check; kody: P0001.
Pełny zakres weryfikacji przykładów.
Jawne luki
- Pełna kontrola nazw, kompletności i typów inicjalizowanych pól oraz powtórzeń pól.
- Walidacja typów i wartości domyślnych w deklaracjach danych oraz ich wykonanie.
- Istnienie wariantów enum, powtórzenia, dane wariantów i generyczne podstawianie typów.
- Kontrolowane diagnostyki kolizji w całej ścieżce projektowej.
- Zgodność argumentów, wartości return i kompletność powrotu ze zwykłej funkcji.
- Pełna kontrola czystości, efektów i zachowania funkcji rekurencyjnych.
- Przenośna reprezentacja danych i zgodność tych konstrukcji we wszystkich generatorach.
Nie uzupełniamy luk zachowaniem pojedynczego języka hosta.
Podstawa w repozytorium
grammar/JunexisLang.g4: reguły przytoczone powyżej.compiler/JunexisLang.Compiler.Core/Semantics/SemanticAnalyzer.cs: zbieranie symboli,ValidateFnBody,ValidateStmt,ValidateStructLiteral.compiler/JunexisLang.Compiler.Core/Projects/ProjectSymbolBinder.cs: budowanie eksportów i zakresów projektowych.compiler/JunexisLang.Compiler.Tests/Parser/: testy struktur, enum i funkcji.compiler/JunexisLang.Compiler.Tests/Semantics/SemanticAnalyzerTests.cs: wybrane kontrole nazw i wyrażeń; nie dowód kompletnej walidacji deklaracji.