diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4cebbde..7f8d803 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,6 +36,80 @@ jobs: with: submodules: true + - name: Verify submodules (Unix) + if: runner.os != 'Windows' + shell: bash + run: | + echo "Checking submodules..." + if [ -d "pain-compiler" ]; then + echo "pain-compiler exists" + ls -la pain-compiler/resources/icons/linux/ || echo "ERROR: pain-compiler icons directory not found" + else + echo "ERROR: pain-compiler submodule not found" + exit 1 + fi + if [ -d "pain-lsp" ]; then + echo "pain-lsp exists" + ls -la pain-lsp/resources/icons/linux/ || echo "ERROR: pain-lsp icons directory not found" + else + echo "ERROR: pain-lsp submodule not found" + exit 1 + fi + + - name: Verify submodules (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + Write-Host "Checking submodules..." + if (Test-Path "pain-compiler") { + Write-Host "pain-compiler exists" + if (Test-Path "pain-compiler/resources/icons/linux") { + Get-ChildItem "pain-compiler/resources/icons/linux" | Format-Table + } else { + Write-Host "ERROR: pain-compiler icons directory not found" + } + } else { + Write-Host "ERROR: pain-compiler submodule not found" + exit 1 + } + if (Test-Path "pain-lsp") { + Write-Host "pain-lsp exists" + if (Test-Path "pain-lsp/resources/icons/linux") { + Get-ChildItem "pain-lsp/resources/icons/linux" | Format-Table + } else { + Write-Host "ERROR: pain-lsp icons directory not found" + } + } else { + Write-Host "ERROR: pain-lsp submodule not found" + exit 1 + } + + - name: Update version from tag + if: github.ref_type == 'tag' + shell: bash + run: | + # Extract version from tag (github.ref_name already contains tag name without refs/tags/) + # Remove 'v' prefix if present + TAG_NAME="${{ github.ref_name }}" + if [[ "$TAG_NAME" == v* ]]; then + VERSION="${TAG_NAME#v}" + else + VERSION="$TAG_NAME" + fi + echo "Updating version to: $VERSION" + + # Update version in workspace Cargo.toml + if [[ "$RUNNER_OS" == "Windows" ]]; then + # Windows - use PowerShell for file operations + powershell -Command "(Get-Content Cargo.toml) -replace 'version = \".*\"', 'version = \"$VERSION\"' | Set-Content Cargo.toml" + powershell -Command "Select-String -Path Cargo.toml -Pattern '^version = '" + else + # Unix (Linux/macOS) - use sed + sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" Cargo.toml + rm -f Cargo.toml.bak + grep "^version = " Cargo.toml + fi + - name: Install Rust uses: dtolnay/rust-toolchain@stable diff --git a/.gitignore b/.gitignore index c3a6d0a..2aac05a 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,8 @@ hello.pain utils/__pycache__/ utils/*.pyc +# Development tasks tracking (local only) +TASKS.MD +TASKS.md +tasks.md + diff --git a/CHANGELOG.md b/CHANGELOG.md index 2406009..5ed4c50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,52 @@ All notable changes to the Pain programming language will be documented in this The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.0] - 2025-01-XX + +### Added + +#### PML (Pain Markup Language) v0.1 +- **PML Parser** - Complete lexer and parser implementation for PML format + - Tab-based indentation parsing (1 TAB = 1 level) + - Support for SCALAR, MAP, and LIST node types + - Comment parsing with `#` syntax + - String, int, float, bool, and null value types + - Escape sequence support (`\n`, `\t`, `\r`, `\\`, `\"`, `\'`) + - Comprehensive error handling with detailed error messages +- **PML Standard Library** - Runtime functions for PML integration + - `pml_load_file(path: str) -> dynamic` - Load and parse PML files + - `pml_parse(source: str) -> dynamic` - Parse PML strings +- **PML Documentation** - Complete specification and examples + - PML v0.1 specification document (`docs/PML_SPEC.md`) + - PML examples and usage guide (`docs/examples_pml.md`) + - 40+ unit tests covering edge cases (Unicode, long strings, deep nesting, etc.) + - 5 integration tests for stdlib functions + - Performance benchmarks (10 benchmarks documented) + +#### VS Code Extension Enhancements +- **PML Language Support** - Full syntax highlighting for `.pml` files + - TextMate grammar for PML syntax + - Language configuration for PML + - PML file association and recognition +- **Custom File Icons** - Visual file type identification + - Custom PNG icons for `.pain` files + - Custom PNG icons for `.pml` files + - Icon theme configuration for VS Code/Cursor +- **Extension Distribution** - Ready for VSIX distribution via repository + +### Changed +- Improved error handling in PML parser with Display trait for `PmlParseError` +- Enhanced code quality (clippy, fmt) - all checks passing +- Performance profiling and analysis document created (`.pain_dev_docs/PML_PERFORMANCE.md`) + +### Testing +- Expanded PML parser test coverage (40 unit tests) +- Added PML integration tests (5 tests for stdlib functions) +- Edge case testing (15+ edge cases: Unicode, long strings, deep nesting, etc.) +- Performance benchmarking suite (10 benchmarks) + +[0.2.0]: https://github.com/pain-lng/pain/releases/tag/v0.2.0 + ## [0.1.0] - 2025-11-20 ### Added diff --git a/Cargo.toml b/Cargo.toml index 6f5be5f..8ee3c6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = ["Pain Team"] license = "MIT OR Apache-2.0" diff --git a/RELEASE_NOTES_v0.2.0.md b/RELEASE_NOTES_v0.2.0.md new file mode 100644 index 0000000..bed7f8f --- /dev/null +++ b/RELEASE_NOTES_v0.2.0.md @@ -0,0 +1,110 @@ +# Pain v0.2.0 Release Notes + +**Release Date:** January 2025 +**Release Type:** Feature Release (Alpha) + +## Overview + +Pain v0.2.0 introduces **PML (Pain Markup Language)** v0.1, a minimalistic declarative data format designed for UI structures, configurations, and tree data. This release also includes significant enhancements to the VS Code/Cursor extension with PML support and custom file icons. + +## 🎉 Major Features + +### PML (Pain Markup Language) v0.1 + +PML is a YAML-inspired but simplified markup language with strict rules for predictability, ease of parsing, and high performance. + +**Key Features:** +- **Tab-based indentation** - Strict 1 TAB = 1 level rule +- **Three node types**: SCALAR, MAP, LIST +- **Type support**: strings, integers, floats, booleans, null +- **Comment support** with `#` syntax +- **Escape sequences** for strings (`\n`, `\t`, `\r`, `\\`, `\"`, `\'`) +- **Simple syntax**: `key: value` for maps, `- item` for lists + +**Standard Library Functions:** +- `pml_load_file(path: str) -> dynamic` - Load and parse PML files +- `pml_parse(source: str) -> dynamic` - Parse PML strings + +**Example Usage:** +```pain +fn main(): + let config = pml_load_file("config.pml") + let app_name = config.app.name + print("Starting " + app_name) +``` + +**Documentation:** +- Complete specification: [`docs/PML_SPEC.md`](docs/PML_SPEC.md) +- Examples and usage: [`docs/examples_pml.md`](docs/examples_pml.md) + +### VS Code/Cursor Extension Enhancements + +**PML Language Support:** +- Full syntax highlighting for `.pml` files +- TextMate grammar for PML syntax +- Language configuration and file associations + +**Custom File Icons:** +- Custom PNG icons for `.pain` files +- Custom PNG icons for `.pml` files +- Icon theme configuration for better visual identification + +**Distribution:** +- Extension ready for VSIX distribution via repository +- Version 0.2.0 with PML support + +## 📊 Testing & Quality + +- **40+ unit tests** for PML parser covering edge cases +- **5 integration tests** for PML stdlib functions +- **15+ edge cases** tested: Unicode, long strings, deep nesting, etc. +- **10 performance benchmarks** documented +- All code quality checks passing (clippy, fmt) + +## 📚 Documentation + +- PML v0.1 specification document +- PML examples and usage guide +- Performance analysis document (`.pain_dev_docs/PML_PERFORMANCE.md`) + +## 🔧 Technical Details + +**PML Parser Implementation:** +- ~250-350 lines of parser code +- Comprehensive error handling with detailed messages +- High-performance parsing with minimal allocations +- Support for deeply nested structures (tested up to 10+ levels) + +**Performance:** +- Fast parsing of simple maps: < 1μs +- Efficient handling of large structures (100+ keys/items) +- Optimized for common use cases (UI configs, app settings) + +## 🚀 Migration Guide + +No breaking changes from v0.1.0. This is a purely additive release. + +**To use PML:** +1. Create `.pml` files with your data structures +2. Use `pml_load_file()` or `pml_parse()` in your Pain code +3. Access parsed data as dynamic objects + +**VS Code Extension:** +- Update to v0.2.0 to get PML syntax highlighting +- Custom icons will appear automatically for `.pain` and `.pml` files + +## 📦 What's Next + +**Planned for v0.3.0:** +- PML v0.2: inline strings, extended escape sequences, nullable values +- Tensor support for ML workloads +- Python bridge for data science workflows + +## 🙏 Acknowledgments + +Thanks to all contributors and testers who helped make this release possible! + +--- + +**Full Changelog:** See [CHANGELOG.md](CHANGELOG.md) for complete list of changes. + diff --git a/docs/PML_SPEC.md b/docs/PML_SPEC.md new file mode 100644 index 0000000..e8ed547 --- /dev/null +++ b/docs/PML_SPEC.md @@ -0,0 +1,282 @@ +# PML v0.1 Specification — Pain Markup Language + +PML (Pain Markup Language) — это минималистичный декларативный формат данных, разработанный для: +- описания UI-структур в Pain +- хранения конфигураций +- работы с древовидными данными +- использования внутри C++/Rust/Pain-рантайма + +PML вдохновлён YAML, но избавлен от его сложностей. PML использует строгие правила, обеспечивая предсказуемость, простоту парсинга и высокую производительность. + +## Основные концепции + +PML описывает данные через комбинацию: +- **SCALAR** — простые значения (строки, числа, булевы, null) +- **MAP** — словари (ключ-значение) +- **LIST** — списки + +Структура определяется уровнем вложенности, выраженным табуляцией. + +## Правила синтаксиса PML + +### 1. Отступы + +Уровень вложенности определяется **только TAB**. + +- 1 TAB = 1 уровень +- Пробелы в начале строки запрещены +- Смешивание табов и пробелов не допускается + +Пример: + +```pml +window: + title: "Demo" + width: 400 +``` + +### 2. Комментарии + +Комментарии начинаются с `#`, всё после символа — игнорируется. + +```pml +width: 400 # ширина окна +# Это полный комментарий +title: "Hello" +``` + +### 3. Пары "ключ: значение" + +Формат: `ключ: значение` + +- Пробел после двоеточия обязателен +- Ключ — строка без пробелов +- Значение может быть на той же строке или на следующей (с отступом) + +Примеры: + +```pml +title: "Hello" +width: 400 +mode: prod +``` + +### 4. Скаляры + +Поддерживаемые типы: + +| Тип | Пример | +|-----|--------| +| string | `"Hello"` или `hello` | +| int | `123` | +| float | `3.14` | +| bool | `true`, `false` | +| null | `null` | + +**Правила строк:** + +- Если строка содержит пробелы, `:`, `#` → обязательны кавычки +- В остальных случаях — опционально +- Поддерживаются escape-последовательности: `\n`, `\t`, `\r`, `\\`, `\"`, `\'` + +Примеры: + +```pml +caption: "Clicks: 0" +mode: release +path: "C:/Program Files/Pain" +message: "Line 1\nLine 2" +``` + +### 5. MAP (словари) + +MAP создаётся ключом с двоеточием: + +```pml +window: + title: "Pain Demo" + width: 400 + height: 300 +``` + +Значение после `:` может быть пустым → значит MAP или LIST начинается на следующем уровне: + +```pml +config: + database: + host: "localhost" + port: 5432 +``` + +### 6. LIST (списки) + +Список определяется ключом + блоком `-`: + +```pml +children: + - type: label + text: "Hello" + - type: button + text: "OK" + on_click: on_ok +``` + +Каждый `-` может содержать: +- SCALAR +- MAP (как в примере выше) + +Список скаляров: + +```pml +items: + - "apple" + - "banana" + - "orange" +``` + +LIST всегда находится под MAP-ключом. + +## AST (внутреннее представление PML) + +Рекомендуемая структура: + +```rust +enum PmlNodeKind { + Scalar, + Map, + List, +} + +struct PmlNode { + kind: PmlNodeKind, + scalar: Option, // если SCALAR + map: Option>, // если MAP + list: Option>, // если LIST +} +``` + +## Ограничения PML v0.1 + +Для упрощения парсинга: + +- ❌ Нет многострочных строк +- ❌ Нет inline списков `[a, b]` +- ❌ Нет inline map `{a: 1}` +- ❌ Нет YAML-ссылок (`&alias`, `*alias`) +- ❌ Нет типов `!!str`, `!!int` +- ❌ Нет явных тэгов +- ❌ Нет имён пространств, как в XML + +PML — минимальный, строгий, простой. + +## Примеры использования + +### Простой конфиг + +```pml +app: + name: "My App" + version: "1.0.0" + debug: false +``` + +### UI структура + +```pml +window: + id: main_window + title: "Pain Demo" + width: 400 + height: 300 + layout: + type: vbox + spacing: 8 + padding: 12 + children: + - type: label + id: label_counter + text: "Clicks: 0" + - type: button + id: button_click + text: "Click me" + on_click: on_btn_click +``` + +### Использование в Pain + +```pain +fn main(): + let doc = pml_load_file("ui/main_window.pml") + let window = doc.window + let title = window.title + print("Window: " + title) +``` + +Или через парсинг строки: + +```pain +fn main(): + let pml_source = "title: \"Hello\"\nwidth: 400" + let doc = pml_parse(pml_source) + print("Parsed PML successfully!") +``` + +## API Reference + +### `pml_load_file(path: str) -> dynamic` + +Загружает и парсит PML файл, возвращает распарсенное значение. + +**Параметры:** +- `path` — путь к PML файлу + +**Возвращает:** +- `dynamic` — распарсенное PML дерево (обычно Object с полями) + +**Пример:** + +```pain +let config = pml_load_file("config.pml") +``` + +### `pml_parse(source: str) -> dynamic` + +Парсит PML строку и возвращает распарсенное значение. + +**Параметры:** +- `source` — PML исходный код + +**Возвращает:** +- `dynamic` — распарсенное PML дерево + +**Пример:** + +```pain +let doc = pml_parse("title: \"Hello\"\nwidth: 400") +``` + +## Roadmap + +### v0.2 (позже) +- inline строки `"строка"` без ограничений +- escape-последовательности (расширенная поддержка) +- nullable значения (`key:` → `null`) + +### v0.3 +- anchors? (под вопросом) +- мини-шаблоны +- include-директивы `!include` + +## Парсер PML — базовый алгоритм + +1. Разбить текст на строки +2. Игнорировать пустые и комментарии +3. Определить `indent_level` по табам +4. Поддерживать стек узлов для вложенности +5. Если строка содержит `-` → LIST +6. Если строка содержит `key:` → MAP +7. Если справа есть значение → SCALAR +8. Строить дерево `PmlNode` + +Весь парсер ≈ 250–350 строк. + diff --git a/docs/examples_pml.md b/docs/examples_pml.md new file mode 100644 index 0000000..a568ee9 --- /dev/null +++ b/docs/examples_pml.md @@ -0,0 +1,119 @@ +# PML Examples + +Примеры использования PML (Pain Markup Language) в различных сценариях. + +## 1. Простой конфиг + +**config.pml:** + +```pml +app: + name: "My Application" + version: "1.0.0" + debug: false + port: 8080 +``` + +**Использование в Pain:** + +```pain +fn main(): + let config = pml_load_file("config.pml") + let app_name = config.app.name + let port = config.app.port + print("Starting " + app_name + " on port " + to_string(port)) +``` + +## 2. UI структура + +**ui/main_window.pml:** + +```pml +window: + id: main_window + title: "Pain Demo" + width: 400 + height: 300 + layout: + type: vbox + spacing: 8 + padding: 12 + children: + - type: label + id: label_counter + text: "Clicks: 0" + - type: button + id: button_click + text: "Click me" + on_click: on_btn_click +``` + +**Использование в Pain:** + +```pain +fn main(): + let doc = pml_load_file("ui/main_window.pml") + let window = doc.window + let title = window.title + let width = window.width + let height = window.height + print("Window: " + title + " (" + to_string(width) + "x" + to_string(height) + ")") +``` + +## 3. Список элементов + +**items.pml:** + +```pml +items: + - "apple" + - "banana" + - "orange" +``` + +**Использование в Pain:** + +```pain +fn main(): + let doc = pml_load_file("items.pml") + let items = doc.items + # items is a List + print("Items loaded: " + to_string(len(items))) +``` + +## 4. Вложенные структуры + +**database.pml:** + +```pml +database: + host: "localhost" + port: 5432 + credentials: + username: "admin" + password: "secret" + options: + ssl: true + timeout: 30 +``` + +## 5. Парсинг строки + +```pain +fn main(): + let pml_source = "title: \"Hello\"\nwidth: 400\nheight: 300" + let doc = pml_parse(pml_source) + print("Parsed PML successfully!") +``` + +## 6. Комментарии + +```pml +# Application configuration +app: + name: "My App" # Application name + version: "1.0.0" + # Debug mode + debug: false +``` + diff --git a/pain-compiler b/pain-compiler index e6c9fb4..80a329b 160000 --- a/pain-compiler +++ b/pain-compiler @@ -1 +1 @@ -Subproject commit e6c9fb41cc30d64d7133e97cb1f240793d99ba60 +Subproject commit 80a329b0a59aaa5fac2caabd0ce56185db011351 diff --git a/utils/convert_icons.py b/utils/convert_icons.py index ac57895..fcfe916 100644 --- a/utils/convert_icons.py +++ b/utils/convert_icons.py @@ -60,10 +60,20 @@ def _collect_sources(png_dir: Path, base_name: str): def create_ico_from_pngs(png_dir: Path, output_path: Path, base_name: str): """Create ICO file from multiple PNG sizes.""" + print(f" Looking for PNG files in: {png_dir}") + print(f" Base name: {base_name}") source_images = _collect_sources(png_dir, base_name) if not source_images: - print(f"Warning: No PNG files found for {base_name}, skipping ICO") + print(f"ERROR: No PNG files found for {base_name} in {png_dir}") + print(f" Expected pattern: {base_name}_*x*.png or {base_name}.png") + if png_dir.exists(): + print(f" Directory exists. Contents:") + for item in png_dir.iterdir(): + print(f" - {item.name}") + else: + print(f" Directory does not exist!") return False + print(f" Found {len(source_images)} PNG source(s): {sorted(source_images.keys())}") entries = [] sorted_sizes = sorted(source_images) largest = sorted_sizes[-1] @@ -199,14 +209,24 @@ def main(): for config in icon_configs: print(f"\nProcessing {config['label']} icons...") linux_dir = config["linux"] + print(f" Linux icons directory: {linux_dir}") + print(f" Directory exists: {linux_dir.exists()}") if not linux_dir.exists(): - print(f"Warning: Linux icons directory not found: {linux_dir}") - print("Warning: Skipping this icon set") - continue + print(f"ERROR: Linux icons directory not found: {linux_dir}") + print(f" Absolute path: {linux_dir.resolve()}") + print("ERROR: Skipping this icon set") + sys.exit(1) for target in config.get("windows", []): target.parent.mkdir(parents=True, exist_ok=True) - create_ico_from_pngs(linux_dir, target, config["base"]) + success = create_ico_from_pngs(linux_dir, target, config["base"]) + if not success: + print(f"ERROR: Failed to create {target}") + sys.exit(1) + if not target.exists(): + print(f"ERROR: Icon file was not created: {target}") + sys.exit(1) + print(f"Verified: {target} exists ({target.stat().st_size} bytes)") for target in config.get("macos", []): target.parent.mkdir(parents=True, exist_ok=True)