Przejdź do treści

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 Missing w 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.