# Język Volang
Witamy w dokumentacji języka Volang, autorskiego języka programowania Voldeno. Służy on jako podstawowe narzędzie do implementacji logiki bloków w naszym ekosystemie inteligentnego domu.
# Wprowadzenie
Volang to wyspecjalizowany język programowania opracowany przez Voldeno, zaprojektowany specjalnie do implementacji logiki bloków w ekosystemie inteligentnego domu. Łączy możliwości sprzętowe z automatyzacją wysokiego poziomu, umożliwiając programistom tworzenie zaawansowanych scenariuszy sterowania w sposób wydajny i bezpieczny.
Głównym celem Volang jest definiowanie zachowania Bloków. W systemie Voldeno blok reprezentuje autonomiczną jednostkę funkcjonalną (np. sterownik rolet, termostat lub licznik energii). Volang pozwala programistom określić, jak te bloki reagują na sygnały wejściowe, przetwarzają stan wewnętrzny i wyzwalają akcje na podstawie zdarzeń.
# Architektura: Model kompilowany
W przeciwieństwie do prostych języków skryptowych interpretowanych bezpośrednio z tekstu, Volang wykorzystuje architekturę Maszyny Wirtualnej (VM). Proces wykonania składa się z dwóch odrębnych etapów:
- Kompilacja: Kod źródłowy napisany przez programistę jest przetwarzany przez kompilator Volang. Ten krok weryfikuje składnię, typy danych i integralność logiczną, przekształcając kod w zoptymalizowany format binarny znany jako bytecode.
- Wykonanie: Wynikowy bytecode jest ładowany do Maszyny Wirtualnej Volang (Volang VM) — dedykowanego środowiska uruchomieniowego działającego na kontrolerach Voldeno. VM odpowiada za interpretację instrukcji bytecode i ich bezpieczne wykonanie na sprzęcie fizycznym.
# Dlaczego Volang VM?
Przyjęcie architektury maszyny wirtualnej oferuje znaczące korzyści dla środowisk inteligentnego domu, unifikuje przepływ pracy programistycznej i rozszerza możliwości sprzętu:
- Bezpieczeństwo (Sandboxing): Kod użytkownika działa w izolowanym środowisku, oddzielonym od podstawowego systemu operacyjnego. Błąd wykonania (np. dzielenie przez zero) zatrzyma tylko konkretny skrypt w VM, zapewniając nienaruszalność ogólnej stabilności kontrolera.
- Wydajność: Wykonywanie kompaktowego binarnego bytecode jest znacznie szybsze i bardziej efektywne pod względem zasobów niż parsowanie plików tekstowych w czasie wykonania, co jest kluczowe dla systemów wbudowanych i mikrokontrolerów.
- Przenośność: Logika napisana w Volang jest niezależna od sprzętu. Może być wykonywana na dowolnym urządzeniu zdolnym do uruchomienia Volang VM, niezależnie od podstawowej architektury procesora.
- Identyczna symulacja: Ponieważ Volang VM działa spójnie na różnych platformach, dokładnie ta sama logika bloku może być wykonana w Voldeno Studio. Pozwala to programistom testować i debugować kod w środowisku desktopowym z gwarancją, że zachowanie będzie identyczne z rzeczywistym systemem produkcyjnym.
- Rozproszone wykonanie: Wydajność VM pozwala na uruchamianie jej nie tylko na centralnym Voldeno Hub, ale także na modułach rozszerzeń peryferyjnych. Ta możliwość umożliwia prawdziwie rozproszoną logikę, gdzie złożone przetwarzanie może odbywać się lokalnie na urządzeniu (na brzegu sieci) bez ograniczenia do podstawowego raportowania zdarzeń.
# Konwencje leksykalne
# Słowa kluczowe
Następujące słowa są zarezerwowane w Volang i nie mogą być używane jako nazwy zmiennych lub funkcji. Tworzą one strukturalną podstawę języka.
and break else
extern false fn
if or shl
shr return true
while xor
# Literały
W Volang literał to notacja reprezentująca stałą wartość bezpośrednio w kodzie źródłowym. To surowe wartości danych, które przypisujesz do zmiennych lub przekazujesz do funkcji. Volang obsługuje określone formaty dla literałów logicznych, łańcuchowych, całkowitych i zmiennoprzecinkowych.
# Literały logiczne (Boolean)
Używane do reprezentowania stanów logicznych. Istnieją dokładnie dwa literały logiczne:
true: Reprezentuje stan pozytywny lub aktywny (Włączony).false: Reprezentuje stan negatywny lub nieaktywny (Wyłączony).
# Literały całkowite (Integer)
Literały całkowite reprezentują liczby całkowite. Volang obsługuje dwie notacje:
- Dziesiętna (Baza-10): Standardowa reprezentacja numeryczna używająca cyfr
0-9. Liczby ujemne są poprzedzone znakiem minus-. Wartości muszą się mieścić w przedziale od-9223372036854775808do9223372036854775807. - Szesnastkowa (Baza-16): Często używana dla kodów kolorów, masek bitowych lub adresów sprzętowych. Te literały muszą zaczynać się od przedrostka
0xpo którym następują cyfry0-9i literyA-F(wielkość liter nie ma znaczenia).
# Literały zmiennoprzecinkowe (Float)
Literały zmiennoprzecinkowe reprezentują liczby z częścią ułamkową.
- Składnia: Zapisywane są w notacji dziesiętnej z kropką
.jako separatorem dziesiętnym. - Wymaganie: Prawidłowy literał float zazwyczaj wymaga co najmniej jednej cyfry przed lub po kropce dziesiętnej (np.
0.5,10.0,23.34).
# Literały tekstowe (String)
Literały tekstowe reprezentują sekwencje znaków i są używane do wiadomości, identyfikatorów lub etykiet.
- Składnia: Tekst musi być ujęty w podwójne cudzysłowy
". - Obsługa wieloliniowa: Literał łańcuchowy w Volang może rozciągać się na wiele linii. Znaki nowej linii wewnątrz cudzysłowów są zachowywane.
# Surowe łańcuchy (Raw Strings)
Surowe łańcuchy są używane do reprezentowania tekstu dokładnie tak, jak został napisany, ignorując znaki specjalne lub formatowanie.
- Składnia: Surowe łańcuchy muszą być ujęte w potrójne podwójne cudzysłowy
""". - Użycie: Wszystko wewnątrz potrójnych cudzysłowów jest traktowane jako tekst dosłowny. Jest to szczególnie przydatne do przechowywania bloków kodu, złożonych wzorców regex lub wstępnie sformatowanej dokumentacji.
- Zachowanie: Podobnie jak standardowe łańcuchy wieloliniowe, wszystkie wcięcia i podziały linii są zachowywane dokładnie tak, jak pojawiają się między ogranicznikami
""".
Przykłady:
// --- Literały logiczne ---
swiatlo_wlaczone = true
drzwi_zamkniete = false
// --- Literały całkowite (Dziesiętne) ---
licznik = 10
przesuniecie_ujemne = -5
// --- Literały całkowite (Szesnastkowe) ---
maska_statusu = 0xFF // Dziesiętnie 255
kolor_czerwony = 0xFF0000 // Kod koloru
// --- Literały zmiennoprzecinkowe ---
temperatura = 23.34
wspolczynnik_kalibracji = 0.95
napiecie = 12.0 // .0 wskazuje, że to Float, nie Integer
// --- Literały łańcuchowe ---
nazwa_urzadzenia = "Termostat salon"
// Łańcuch wieloliniowy (zachowuje formatowanie)
komunikat_powiadomienia = "
Ostrzeżenie: Wysoka temperatura!
Akcja: Chłodzenie aktywowane
Czas: 12:00
"
// --- Surowy literał łańcuchowy ---
configJson = """
{
"status": "sukces",
"katalog": "C:\users\volang\app",
"wzorzec": "\b[A-Z0-9._%+-]+",
"opis": "Nie trzeba escapować backslashy ani "cudzysłowów" tutaj!"
}
"""
# Wartości i typy
Volang działa na dynamicznym systemie typów, oferując elastyczne podejście do zarządzania danymi. W Volang zmienne funkcjonują jako generyczne kontenery, a nie ściśle typowane magazyny. W związku z tym nie ma potrzeby jawnego deklarowania typów (takich jak int lub float) w kodzie. Informacja o typie jest nieodłączna od samej wartości, nie od przypisanej do niej zmiennej.
# Wartości pierwszej klasy
Wszystkie wartości w Volang są wartościami pierwszej klasy. Oznacza to, że każda wartość, niezależnie od typu, może być:
- Przechowywana w zmiennych.
- Przekazywana jako argumenty do funkcji.
- Zwracana jako wyniki z funkcji.
# Spójność typów (Ścisłe typowanie)
Chociaż Volang nie wymaga deklaracji typów, wymusza stabilność typów. Gdy zmienna zostanie przypisana wartość określonego typu (np. Integer), staje się związana z tym typem. Nie można następnie przypisać wartości innego typu (np. String) do tej samej zmiennej. Ten mechanizm zapobiega typowym błędom wykonania i zapewnia przewidywalne zachowanie kontrolera.
# Kategorie wartości
Volang rozróżnia cztery podstawowe rodzaje wartości:
# 1. Numeryczne (Integer i Float)
Używane do obliczeń matematycznych, odczytów czujników i liczników.
- Integer: Liczby całkowite bez części ułamkowej (np.
0,42,-15). - Float: Liczby zmiennoprzecinkowe reprezentujące wartości ułamkowe (np.
3.14,21.5,-0.001).
# 2. Tekstowe (String)
Używane do komunikatów statusu, logów, nazw i przetwarzania tekstu. Łańcuchy to sekwencje znaków ujęte w podwójne cudzysłowy ".
- Obsługa wieloliniowa: Volang obsługuje łańcuchy wieloliniowe. Możesz nacisnąć Enter wewnątrz cudzysłowów, aby utworzyć blok tekstu rozciągający się na kilka linii.
# 3. Logiczne (Boolean)
Wartości logiczne reprezentują wartości prawdy i są kluczowe dla logiki decyzyjnej (if, while).
- Wartości:
truelubfalse.
# 4. Nieprzezroczyste (Opaque)
Wartości nieprzezroczyste reprezentują złożone struktury danych, których wewnętrzna implementacja jest zarządzana przez Volang VM. Użytkownik korzysta z nich za pośrednictwem dedykowanych funkcji Biblioteki Standardowej, a nie bezpośrednich operatorów składniowych. Przykłady typów nieprzezroczystych to m.in.:
- Array (Tablica): Uporządkowana, indeksowana kolekcja wartości.
- Map (Mapa): Kolekcja par klucz-wartość, gdzie każdy klucz jest unikalny.
To tylko niektóre z dostępnych typów nieprzezroczystych — Biblioteka Standardowa może udostępniać kolejne. Wartości nieprzezroczyste podlegają tym samym regułom pierwszej klasy co inne typy — mogą być przechowywane w zmiennych, przekazywane jako argumenty i zwracane z funkcji. Nie mogą jednak być używane ze standardowymi operatorami arytmetycznymi ani logicznymi.
Przykład:
// 1. Dynamiczne typowanie
status = 25.5
// 2. Łańcuchy wieloliniowe
info_konfiguracji = "Urządzenie: Termostat
Lokalizacja: Salon
Firmware: v1.2"
// 3. Przekazywanie wartości
fn logujStatus(wiadomosc) {
print(wiadomosc)
}
logujStatus(info_konfiguracji)
# Zmienne
Volang stosuje uproszczone podejście do zarządzania zmiennymi. Nie ma słów kluczowych deklaracji (takich jak var, let lub int). Zmienna jest tworzona w momencie przypisania wartości do nazwy. Miejsce, w którym definiujesz zmienną, określa jej zakres (widoczność) w całym skrypcie.
# Tworzenie zmiennych
Aby zdefiniować zmienną, po prostu użyj operatora przypisania =. Typ danych zmiennej jest wnioskowany z przypisanej wartości i staje się stały na czas życia tej zmiennej.
Składnia:
nazwa_zmiennej = wartosc
# Zakres zmiennych
Zakres określa, gdzie zmienna może być dostępna w kodzie. Volang rozróżnia dwa typy zakresu:
# Zmienne globalne
Zmienne zdefiniowane na najwyższym poziomie skryptu (poza jakąkolwiek funkcją lub blokiem sterującym) są Globalne.
- Widoczność: Są dostępne z dowolnego miejsca w skrypcie, ale nie wewnątrz funkcji. Aby użyć wartości globalnej w funkcji, przekaż ją jako argument.
- Zastosowanie: Idealne do przechowywania ogólnego stanu urządzenia, takiego jak
temperatura_docelowa,tryb_systemulubstatus_alarmu.
# Zmienne lokalne
Zmienne zdefiniowane wewnątrz funkcji (fn) lub bloku sterującego są Lokalne.
- Widoczność: Istnieją tylko w bloku, w którym zostały utworzone. Są niszczone, gdy wykonanie opuszcza ten blok.
- Zastosowanie: Używane do tymczasowych obliczeń, liczników pętli lub pośrednich kroków logicznych.
Przykład:
// --- Zmienne globalne ---
status_systemu = "BEZCZYNNY"
nastawiona = 21.5
// Zmienne globalne NIE SĄ dostępne wewnątrz funkcji.
// Przekaż je jako argumenty:
fn sprawdzTemperature(aktualna_temp, cel) {
// --- Zmienna lokalna ---
// 'roznica' istnieje tylko wewnątrz tej funkcji
roznica = aktualna_temp - cel
if roznica > 1.0 {
return "Wymagane chłodzenie"
}
return "Stabilna"
}
// Przekaż globalną 'nastawiona' jako argument
wynik = sprawdzTemperature(22.8, nastawiona)
// 'roznica' nie jest tutaj dostępna
# Komentarze
Komentarze to fragmenty kodu ignorowane przez kompilator Volang. Służą do wyjaśniania logiki, pozostawiania notatek dla innych programistów lub tymczasowego wyłączania określonych części kodu podczas testowania.
# Komentarze jednoliniowe
Komentarze jednoliniowe zaczynają się od dwóch ukośników //. Cały tekst następujący po tych znakach do końca linii jest traktowany jako komentarz.
# Komentarze wieloliniowe
Komentarze wieloliniowe (blokowe) zaczynają się od /* i kończą na */. Wszystko między tymi znacznikami jest ignorowane, niezależnie od liczby linii.
Przykład:
// To jest komentarz jednoliniowy
tempDocelowa = 21.5 // Komentarz na końcu linii
/*
To jest komentarz wieloliniowy.
Przydatny do opisywania złożonej logiki
lub nagłówków bloków.
*/
/* staraLogika = 10
if (staraLogika > 5) {
// Ten kod jest wyłączony
}
*/
# Instrukcje
Volang jest zaprojektowany, aby minimalizować szum wizualny i maksymalizować czytelność. W przeciwieństwie do języków takich jak C++ czy Java, Volang nie używa średników (;) do oznaczania końca instrukcji.
Znak nowej linii (naciśnięcie Enter) służy jako definitywny terminator instrukcji. Kompilator interpretuje koniec linii jako zakończenie bieżącego polecenia.
# Przypisania
Operatory przypisania służą do przechowywania wartości w zmiennej. Volang obsługuje standardowy podstawowy operator przypisania oraz złożone operatory przypisania.
# Podstawowe przypisanie
Operator = przypisuje wartość z prawej strony do zmiennej po lewej stronie.
temp_docelowa = 21.0
licznik_zdarzen = 0
# Złożone operatory przypisania
Złożone operatory wykonują operację na bieżącej wartości zmiennej, a następnie aktualizują ją nowym wynikiem.
| Operator | Opis |
|---|---|
+= | Dodaj i przypisz |
-= | Odejmij i przypisz |
*= | Pomnóż i przypisz |
/= | Podziel i przypisz |
Kluczowe zachowania:
- Wymaganie inicjalizacji: Dla złożonych operatorów zmienna po lewej stronie musi już istnieć.
- Promocja typów: Złożone przypisanie podlega tym samym regułom promocji typów co standardowa arytmetyka.
Przykłady:
// Inkrementacja licznika
licznik_zdarzen += 1
// Zmniejszenie nastawy
temp_docelowa -= 0.5
// Skalowanie jasności do 80%
jasnosc = 100
jasnosc *= 0.8 // Wynik: 80.0
// Akumulacja energii
calkowita_energia_kwh = 0.0
biezace_zuzycie = 1.5
calkowita_energia_kwh += biezace_zuzycie
# Sterowanie przepływem
Instrukcja if jest podstawową strukturą decyzyjną w Volang. Wyrażenie warunkowe musi zwracać wartość logiczną (true lub false). Wartości innych typów (takie jak liczby całkowite, zmiennoprzecinkowe czy łańcuchy tekstowe) nie są niejawnie konwertowane — użycie ich jako warunku spowoduje błąd kompilacji.
# Podstawowy if
if (warunek) {
// Kod do wykonania jeśli warunek jest prawdziwy
}
# if z else
if (warunek) {
// Kod do wykonania jeśli warunek jest prawdziwy
} else {
// Kod do wykonania jeśli warunek jest fałszywy
}
# Łańcuchowe warunki (else if)
if (warunek_1) {
// Wykonuje się jeśli warunek_1 jest prawdziwy
} else if (warunek_2) {
// Wykonuje się jeśli warunek_1 jest fałszywy I warunek_2 jest prawdziwy
} else {
// Wykonuje się jeśli wszystkie powyższe warunki są fałszywe
}
Przykład - Logika termostatu:
aktualna_temp = 19.0
temp_docelowa = 21.0
histereza = 0.5
if (aktualna_temp < (temp_docelowa - histereza)) {
// Za zimno -> Włącz ogrzewanie
ustawOgrzewanie(true)
} else if (aktualna_temp > (temp_docelowa + histereza)) {
// Za gorąco -> Włącz chłodzenie
ustawChlodzenie(true)
} else {
// Temperatura w optymalnym zakresie
ustawOgrzewanie(false)
ustawChlodzenie(false)
}
# Pętle
Volang obsługuje jedną konstrukcję pętli: pętlę while. W języku nie ma pętli for ani do-while. Podobnie jak w przypadku if, wyrażenie warunkowe musi zwracać wartość logiczną (true lub false).
Składnia:
while (warunek) {
// Kod do powtarzalnego wykonania
}
Volang obsługuje słowo kluczowe break wewnątrz pętli. Gdy napotkane zostanie break, pętla jest natychmiast zakończona.
W systemach wbudowanych i kontrolerach inteligentnego domu nieskończone pętle mogą być niebezpieczne. Volang VM monitoruje czas wykonania, ale to twoja odpowiedzialność, aby upewnić się, że warunek pętli ostatecznie stanie się false lub że zostanie osiągnięta instrukcja break.
Przykłady:
// Podstawowy licznik
licznik = 5
while (licznik > 0) {
logujWartosc(licznik)
licznik -= 1
}
// Oczekiwanie z limitem czasu (używając break)
proby = 0
max_ponowien = 10
sukces = false
while (proby < max_ponowien) {
if (sprawdzPolaczenie()) {
sukces = true
break
}
proby += 1
}
if (sukces) {
rozpocznijProces()
}
# Funkcje
Funkcje to wielokrotnego użytku bloki kodu zaprojektowane do wykonywania określonego zadania. W Volang funkcje definiuje się przy użyciu słowa kluczowego fn.
Składnia:
fn nazwaFunkcji(argument1, argument2) {
// Kod do wykonania
}
Kluczowe zasady:
- Argumenty: Funkcja może przyjmować wiele argumentów (oddzielonych przecinkami) lub nie przyjmować żadnych.
- Wartości zwracane: Użyj
return, aby odesłać wartość. Jeśli pominięty, funkcja nie zwraca nic (void). - Ograniczenia nazewnictwa: Nazwy funkcji muszą być różne od nazw funkcji Biblioteki Standardowej.
- Zakres: Zmienne zdefiniowane wewnątrz funkcji są lokalne dla tej funkcji.
- Brak dostępu do globali: Zmienne globalne nie są dostępne wewnątrz funkcji. Wszelkie zewnętrzne dane potrzebne funkcji muszą być przekazane jako argumenty.
Przykład:
fn obliczSrednia(a, b, c) {
suma = a + b + c
return suma / 3
}
srednia = obliczSrednia(20.5, 21.0, 19.5)
# Wyrażenia
# Binarne operatory arytmetyczne
| Operator | Opis |
|---|---|
+ | Dodawanie |
- | Odejmowanie |
* | Mnożenie |
/ | Dzielenie |
% | Modulo |
Priorytet: Mnożenie, Dzielenie i Modulo są obliczane przed Dodawaniem i Odejmowaniem.
Przykłady:
// Kalibracja czujnika
surowa_wartosc = 50
wspolczynnik_skali = 1.2
przesuniecie = 5.0
skalibrowana_wartosc = surowa_wartosc * wspolczynnik_skali + przesuniecie // 65.0
// Średnia z nawiasami
srednia_temp = (temp_1 + temp_2 + temp_3) / 3
// Modulo dla zdarzeń cyklicznych
reszta = licznik_petli % 10
# Binarne operatory logiczne
| Operator | Opis |
|---|---|
and | Logiczne I - Zwraca true tylko jeśli oba operandy są true |
or | Logiczne LUB - Zwraca true jeśli przynajmniej jeden operand jest true. Oba operandy są zawsze wyliczane (brak skróconej ewaluacji). |
Przykłady:
// Warunkowe oświetlenie (AND)
if (ruch_aktywny and poziom_jasnosci < prog) {
ustawSwiatlo(true)
}
// Redundantne wyzwalanie (OR)
if (drzwi_otwarte or wibracja_okna) {
wyzwolAlarm()
}
// Złożona logika (Grupowanie)
ogrzewanie_wl = reczne_nadpisanie or (jest_weekend and uzytkownik_w_domu)
# Binarne operacje bitowe
Operacje bitowe umożliwiają bezpośrednią manipulację pojedynczymi bitami w wartościach całkowitych.
| Operator | Opis |
|---|---|
& | Bitowe AND |
| | Bitowe OR |
xor | Bitowe XOR |
shl | Przesunięcie w lewo |
shr | Przesunięcie w prawo |
Przykład - Bitowe AND do maskowania:
wartosc_czujnika = 0xA7 // Binarnie: 1010 0111
maska = 0x0F // Binarnie: 0000 1111
// Wyodrębnij tylko dolne 4 bity
dolny_nibble = wartosc_czujnika & maska
// Wynik: 0x07 (Binarnie: 0000 0111)
