Przejdź do treści

Zadania

task deklaruje nazwaną jednostkę zachowania: jej wejścia, opcjonalny wynik, adnotacje platformy i ciało. Ten rozdział oddziela deklarację, wykonanie oraz instancję ze stanem. Nie traktuje mechanizmu jednego backendu jako reguły języka.

1. Składnia deklaracji

taskDeclaration
    : TASK IDENTIFIER
      (LPAREN parameterList? RPAREN)?
      platformAnnotation*
      communicationSignature?
      taskBody
    ;
parameterList
    : parameter (COMMA parameter)*
    ;
parameter
    : anyIdentifier COLON typeReference (ASSIGN expression)?
    ;
communicationSignature
    : ARROW typeReference
    ;

Reguły poprawności składniowej

Reguła Wymaganie
T01 Po task musi wystąpić identyfikator nazwy.
T02 Lista parametrów może zostać pominięta albo ujęta w nawiasy. Każdy parametr wymaga nazwy i typu po :.
T03 Wartość domyślna parametru ma postać = wyrażenie. Sama możliwość zapisu nie rozstrzyga wszystkich zasad jej podstawiania.
T04 Adnotacje platformy występują po liście parametrów, przed wynikiem. Gramatyka dopuszcza ich brak lub więcej niż jedną.
T05 Typ wyniku jest opcjonalny. Jeżeli występuje strzałka ->, musi po niej wystąpić typ.
T06 Zwykły task zawsze ma ciało w { ... }; ciało może być puste.

task Main @server {} oraz task Main() @server {} są przyjmowane przez obecną gramatykę i check. Nie ustanawiamy tu wymagania pustych nawiasów, którego nie ma w źródłach.

Deklarację bez ciała zapewnia odrębna konstrukcja extern task. Nie jest to skrócona postać zwykłego zadania.

2. Deklaracja, instancja i uruchomienie

Pojęcie Ustalone znaczenie Granica definicji
Deklaracja Nazwany opis wejść, wyniku i zachowania. Samo wystąpienie deklaracji nie dowodzi uruchomienia na konkretnej maszynie.
Instancja Kontekst wykonania i stanu, do którego odwołuje się kontrakt cyklu życia. Brak kompletnej reguły tworzenia wielu instancji, ich tożsamości i izolacji.
Wywołanie Wykonanie zadania z argumentami; repo zawiera wykonywalny scenariusz powtarzanych wywołań zadania wartościowego. Nie jest ogólną obietnicą nowej instancji przy każdym wywołaniu.
Połączenie w pipeline Ustanowienie przepływu między zadaniami. Nie określa samo liczby procesów, schedulera ani trwałości transportu.

Co uruchamia zadanie?

W repozytorium występują uruchomienie przez wygenerowany punkt wejścia, wywołanie zadania z argumentami i podłączenie go do pipeline. Nie ma jednak jednego domkniętego kontraktu uruchamiania wszystkich tych postaci na wszystkich targetach. Ta luka pozostaje jawna. Nie zastępujemy jej regułą „każda deklaracja uruchamia się automatycznie”.

Odebranie danych jest kolejnym zagadnieniem: recv lub iteracja komunikacyjna mogą oczekiwać na wejście już uruchomionego zadania. Dostarczenie wiadomości nie jest tym samym co utworzenie nowej instancji.

3. Wejścia i wynik

Parametr ma postać nazwa: Typ. Typ może być wartościowy, np. Int, albo komunikacyjny, np. chan<Int>; nie każdy parametr jest kanałem. Parametr komunikacyjny udostępnia źródło danych zgodnie z kontraktem swojego typu.

Zapis Znaczenie
value: Int Wartościowe wejście o typie Int.
values: chan<Int> Wejście komunikacyjne, z którego zadanie może odbierać liczby.
-> Int Zadeklarowany wynik wartościowy. return wyprowadza wynik z wykonania.
-> chan<Int> Zadeklarowane wyjście komunikacyjne. send emituje wiadomość.
Brak -> Typ Brak jawnej deklaracji wyniku; nie dopisujemy niejawnego wyniku na podstawie języka hosta.

send 1 i return 1 nie są zamienne. Zgodność typów wartości wejścia, wyniku i wiadomości wymaga kontroli znaczenia. Poprawny zapis składniowy nie dowodzi, że obecny check diagnozuje każdą niezgodność.

Reguły połączenia źródła i odbiorcy określa pipeline, a własności dostarczania — komunikacja.

4. Ciało zadania

taskBody
    : LBRACE taskMember* RBRACE
    ;
taskMember
    : stateDeclaration
    | configBlock
    | nativeSection                  // embedded library deps — spójne z device.native (D13)
    | lifecycleHook
    | statement
    ;

Element Rola
state nazwa: Typ = wyrażenie Stan zadania.
config { ... } Typowane pola konfiguracji.
native { ... } Deklaracje zależności natywnych; nie dowolny blok instrukcji Junexis.
on nazwa { ... } Nazwany hak cyklu życia.
Instrukcje Obliczenia, przepływ sterowania, komunikacja i zakończenie wykonania.

config i state nie są synonimami. Wartość domyślna pola konfiguracji jest w gramatyce poprzedzona drugim dwukropkiem, np. period: Duration : 1s. Inicjalizacja stanu używa =, np. state count: Int = 0.

configBlock
    : CONFIG LBRACE configField* RBRACE
    ;
configField
    : IDENTIFIER COLON typeReference (COLON literal)? (SEMICOLON | COMMA)?
    ;

5. Stan i cykl życia

Kontrakt junexis.task-lifecycle/0.1 opisuje ograniczony scenariusz okresowy:

  1. Inicjalizacja stanu następuje raz na instancję zadania.
  2. Wartości stanu są zachowane między kolejnymi wywołaniami tego kontekstu.
  3. Zdarzenie start poprzedza wykonania okresowe; tick oznacza zakończony takt.
  4. Pierwszy takt every następuje po interwale.
  5. Anulowanie jest obserwowane pomiędzy zakończonymi taktami, a stop występuje po jego zaobserwowaniu.

Dla trzech zakończonych taktów kontrakt podaje kolejność start → tick → tick → tick → stop. Nie jest to uniwersalna definicja wszystkich zadań, haków i wyjątkowych zakończeń.

Granice gwarancji stanu

  • Stan między wywołaniami nie oznacza automatycznie zapisu na dysk, odtworzenia po restarcie ani przetrwania utraty zasilania.
  • Kontrakt nie definiuje tutaj izolacji kilku równoległych instancji tej samej deklaracji.
  • Nie ma ogólnej gwarancji transakcyjności aktualizacji wobec innych zadań.
  • Zachowanie bloku every nie gwarantuje dokładności zegara ściennego ani twardego czasu rzeczywistego.

Szczegóły zapisu i ograniczeń są w rozdziale o stanie i czasie.

Klasyfikacja cyklu życia a zachowanie

Typed Semantic IR rozróżnia OneShot, Periodic, Looping, HookDriven i Mixed. Projektor klasyfikuje obecność every, jawnego loop i haków. To klasyfikacja opisu, nie scheduler. W szczególności iteracja for value in values po kanale nie staje się jednoelementowym odbiorem tylko z powodu braku jawnego loop w tej klasyfikacji.

6. Haki cyklu życia

lifecycleHook
    : ON IDENTIFIER LBRACE statement* RBRACE
    ;

Ustalona składnia wymaga on, identyfikatora i bloku. Starszy dokument deklaracji wymienia start, connect, reconnect i disconnect. Sam parser nie ogranicza jednak identyfikatora do tego katalogu.

Luka walidacji: kontrolna jednostka z on imaginary_event { ... } została przyjęta przez bieżący check. Nie potwierdziliśmy starszej obietnicy odrzucenia nieznanego haka kodem E0013. Przyjęcie tej jednostki nie dowodzi, że hak zostanie wykonany.

Zdarzenie stop w kontrakcie śladu cyklu życia również nie ustanawia samo obsługi składni on stop. Nie utożsamiamy tych dwóch pojęć.

7. Znaczenie platformy

Adnotacja wskazuje kontekst wykonania: urządzenie, serwer lub raport. Nie przydziela sama identyfikatora hosta, procesu, wątku ani rdzenia. Przeniesienie zadania nie usuwa jego zależności natywnych lub ograniczeń komunikacji.

Próby bez adnotacji i z jednoczesnymi @server @embedded są przyjmowane przez check. Pierwsza jest dopuszczona przez platformAnnotation*; druga nie dowodzi, że istnieje kompletna semantyka jednoczesnego wykonania takiej deklaracji na dwóch targetach.

8. Przykłady i ich zakres

Poniższe jednostki sprawdzono poleceniem check. Przykłady błędne są odrzucane z diagnostyką języka. Nie przypisujemy im niezweryfikowanych błędów znaczeniowych.

Przykład poprawny

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

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

Przykład błędny

task Emit() @server -> chan<Int>

Powód: Zwykła deklaracja task wymaga ciała. Deklaracja bez ciała wymaga extern task.

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

Pełny zakres weryfikacji przykładów.

Przykład: Parametr i wynik wartościowy

Poprawny zapis

task Twice(value: Int = 2) @server -> Int {
    return value * 2
}

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

Błędny zapis

task Twice(value) @server -> Int { return value * 2 }

Powód: Parametr zadania wymaga jawnego typu po dwukropku.

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

Pełny zakres weryfikacji przykładów.

Przykład: Wejście komunikacyjne

Poprawny zapis

task Read(values: chan<Int>) @server {
    for value in values {
        log(value)
    }
}

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

Błędny zapis

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

Powód: Iteracja ma postać for nazwa in wyrażenie; pominięto słowo in.

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

Pełny zakres weryfikacji przykładów.

Przykład: Stan i wykonanie okresowe

Poprawny zapis

task Pulse() @server {
    state count: Int = 0
    every 1s {
        count = count + 1
        log(count)
    }
}

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

Błędny zapis

task Pulse() @server { state count = 0 }

Powód: Deklaracja stanu wymaga typu. state count = 0 nie jest poprawną deklaracją state.

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

Pełny zakres weryfikacji przykładów.

Przykład: Zewnętrzna postać haka

Poprawny zapis

task Started() @server {
    on start {
        log("started")
    }
}

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

Błędny zapis

task Started() @server { on start log("started") }

Powód: Hak on nazwa wymaga bloku instrukcji w nawiasach klamrowych.

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

Pełny zakres weryfikacji przykładów.

9. Jawne luki specyfikacji

Pytanie Czego nie rozstrzygają sprawdzone źródła
Ile powstaje instancji? Pełne reguły instancjonowania przez wywołania, wiele pipeline i różne targety.
Jak izolowany jest stan? Tożsamość instancji, równoległe wywołania i relacja do odtwarzania stanu.
Jak zaczyna i kończy się każde zadanie? Jeden model dla zadań wartościowych, konsumentów, haków i modeli mieszanych.
Co oznacza nieznany hak? Ujednolicona lista, walidacja i warunki wywołania we wszystkich kontekstach.
Jak działają parametry domyślne? Pełne zasady podstawiania w wywołaniach, połączeniach i wiązaniach protokołu.
Jak kończy się odbiór? Ogólne zasady zamknięcia wejścia, błędu, anulowania i kolejności efektów końcowych.
Granice bieżącej implementacji i dowodów

To obserwacje implementacji, nie dodatkowe reguły języka:

  • Generator Go wiąże stan i jego cache z nazwą zadania. Nie jest to dowód izolacji wielu niezależnych instancji.
  • Generator embedded pozostawia zadania wymagające parametrów bez gotowego doprowadzenia wejść w punkcie startowym. Nie wolno obiecywać uruchomienia dowolnego konsumenta tylko na podstawie adnotacji.
  • W teście natywnym GeneratedLifecycleStateAndTime_IsEquivalentAcrossCppGoAndTypeScriptAsync rzeczywiste wywołania generowanych zadań potwierdzają wartości stanu 12, 15, 19 oraz interwał 250 ms. Lista zdarzeń cyklu życia jest wpisana w kod otoczki testu; ten test nie jest samodzielnym pomiarem uruchamiania haków i anulowania.
  • Osobne testy EveryLowering_WaitsBeforeFirstTickOnEveryBackendAsync i StatefulEvery_PersistsEachCompletedTickOnGoAndTypeScriptAsync sprawdzają wygenerowany kod. Ich wyniku nie należy nazywać pełnym testem wykonania.

10. Podstawa reguł i sposób sprawdzenia

Źródło w repozytorium Zakres
grammar/JunexisLang.g4: taskDeclaration, taskMember, parameter, lifecycleHook Zewnętrzna składnia i składniki ciała.
spec/04-declarations.md: „CORE: task” Opis znaczenia, konfiguracji i deklarowany katalog haków; z wyżej wskazaną rozbieżnością walidacji.
spec/22-semantic-conformance.md: „Lifecycle tasku, stan i czas” Zakres kontraktu scenariusza okresowego.
conformance/semantic-scenarios.json: runtime.task-lifecycle-state-time Zadeklarowane własności stanu i czasu.
compiler/JunexisLang.Compiler.IR/TaskLifecycleRuntimeContract.cs Wersja kontraktu i kanoniczna kolejność zdarzeń ograniczonego scenariusza.
compiler/JunexisLang.Compiler.IR/TypedSemanticIR.cs: ClassifyLifecycle Sposób klasyfikacji zadania, nie uruchamianie.
Testy TaskLifecycleRuntimeContractTests, TypedSemanticProjectorTests, GeneratedLifecycleStateTimeConformanceTests Różne poziomy kontroli kontraktu, projekcji i generowania; ich zakres opisano powyżej.

Rozdział nie zmienia gramatyki ani zachowania wykonawcy. Luki nie są rozstrzygane przez dopisanie zachowania, którego źródła nie ustalają.