Przejdź do treści

Połączenia zadań: pipeline

pipeline opisuje nazwany, skierowany graf logicznych połączeń zadań. Poniższe reguły rozdzielają składnię, analizę grafu i niepełny kontrakt wiązania danych. Przejście check nie jest dowodem uruchomienia ani poprawnego przesyłania wartości.

Składnia

pipelineDeclaration
    : PIPELINE IDENTIFIER LBRACE pipelineConnection* RBRACE
    ;
pipelineConnection
    : USE pipelineSource (ARROW pipelineTarget)? SEMICOLON?
    | PARALLEL LBRACE pipelineConnection+ RBRACE
    ;
pipelineSource
    : IDENTIFIER (DOT IDENTIFIER)?
    ;
pipelineTarget
    : IDENTIFIER (DOT IDENTIFIER)?
    ;

Reguły zapisu i budowania grafu

P01 — deklaracja i połączenie

Deklaracja wymaga nazwy i nawiasów klamrowych. Jej ciało może być puste. use A -> B zapisuje krawędź skierowaną od zadania A do zadania B. Średnik po use jest opcjonalny. Jedna instrukcja dopuszcza najwyżej jedną strzałkę: łańcuch A → B → C wymaga dwóch instrukcji use. W tej składni nie ma argumentów wywołania, aliasów instancji ani operatora |>.

P02 — nazwy zadań

Analizator grafu rozwiązuje nazwy źródła i celu względem zadań w dokumencie IR, z rozróżnianiem wielkości liter. Nieznane zadanie powoduje błąd T0002; nie staje się nowym węzłem. Dotyczy to również źródła w use A bez celu. Węzłami analizowanego grafu są wszystkie zadeklarowane zadania, także niepołączone. Graf jest zbierany ze wszystkich deklaracji pipeline; nazwa deklaracji pozostaje zapisana przy każdej krawędzi. Analiza cykli nie jest ograniczona do jednej deklaracji. Nie wynika z tego tworzenie osobnej instancji zadania dla każdego wystąpienia use.

P03 — postać z portem

Każda strona dopuszcza dokładnie jeden opcjonalny człon: Task.port. Zapis oznacza wskazanie nazwanego końca połączenia; parser i IR zachowują obie nazwy. Zachowanie nazwy portu nie jest jej rozwiązaniem. PipelineGraph.Build sprawdza istnienie zadań, ale nie istnienie portów ani zgodność ich typów.

Pełna reguła deklarowania wyjść nazwanych, wyboru wejścia przy pominięciu portu oraz dopasowania wielu parametrów nie jest tu ustalona. Nie należy traktować dowolnego identyfikatora po kropce jako poprawnego portu tylko dlatego, że check nie zgłasza błędu.

P04 — źródło bez celu

use A jest dopuszczone przez gramatykę. Analizator sprawdza istnienie A, ale nie dodaje krawędzi. Ten fakt nie stanowi kontraktu uruchamiania zadania: nie ustala liczby instancji, argumentów ani momentu startu.

P05 — grupowanie parallel

parallel { ... } wymaga co najmniej jednego połączenia i może zawierać kolejne bloki parallel. IR zachowuje grupowanie, a analizator grafu zbiera z niego te same krawędzie co z zapisów bez grupowania. Sama analiza grafu nie ustanawia bariery synchronizacji, kolejności startu, liczby wątków, procesów ani fizycznej równoczesności wykonania.

P06 — rozgałęzienia, powtórzenia i cykle

Kilka instrukcji może mieć wspólne źródło lub wspólny cel. Jest to opis topologii, nie pełna reguła kopiowania wiadomości, rozdzielania pracy czy scalania danych. Rozgałęzienie nie zmienia samo kontraktu chan, queue lub stream.

Budowanie grafu zachowuje powtórzone krawędzie. Lista następców używana przy analizie cykli usuwa powtórzenia nazw zadań. Nie oznacza to ani podwójnej dostawy, ani gwarantowanej deduplikacji wiadomości podczas wykonania.

Cykl, w tym połączenie zadania z nim samym, wywołuje ostrzeżenie T0001, a nie błąd. Nie jest to sprawdzenie zakleszczeń, inicjalizacji cyklu ani jego żywotności.

Sprawdzone przykłady

Pary poniżej są sprawdzane przez rzeczywiste CLI w osobnych projektach. Potwierdzają przyjęcie lub odrzucenie źródła; nie są próbami wykonania systemu.

Przykład poprawny

task Emit() @server -> chan<Int> {
    send 1
}

task Read(values: chan<Int>) @server {}

pipeline Flow {
    use Emit -> Read
}

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

Przykład błędny

pipeline Flow { use -> Read }

Powód: Po use wymagana jest nazwa źródłowego zadania.

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

Pełny zakres weryfikacji przykładów.

Przykład: Istniejące i nieznane zadanie

Poprawny zapis

task Emit() @server -> chan<Int> { send 1 }
task Read(values: chan<Int>) @server {}
pipeline P { use Emit -> Read }

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

Błędny zapis

task Emit() @server -> chan<Int> { send 1 }
task Read(values: chan<Int>) @server {}
pipeline P { use Emit -> Missing }

Powód: Cel Missing nie jest zadeklarowanym zadaniem; analiza topologii zgłasza T0002.

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

Pełny zakres weryfikacji przykładów.

Przykład: Grupowanie połączeń

Poprawny zapis

task Emit() @server -> chan<Int> { send 1 }
task Read(values: chan<Int>) @server {}
pipeline P { parallel { use Emit -> Read; } }

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

Błędny zapis

task Emit() @server -> chan<Int> { send 1 }
task Read(values: chan<Int>) @server {}
pipeline P { parallel {} }

Powód: Ciało parallel musi zawierać co najmniej jedno połączenie; ciało pipeline może być puste.

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

Pełny zakres weryfikacji przykładów.

Przykład: Łańcuch zapisany jako osobne połączenia

Poprawny zapis

task A() @server {}
task B() @server {}
task C() @server {}
pipeline P { use A -> B use B -> C }

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

Błędny zapis

task A() @server {}
task B() @server {}
task C() @server {}
pipeline P { use A -> B -> C }

Powód: Jedna instrukcja use dopuszcza najwyżej jedną strzałkę. Ten przykład sprawdza strukturę grafu, nie przesyłanie danych.

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

Pełny zakres weryfikacji przykładów.

Granice obecnego sprawdzania

Dodatkowe próby check dały następujące wyniki:

Źródło / sytuacja Wynik Co z tego wynika
Puste pipeline P {} przyjęte Brak wymogu co najmniej jednej krawędzi.
use Emit przy istniejącym zadaniu przyjęte Dopuszczony zapis bez celu; graf nie dostaje krawędzi.
use Emit.unknown -> Read.missing przyjęte bez diagnostyk Brak potwierdzenia istnienia wskazanych portów.
Wyjście chan<Int> połączone z wejściem queue<Text> przyjęte bez diagnostyk Brak diagnozy zarówno niezgodności rodzaju komunikacji, jak i typu elementu w tej próbie.
Dwukrotnie use Emit -> Read przyjęte bez diagnostyk Brak zakazu powtórzeń na tym etapie.
use Emit -> Read i use Read -> Emit przyjęte z ostrzeżeniem T0001 Wykrycie cyklu nie blokuje check.
Cel Missing bez deklaracji zadania odrzucone, T0002 Sprawdzanie nazw zadań jest zaimplementowane.

Próby z nieznanymi portami i niezgodnymi typami pokazują lukę walidacji, a nie dozwoloną konwersję. Nie dodajemy na tej podstawie reguły niejawnej zamiany chan na queue ani Int na Text.

Graf logiczny a generowanie systemu

Analizator liczy rozgałęzienia według unikalnych par (zadanie docelowe, port docelowy), a zbieganie połączeń według par (zadanie źródłowe, port źródłowy). Od czterech takich końców zgłasza informacje T0003 lub T0004. Nie są to limity dopuszczalnej liczby połączeń ani pomiary obciążenia.

DistributedTopologyLowerer tworzy osobny plan rozmieszczenia i transportu. Obecnie odczytuje rodzaj komunikacji najpierw z typu wyjścia zadania źródłowego; gdy go brak, sięga do nazwanego parametru celu albo pierwszego parametru przy pominiętym porcie. Jest to zachowanie tej implementacji, nie kompletny kontrakt rozwiązywania portów ani dowód zgodności obu końców.

Transformacja do IR ma osobne deskryptory mostów chan, queue i stream między @server a @report. Ich tworzenie sprawdza rodzaj wyjścia i obecność parametru komunikacyjnego danego rodzaju; nie zastępuje pełnej walidacji portów i typów elementów. Sam deskryptor nie dowodzi uruchomienia transportu.

Jawne luki kontraktu

  • Reguła deklarowania i rozwiązywania nazwanych wyjść oraz pełne wiązanie wejść, szczególnie przy wielu parametrach i pominiętym porcie.
  • Walidacja zgodności rodzaju komunikacji oraz typu elementu na obu końcach; diagnostyki nieistniejących i niejednoznacznych portów.
  • Znaczenie powtórzonej krawędzi i współdzielenia zadania między pipeline'ami dla instancji i dostarczania danych.
  • Reguły rozsyłania i scalania dla rozgałęzień, niezależne od samego liczenia krawędzi.
  • Pełny kontrakt wykonania parallel i use A bez celu.
  • Warunki startu, inicjalizacji i postępu cykli; wykrycie cyklu ich nie rozstrzyga.

Podstawa w repozytorium i zakres dowodów

  • grammar/JunexisLang.g4: cztery reguły składni zamieszczone powyżej.
  • spec/04-declarations.md, sekcja „CORE: pipeline”: kierunek przepływu i zapis portów.
  • compiler/JunexisLang.Compiler.IR/AstToIrTransformer.cs: zachowanie połączeń, grupowania i budowanie deskryptorów mostów.
  • compiler/JunexisLang.Compiler.Topology/PipelineGraph.cs: nazwy, porty i krawędzie.
  • compiler/JunexisLang.Compiler.Topology/TopologyAnalyzer.cs: cykle i metryki.
  • compiler/JunexisLang.Compiler.Topology/DistributedTopologyLowerer.cs: odrębny plan rozproszony.
  • compiler/JunexisLang.Compiler.Tests/IR/IrTransformerPipelineTests.cs oraz testy w compiler/JunexisLang.Compiler.Tests/Topology/: transformacja i analiza.

Testy struktury i wyniki check nie potwierdzają wykonania wszystkich tych połączeń w C++, Go i TypeScript ani działania między maszynami.