diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4dbcebc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "plic" +version = "3.2.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +base64 = "0.21" +chrono = "0.4" +once_cell = "1.19" +native-tls = "0.2" +reqwest = { version = "0.11", features = ["blocking", "json"] } +rustyline = "10.0" +hex = "0.4" +sha2 = "0.10" +pqcrypto-kyber = "0.7" +pqcrypto-traits = "0.3" +rand = "0.8" +rcgen = "0.10" +anyhow = "1.0" +thiserror = "1.0" +aes-gcm = "0.10" +codespan = "0.11" +codespan-reporting = "0.11" +ctrlc = "3.4" + +[[bin]] +name = "plic" +path = "src/main.rs" diff --git a/README.md b/README.md index f584a0b..f0d159c 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,152 @@ -# plic (langjam) -## Цель +# PLIC -Разработать язык программирования и реализовать на нём многопользовательский чат. +**PLIC** — это динамический язык программирования, сочетающий функциональное, императивное и объектно‑ориентированное программирование. Встроенная поддержка P2P‑сообщений и файлового обмена позволяет использовать его как платформу для децентрализованных чат‑приложений. ---- +## Особенности -## Что нужно сделать +- **Каррирование** всех функций (как в Haskell). +- **Единый числовой тип `Num`** (Int и Float автоматически объединены). +- **Блоки на основе отступов** (как в Python) вместо фигурных скобок. +- **Списковые включения** (`[expr for x in iterable if cond]`). +- **Цикл `loop` и оператор `break`** (с возвратом значения). +- **Клиент‑серверная модель чата** (сервер хранит только контакты, вся история локальна). +- **Упоминания** (`@user`) в сообщениях. +- **F‑строки** с подсветкой выражений внутри `{}`. +- **Подробные отчёты об ошибках** с указанием файла, строки, столбца и фрагментом кода. -### 1. Язык программирования -- Придумать синтаксис и семантику -- Написать компилятор или интерпретатор -- Обеспечить работу на выбранной платформе +## Требования -### 2. Проект на этом языке — Чат-комната -- Пользователи могут подключаться к чату -- Отправлять сообщения всем участникам -- Видеть историю сообщений -- Выходить из чата +- [Rust](https://rustup.rs/) (стабильная версия 1.70 или новее) +- Cargo (входит в Rust) ---- +## Установка -## Технические требования +### Быстрая установка (скрипт) -### Платформа -- **Приоритет** — AtomVM (Erlang-совместимая виртуальная машина): https://atomvm.org/ -- Допустимы альтернативы (JVM, .NET, CPython, собственная VM и т.д.) +В корне репозитория выполните: -### Язык должен поддерживать -- Переменные и типы данных -- Условные конструкции -- Циклы или рекурсию -- Функции/процедуры -- Работу с коллекциями (списки, массивы, словари) +```bash +chmod +x install.sh +./install.sh +``` -Дополнительные фишки будут плюсом +Скрипт соберёт проект в режиме `release` и установит исполняемый файл в `/bin/plic` (потребуются права sudo). -### Чат должен поддерживать -- Добавление пользователя -- Отправка сообщения всем участникам -- Просмотр последних N сообщений (история) -- Удаление пользователя (выход из чата) +### Ручная сборка ---- +```bash +cargo build --release +sudo cp target/release/plic /bin/plic +``` -## Критерии оценки +После этого команда `plic` будет доступна в терминале. -| Критерий | Баллы | Описание | -|----------|-------|----------| -| Работоспособность | 5 | Код запускается и выполняет базовые функции чата | -| Дизайн языка | 5 | Синтаксис логичен, выразителен, удобен | -| Архитектура | 5 | Использование процессов/акторов, продуманность структуры | -| Качество кода | 3 | Читаемость, обработка ошибок | -| Креативность | 2 | Нестандартные решения, дополнительные фичи | +## Использование -**Максимальный балл:** 20 +### REPL (интерактивный режим) ---- +```bash +plic +``` -## Дополнительные возможности (приветствуются) +Появится приглашение `>>>`. Вводите выражения, результаты выводятся с помощью встроенной функции `show`. -- Комнаты / приватные сообщения -- Таймстемпы сообщений -- Имена пользователей -- Сохранение истории между сессиями -- Визуальный интерфейс (консольный или графический) -- Команды (/help, /kick, /clear) -- Авторизация пользователей +Пример: ---- +``` +>>> let square x = x * x +>>> show $ square 5 +25 +>>> f"2 + 2 = {2 + 2}" +"2 + 2 = 4" +``` -## Требования к сдаче +Многострочные блоки оформляются через двоеточие и отступ: -- [ ] Исходный код языка (компилятор/интерпретатор) -- [ ] Исходный код чата на этом языке -- [ ] Инструкция по запуску (1–2 абзаца) -- [ ] Краткое описание языка (синтаксис, особенности) +``` +>>> if x > 0: +... show $ "positive" +... else: +... show $ "non-positive" +``` ---- +### Запуск скриптов -## Как сдавать -Форкайте репозиторий -> добавляете директорию с названием вашего языка и выгружаете в нее проект -> делаете pull request +Передайте имя файла с расширением `.plic`: +```bash +plic script.plic +``` -## Дедлайн +Интерпретатор выполнит скрипт и завершит работу. -**Дата и время:** 20 июля +### Встроенные команды в REPL ---- +- `exit()` – завершить сеанс. +- `load "файл.plic"` – выполнить скрипт в текущем окружении (определения сохраняются). -## Приз +## Примеры -5000 RUB +### Математика и функции ---- +```plic +let add a b = a + b +let inc = add 1 +show $ inc 10 # 11 +show $sqrt 16 # 4.0 +``` -## Контакты +### Работа со списками и множествами -По всем вопросам: @k1ngmang (telegram) +```plic +let numbers = [1, 2, 3, 4, 5] +show $ map (lambda x -> x * 2) numbers # [2, 4, 6, 8, 10] +show $ filter (lambda x -> x > 2) numbers # [3, 4, 5] +let s = %[1, 2, 3] +show $ setContains s 2 # true +``` ---- +### Списковые включения -**Удачи!** +```plic +show $ [x * x for x in [1..10] if x % 2 == 0] # [4, 16, 36, 64, 100] +``` + +### Классы и объекты + +```plic +class Counter = (val = Num; inc(self) = self { val = self.val + 1 }; get(self) = self.val) +let c = new Counter(0) +show $ c.inc().get() # 1 +``` + +### Чат: запуск сервера и подключение + +```plic +# терминал 1 (сервер) +serverStart("127.0.0.1:9000", "secret") +setExternalIP("203.0.113.5") # если нужен внешний IP + +# терминал 2 (Алиса) +login(@alice) +connect("127.0.0.1:9000", @alice, "secret") +newChat("general", [@bob, @alice]) +sendChat("general", "Hello everyone!") + +# терминал 3 (Боб) +login(@bob) +connect("127.0.0.1:9000", @bob, "secret") +inbox() # покажет сообщение от Алисы +``` + +### Отправка файлов + +```plic +sendFile(@bob, "report.txt") +downloads() +saveFile(0, "received.txt") +``` + +## Документация + +Полное описание языка, всех встроенных функций и синтаксиса находится в файле `doc.md`. diff --git a/doc.md b/doc.md new file mode 100644 index 0000000..6fdf96b --- /dev/null +++ b/doc.md @@ -0,0 +1,600 @@ +## Введение +PLIC — это динамический язык программирования с функциональным ядром и встроенной поддержкой P2P-чата. Он сочетает функциональную, императивную и объектно-ориентированную парадигмы. + +Версия 3.2 поддерживает: +- **Каррирование** – все функции каррированы. +- **Тип Num** – объединяет Int и Float. +- **Блоки на основе отступов** – `:` и отступы вместо `{ }`. +- **Генераторы списков** – `[expr for var in iterable if condition]`. +- **Цикл** – `loop { ... }` и `break` с необязательным значением. +- **Чат** – клиент-серверные контакты, локальные чаты, управляющие сообщения для синхронизации. +- **Упоминания** – `@uid` в сообщениях доставляет личную копию. +- **Вывод ошибок** – файл, строка, столбец, фрагмент кода. +- **F-строки** – `f"текст {выражение} текст"` с подсветкой в REPL. +- **`load`** – импорт файлов. +- **Устойчивость к панике** – нет `unwrap`; ошибки обрабатываются. + +Все описанные функции реализованы и протестированы. + +--- + +## 1. REPL +Запуск: `cargo run` или `plic`. + +- Ввод однострочных выражений; результаты **не выводятся автоматически**. Используйте `show $ expr` для печати значения (она выводит на экран и возвращает `()`). +- Для многострочных блоков используйте двоеточие, затем строки с увеличенным отступом. Завершите блок пустой строкой или словом `end`. Блок возвращает значение последнего выражения. +- Встроенные функции управления REPL: + - `exit()` – завершает интерпретатор. + - `load(filename)` – загружает и выполняет скрипт; определения сохраняются. +- Ошибки выводятся с позицией и фрагментом кода. + +Пример: +``` +>>> let greet name = "Hello, " ++ name ++ "!" +>>> show $ greet "World" +Hello, World! +>>> let square x = x * x: +... show $ square 5 +25 +``` + +--- + +## 2. Лексика и синтаксис + +### Комментарии +- Однострочные: `# текст` +- Многострочные: `#- текст -#` (вложенные поддерживаются) + +### Идентификаторы +- Буквы, цифры, `_` (начинаются с буквы или `_`). + +### Литералы +- Целые: `42`, `-7` +- Вещественные: `3.14`, `-0.5`, `1.0e10` +- Символы: `'a'`, `'\n'` +- Строки: `"Hello"`, `"line1\nline2"` +- Булевы: `true`, `false` +- Единица: `()` +- UID: `@alice`, `@everyone` +- Списки: `[1, 2, 3]`, `[0..10]` (диапазон, не включая верхнюю границу) +- Кортежи: `(1, "ok")`, `(true, @bob)` +- Байтовые строки: `#B"48656C6C6F"` +- Длительности: `5s`, `500ms`, `2m`, `1h` +- Map-литерал: `%(key => value, ...)` +- Set-литерал: `%[elem, ...]` +- F-строка: `f"текст {выражение} текст"` + +--- + +## 3. Типы значений +- Примитивные: `Num` (Int/Float), `Char`, `String`, `Bool`, `Unit`, `Uid`, `ByteString`, `Pid`, `DateTime`, `Duration`. +- Составные: списки, кортежи, записи. +- Пользовательские: `data`, `struct`, классы. +- Коллекции: `Map` и `Set`. + +--- + +## 4. Выражения + +### 4.1. Основы +- Литералы, переменные. +- Применение функции каррировано и левоассоциативно: `f a b c` = `(((f a) b) c)`. +- Лямбда: `lambda x y -> x + y`. +- Индексация: `expr[index]` работает со списками, строками, байтовыми строками, кортежами, map (возвращает значение или `Unit`), set (возвращает `Bool`). + +### 4.2. Условный оператор +``` +if условие then выражение1 else выражение2 +``` +Только однострочный. + +### 4.3. Сопоставление с образцом +``` +case выражение of + образец1 -> выражение1 + образец2 -> выражение2 + _ -> по умолчанию +``` +Однострочный вариант с точками с запятой: +``` +case выражение of образец1 -> выражение1; образец2 -> выражение2; _ -> по умолчанию +``` + +### 4.4. Локальные определения +- `let x = 5` – определяет новую переменную (ошибка, если уже определена). +- `let f x y = x + y` – определение функции (каррировано). Запятые между параметрами **не допускаются**. +- Аннотации типов: `let N :: Num = 5`, `let add :: Num -> Num -> Num = lambda a b -> a + b`. +- Блоки используют отступы после `:`. + +### 4.5. Операторы (приоритет, от высокого к низкому) +1. `not` (унарный) +2. `*`, `/`, `%` (левоассоциативные) +3. `+`, `-` (левоассоциативные) +4. `++` (конкатенация строк/списков, правоассоциативная) +5. `:` (cons, правоассоциативная) +6. `in`, `not in` (проверка принадлежности) +7. `==`, `!=`, `<`, `>`, `<=`, `>=` +8. `and`, `or` (короткое замыкание) +9. `|>` (pipe, левоассоциативный) +Оператор `$` поддерживается (низкоприоритетное применение, правоассоциативный). + +### 4.6. Циклы +- `for x in коллекция: тело` – итерация по спискам, set, map (для map – пары ключ-значение). +- `while условие: тело` +- `loop { тело }` – бесконечный цикл; используйте `break` с необязательным значением для выхода. + +Пример: +``` +>>> let x = 0 +>>> loop { x = x + 1; if x == 5 then break x else () } +5 +``` + +### 4.7. Обработка ошибок +- `error "сообщение"` – выброс ошибки. +- `try выражение` – перехват ошибки (если есть, передаёт дальше). +- `try выражение catch образец -> обработчик` – обработка ошибки. + +Пример: +``` +try error "oops" catch e -> "caught: " ++ e +``` + +### 4.8. F-строки +Синтаксис: `f"текст {выражение} текст"`. Внутри фигурных скобок может быть любое выражение; оно вычисляется и преобразуется в строку через `show`. Двойные скобки `{{` и `}}` используются для вставки литеральных `{` и `}`. + +Пример: +``` +>>> let x = 5 +>>> f"x = {x}, x*2 = {x * 2}" +"x = 5, x*2 = 10" +``` + +### 4.9. Генераторы списков +``` +[expr for var in iterable if condition] +``` +Может содержать несколько `for` и `if`. + +Пример: +``` +>>> [x * 2 for x in [1,2,3] if x > 1] +[4, 6] +``` + +--- + +## 5. Структуры и алгебраические типы данных + +``` +struct Person = (name = String, age = Num) +data Result a = Ok a | Err String +``` + +Создание структуры: `Person(name = "Alice", age = 30)`. Доступ к полям через `.` (например, `person.name`). Обновление записи: `person { age = 31 }` (возвращает новую запись). + +--- + +## 6. Классы (ООП) + +``` +class Counter = (val = Num; inc(self) = self { val = self.val + 1 }; get(self) = self.val) +``` + +- Конструктор: `new Counter(0)` +- Наследование: `class AdvancedCounter extends Counter = (...)` (с круглыми скобками) +- Вызов метода: `counter.inc()` + +--- + +## 7. Union (ADT) + +``` +data Option a = None | Some a +``` + +Конструкторы: `Some 42`, `None`. Сопоставление с образцом работает. + +--- + +## 8. Map и Set + +### Map +- Создание: `%(key => value, ...)` +- Функции: `mapGet`, `mapSet`, `mapRemove`, `mapKeys`, `mapValues`, `mapEntries`, `mapContains`, `mapSize`, `mapFilter`, `mapMerge` +- Индексация: `map[key]` возвращает значение или `Unit` + +### Set +- Создание: `%[elem, ...]` +- Функции: `setAdd`, `setRemove`, `setContains`, `setUnion`, `setIntersection`, `setDifference`, `setSize`, `setFilter`, `setMap` +- Индексация: `set[elem]` возвращает `Bool` + +--- + +## 9. Встроенные функции + +### 9.1. Математика +- `sqrt`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan :: Num -> Num` + +### 9.2. Преобразования и интроспекция +- `show :: a -> ()` – печатает значение и возвращает `()`. +- `parseInt :: String -> Num`, `parseFloat :: String -> Num` +- `chr :: Num -> Char`, `ord :: Char -> Num` +- `typeof :: a -> String` + +### 9.3. Списки, строки, байтовые строки, Map, Set +- `null :: [a] -> Bool` +- `length :: collection -> Num` (работает со списками, строками, байтовыми строками, Map, Set, кортежами) +- `map :: (a -> b) -> [a] -> [b]` +- `filter :: (a -> Bool) -> [a] -> [a]` +- `foldl :: (b -> a -> b) -> b -> [a] -> b` +- `foldr :: (a -> b -> b) -> b -> [a] -> b` +- `take :: Num -> collection -> collection` +- `drop :: Num -> collection -> collection` +- `reverse :: [a] -> [a]` +- `all :: (a -> Bool) -> [a] -> Bool` +- `any :: (a -> Bool) -> [a] -> Bool` +- `find :: (a -> Bool) -> [a] -> Maybe a` +- `sort :: [a] -> [a]` (по строковому представлению) +- `sortBy :: (a -> a -> Num) -> [a] -> [a]` +- `sum :: [Num] -> Num` +- `concat :: [[a]] -> [a]` +- `flatten :: [[a]] -> [a]` +- `zip :: [a] -> [b] -> [(a,b)]` +- `zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]` +- `unzip :: [(a,b)] -> ([a],[b])` +- `indexOf :: [a] -> a -> Maybe Num` +- `lastIndexOf :: [a] -> a -> Maybe Num` + +### 9.4. Строковые функции +- `split :: String -> String -> [String]` +- `join :: String -> [String] -> String` +- `startsWith :: String -> String -> Bool` +- `endsWith :: String -> String -> Bool` +- `trim :: String -> String` +- `replace :: String -> String -> String -> String` +- `substring :: Num -> Num -> String -> String` + +### 9.5. JSON +- `parseJson :: String -> JsonValue` +- `encodeJson :: JsonValue -> String` +- `lookup :: String -> JsonValue -> Maybe JsonValue` + +### 9.6. Время +- `formatTime :: String -> DateTime -> String` +- `parseTime :: String -> String -> DateTime` +- `addDuration :: DateTime -> Duration -> DateTime` +- `diffDuration :: DateTime -> DateTime -> Duration` +- `now :: () -> DateTime` + +### 9.7. Байтовые строки +- `packBytes :: [Num] -> ByteString` +- `unpackBytes :: ByteString -> [Num]` + +### 9.8. Ввод-вывод и файлы +- `putStrLn :: String -> ()` – печатает строку. +- `getLine :: () -> String` +- `getArgs :: () -> [String]` +- `readFile :: String -> String` +- `readBinaryFile :: String -> ByteString` +- `writeFile :: String -> String -> ()` +- `appendFile :: String -> String -> ()` +- `writeBinaryFile :: String -> ByteString -> ()` +- `fileExists :: String -> Bool` +- `fileSize :: String -> Num` +- `listDir :: String -> [String]` +- `createDir :: String -> ()` +- `removeDir :: String -> ()` +- `fileMove :: String -> String -> ()` +- `filePermissions :: String -> Num` +- `setFilePermissions :: String -> Num -> ()` + +### 9.9. Сеть +- `fetch :: String -> FetchResult` +- `fetchOpts :: FetchOptions -> FetchResult` + +### 9.10. Чат и контакты +- `login :: Uid -> ()` +- `logout :: () -> ()` +- `deleteUser :: Uid -> ()` +- `newChat :: String -> [Uid] -> String` +- `addMember :: Uid -> String -> ()` +- `removeMember :: Uid -> String -> ()` +- `deleteChat :: String -> ()` +- `open :: String -> ()` +- `send :: Uid -> String -> Bool` (личное сообщение) +- `sendFile :: Uid -> String -> Bool` +- `sendChat :: String -> String -> Bool` – отправка в чат с поддержкой упоминаний. +- `sendFileToChat :: String -> String -> Bool` +- `inbox :: () -> [ChatMsg]` +- `history :: String -> [ChatMsg]` +- `downloads :: () -> [FileTransfer]` +- `saveFile :: Num -> String -> Bool` +- `listChats :: () -> [String]` +- `members :: String -> [Uid]` +- `serverStart :: String -> (String?) -> ()` +- `serverStop :: () -> ()` +- `connect :: String -> Uid -> (String?) -> Num` +- `getPublicIP :: () -> Maybe String` +- `setExternalIP :: String -> ()` +- `addContact :: Uid -> String -> ()` +- `removeContact :: Uid -> ()` + +### 9.11. Процессы +- `spawn :: (() -> ()) -> Pid` +- `procSelf :: () -> Pid` +- `procSend :: Pid -> a -> ()` +- `procRecv :: () -> a` +- `procWait :: Pid -> a` +- `procExit :: a -> ()` +- `sleep :: Duration -> ()` +- `after :: Duration -> (() -> ()) -> ()` + +### 9.12. Maybe +- `Nothing :: Maybe a` +- `Just :: a -> Maybe a` +- `maybe :: (a -> b) -> b -> Maybe a -> b` + +### 9.13. Map и Set (дополнительно) +- `mapGet`, `mapSet`, `mapRemove`, `mapKeys`, `mapValues`, `mapEntries`, `mapContains`, `mapSize`, `mapFilter`, `mapMerge` +- `setAdd`, `setRemove`, `setContains`, `setUnion`, `setIntersection`, `setDifference`, `setSize`, `setFilter`, `setMap` +- `listToSet :: [a] -> Set` +- `mapToList :: Map -> [(key, value)]` + +### 9.14. Криптография +- `sha256 :: ByteString -> ByteString` +- `sha256String :: String -> String` +- `kyberKeyPair :: () -> (PublicKey, SecretKey)` (оба как ByteString) +- `kyberEncapsulate :: PublicKey -> (Ciphertext, SharedSecret)` +- `kyberDecapsulate :: SecretKey -> Ciphertext -> SharedSecret` + +### 9.15. Управление переменными +- `load :: String -> ()` – загружает и выполняет файл. + +### 9.16. P2P +- `p2pPort :: () -> Num` – возвращает текущий P2P-порт. + +--- + +## 10. Примеры + +### 10.1. Арифметика и функции +``` +>>> show $ 1 + 2 * 3 +7 +>>> let sq x = x * x +>>> show $ sq 5 +25 +>>> show $ (lambda x -> x + 1) 5 +6 +>>> show $ -5 +-5 +>>> show $ 5 - -2 +7 +``` + +### 10.2. Переменные и присваивание +``` +>>> let x = 5 +>>> show x +5 +>>> let x = 6 +error: variable 'x' already defined +>>> x = 10 +>>> show x +10 +``` + +### 10.3. Символы и строки +``` +>>> show 'a' +a +>>> show '\n' + + +>>> let msg = "Hello" +>>> show msg +Hello +>>> show $ msg ++ " world" +Hello world +``` + +### 10.4. Аннотации типов и typeof +``` +>>> let N :: Num = 5 +>>> show $ typeof N +Num +>>> let add :: Num -> Num -> Num = lambda a b -> a + b +>>> show $ typeof add +Closure +>>> let msg :: String = "hello" +>>> show $ typeof msg +String +>>> N = 'a' # type mismatch +error: Type mismatch: expected 'Num', got 'Char' +``` + +### 10.5. Условный оператор +``` +>>> show $ if 5 > 3 then "yes" else "no" +yes +``` + +### 10.6. Списки, строки, кортежи +``` +>>> show $ [1, 2, 3] |> map(lambda x -> x * 2) +[2, 4, 6] +>>> show $ "Hello, " ++ "world!" +Hello, world! +>>> show $ length "abc" +3 +>>> show $ [0..5] +[0, 1, 2, 3, 4] +>>> show $ (1, "two", 3.0)[1] +two +>>> show $ length (1,2,3) +3 +``` + +### 10.7. Сопоставление с образцом +``` +>>> show $ case 2 of 1 -> "one"; 2 -> "two"; _ -> "other" +two +``` + +### 10.8. JSON +``` +>>> let data = parseJson "[1, 2, 3]" +>>> show data +[1, 2, 3] +>>> show $ encodeJson data +[1,2,3] +``` + +### 10.9. Файлы и ввод-вывод +``` +>>> writeFile "test.txt" "Hello" +() +>>> show $ readFile "test.txt" +Hello +>>> show $ fileExists "test.txt" +true +>>> show $ listDir "." +["test.txt", ...] +``` + +### 10.10. Map и Set +``` +>>> let m = %(1 => "one", 2 => "two") +>>> show $ m[1] +one +>>> show $ mapSet m 3 "three" +%(1: one, 2: two, 3: three) +>>> show $ mapKeys m +[1, 2, 3] +>>> let s = %[1,2,3] +>>> show $ setAdd s 4 +%[1,2,3,4] +>>> show $ s[2] +true +>>> show $ setContains s 5 +false +``` + +### 10.11. Классы +``` +>>> class Counter = (val = Num; inc(self) = self { val = self.val + 1 }; get(self) = self.val) +>>> let c = new Counter(0) +>>> show $ c.inc().get() +1 +``` + +### 10.12. Структуры +``` +>>> struct Person = (name = String, age = Num) +>>> let p = Person(name = "Alice", age = 30) +>>> show $ p.name +Alice +``` + +### 10.13. Чат (полный сценарий с внешним IP и паролем) + +**Установка внешнего IP и запуск сервера с паролем:** +``` +>>> setExternalIP "203.0.113.5" +() +>>> serverStart "0.0.0.0:9000" "secret" +() +``` + +**Алиса:** +``` +>>> login @alice +() +>>> connect "127.0.0.1:9000" @alice "secret" +1 +>>> newChat "general" [@bob, @alice] +general +>>> open "general" +() +>>> sendChat "general" "Hello everyone!" +true +>>> send @bob "Hello Bob!" +true +``` + +**Боб (другой экземпляр):** +``` +>>> login @bob +() +>>> connect "127.0.0.1:9000" @bob "secret" +1 +>>> show inbox +[[Message from @alice in general: "Hello everyone!"], [Message from @alice: "Hello Bob!"]] +>>> show $ history "general" +[[Message from @alice in general: "Hello everyone!"]] +``` + +**Передача файла:** +``` +>>> writeFile "report.txt" "Sales data" +() +>>> sendFileToChat "general" "report.txt" +true +>>> show downloads +[[FileTransfer from @alice: report.txt]] +>>> saveFile 0 "received_report.txt" +true +``` + +### 10.14. Процессы +``` +>>> let p = spawn lambda -> (procRecv |> show) +>>> procSend p "Hi" +>>> sleep 1s +``` + +### 10.15. Криптография +``` +>>> let (pk, sk) = kyberKeyPair() +>>> let (ct, ss1) = kyberEncapsulate pk +>>> let ss2 = kyberDecapsulate sk ct +>>> show $ ss1 == ss2 +true +>>> show $ sha256String "hello" +2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 +``` + +--- + +## 11. Технические детали P2P +- Протокол: JSON-строки поверх TLS. +- Сертификаты генерируются автоматически (`chatlang_cert.pem`, `chatlang_key.pem`). +- P2P-порт: 19000 (можно изменить с помощью `--p2p-port`). +- Порт сервера контактов: настраивается (например, 9000). +- Внешний IP можно задать вручную или получить через `getPublicIP()`. + +--- + +## 12. Известные ограничения +- `let ... in` не поддерживается; используйте блоки с отступами. +- Некоторые встроенные функции могут паниковать при неверных типах аргументов (большинство обрабатывают ошибки). + +--- + +## 13. Подсветка синтаксиса для Vim и VSCode + +### Vim +Поместите содержимое `hl/vim/syntax/plic.vim` в `~/.vim/syntax/plic.vim`, а `hl/vim/ftdetect/plic.vim` – в `~/.vim/ftdetect/plic.vim`. Добавьте в `.vimrc`: +```vim +au BufRead,BufNewFile *.plic set filetype=plic +``` + +### VSCode +Скопируйте папку `hl/vsc` в `.vscode` вашего проекта или используйте расширение. В `settings.json` добавьте: +```json +"files.associations": { + "*.plic": "plic" +} +``` diff --git a/hl/vim/ftdetect/plic.vim b/hl/vim/ftdetect/plic.vim new file mode 100644 index 0000000..da7e122 --- /dev/null +++ b/hl/vim/ftdetect/plic.vim @@ -0,0 +1 @@ +au BufRead,BufNewFile *.plic set filetype=plic diff --git a/hl/vim/ftdetect/plic_header.vim b/hl/vim/ftdetect/plic_header.vim new file mode 100644 index 0000000..cc5e26e --- /dev/null +++ b/hl/vim/ftdetect/plic_header.vim @@ -0,0 +1 @@ +runtime! syntax/plic.vim diff --git a/hl/vim/syntax/plic.vim b/hl/vim/syntax/plic.vim new file mode 100644 index 0000000..d94b109 --- /dev/null +++ b/hl/vim/syntax/plic.vim @@ -0,0 +1,47 @@ +if exists("b:current_syntax") + finish +endif + +" Keywords +syn keyword chKeyword let if then else case of data struct try catch error for while lambda and or not class extends new loop break load del +syn keyword chKeyword addContact removeContact newChat addMember removeMember deleteChat sendChat sendFileToChat inbox history downloads saveFile +syn keyword chKeyword serverStart serverStop connect getPublicIP setExternalIP toFloat toInt typeof + +" DataType +syn region chString start=+"+ end=+"+ skip=+\\"+ contains=@Spell +syn region chString start=+'+ end=+'+ skip=+\\'+ contains=@Spell + +" f-string +syn region chFString start=+f"+ end=+"+ skip=+\\"+ contains=chFStringExpr +syn region chFStringExpr start=+{+ end=+}+ contained contains=chKeyword,chString,chNumber,chType,chOperator,chComment + +" Byte string +syn match chByteString "#B\"[0-9A-Fa-f]*\"" + +" Numbers +syn match chNumber "\<\d\(\d\|[a-zA-Z_]\)*\>" +syn keyword chNumber true false + +" Type +syn keyword chType Num Char String Bool Unit Uid ByteString Duration List Tuple Record Pid DateTime Json Maybe Either ChatMsg FileInfo FileTransfer FetchOptions FetchResult Map Set ClassInstance + +" Operators +syn match chOperator "[-+*%/@&$|^~=<>?:]" +syn match chOperator "[{}()\[\];,.]" + +" Comments +syn match chComment "#.*$" contains=@Spell +syn region chComment start="#-" end="-#" contains=@Spell fold + +" Markup +hi def link chComment Comment +hi def link chTodo Todo +hi def link chString String +hi def link chFString String +hi def link chByteString String +hi def link chNumber Number +hi def link chType Type +hi def link chKeyword Keyword +hi def link chOperator Operator + +let b:current_syntax = "plic" diff --git a/hl/vsc/plic.tmLanguage.json b/hl/vsc/plic.tmLanguage.json new file mode 100644 index 0000000..7d9c0c2 --- /dev/null +++ b/hl/vsc/plic.tmLanguage.json @@ -0,0 +1,121 @@ +{ + "scopeName": "source.plic", + "patterns": [ + { + "include": "#keywords" + }, + { + "include": "#builtins" + }, + { + "include": "#types" + }, + { + "include": "#strings" + }, + { + "include": "#fstrings" + }, + { + "include": "#numbers" + }, + { + "include": "#comments" + } + ], + "repository": { + "keywords": { + "patterns": [ + { + "match": "\\b(let|if|then|else|case|of|lambda|data|struct|try|catch|error|for|while|in|and|or|not|class|extends|new|loop|break|load|del)\\b", + "name": "keyword.control.plic" + } + ] + }, + "builtins": { + "patterns": [ + { + "match": "\\b(sqrt|sin|cos|tan|asin|acos|atan|toFloat|toInt|show|parseInt|parseFloat|chr|ord|null|length|map|filter|foldl|foldr|take|drop|reverse|all|any|find|sort|sortBy|sum|concat|flatten|zip|zipWith|unzip|indexOf|lastIndexOf|split|join|startsWith|endsWith|trim|replace|substring|parseJson|encodeJson|lookup|formatTime|parseTime|addDuration|diffDuration|packBytes|unpackBytes|putStrLn|getLine|getArgs|readFile|readBinaryFile|writeFile|appendFile|writeBinaryFile|fileExists|fileSize|fetch|fetchOpts|login|logout|deleteUser|newChat|addMember|removeMember|deleteChat|open|send|sendFile|sendChat|sendFileToChat|inbox|history|downloads|saveFile|serverStart|serverStop|connect|getPublicIP|setExternalIP|spawn|procSelf|procSend|procRecv|procWait|procExit|sleep|after|Nothing|Just|maybe|mapGet|mapSet|mapRemove|mapKeys|mapValues|mapEntries|mapContains|mapSize|mapFilter|mapMerge|setAdd|setRemove|setContains|setUnion|setIntersection|setDifference|setSize|setFilter|setMap|listToSet|mapToList|sha256|sha256String|kyberKeyPair|kyberEncapsulate|kyberDecapsulate|listDir|createDir|removeDir|fileMove|filePermissions|setFilePermissions|typeof|exit|p2pPort)\\b", + "name": "support.function.plic" + } + ] + }, + "types": { + "patterns": [ + { + "match": "\\b(Num|Char|String|Bool|Unit|Uid|ByteString|List|Tuple|Record|Pid|DateTime|Duration|Json|Maybe|Either|ChatMsg|FileInfo|FileTransfer|FetchOptions|FetchResult|Map|Set|ClassInstance)\\b", + "name": "storage.type.plic" + } + ] + }, + "strings": { + "patterns": [ + { + "begin": "\"", + "end": "\"", + "name": "string.quoted.double.plic", + "patterns": [ + { + "match": "\\\\(n|t|r|\\\\|\"|')", + "name": "constant.character.escape.plic" + } + ] + } + ] + }, + "fstrings": { + "patterns": [ + { + "begin": "f\"", + "end": "\"", + "name": "string.quoted.double.fstring.plic", + "patterns": [ + { + "begin": "{", + "end": "}", + "name": "meta.embedded.expression.plic", + "patterns": [ + { + "include": "#keywords" + }, + { + "include": "#builtins" + }, + { + "include": "#types" + }, + { + "include": "#strings" + }, + { + "include": "#numbers" + } + ] + } + ] + } + ] + }, + "numbers": { + "patterns": [ + { + "match": "\\b[0-9]+(\\.[0-9]+)?\\b", + "name": "constant.numeric.plic" + } + ] + }, + "comments": { + "patterns": [ + { + "begin": "#-", + "end": "-#", + "name": "comment.block.plic" + }, + { + "match": "#.*$", + "name": "comment.line.plic" + } + ] + } + } +} diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..4815a9b --- /dev/null +++ b/install.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +echo "Building PLIC in release mode..." +cargo build --release + +BIN_SRC="target/release/plic" +BIN_DEST="/bin/plic" + +if [ ! -f "$BIN_SRC" ]; then + echo "Error: build failed, binary not found at $BIN_SRC" + exit 1 +fi + +echo "Installing binary to $BIN_DEST (requires sudo)..." +sudo cp "$BIN_SRC" "$BIN_DEST" +sudo chmod 755 "$BIN_DEST" + +echo "Installation complete! You can now run 'plic'." diff --git a/lang_example/README.md b/lang_example/README.md deleted file mode 100644 index 76fb404..0000000 --- a/lang_example/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# lang_example - -Описание вашего языка diff --git a/lang_example/compiler.gleam b/lang_example/compiler.gleam deleted file mode 100644 index e1804c7..0000000 --- a/lang_example/compiler.gleam +++ /dev/null @@ -1,7 +0,0 @@ -import gleam/io - -pub fn main() { - io.println("computing...") -} - - diff --git a/src/ast.rs b/src/ast.rs new file mode 100644 index 0000000..ab0be2e --- /dev/null +++ b/src/ast.rs @@ -0,0 +1,143 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Span { + pub start: usize, + pub end: usize, +} + +impl Span { + pub fn new(start: usize, end: usize) -> Self { + Self { start, end } + } + pub fn dummy() -> Self { + Self { start: 0, end: 0 } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Literal { + Int(i64), // will become Num(f64) after parsing + Float(f64), // also Num + Char(char), + String(String), + Bool(bool), + Unit, + Uid(String), + ByteString(Vec), + Duration(std::time::Duration), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Pattern { + Wildcard(Span), + Var(String, Span), + Literal(Literal, Span), + Constructor(String, Vec, Span), + List(Vec, Span), + Tuple(Vec, Span), + Record(Vec<(String, Pattern)>, Span), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MethodDef { + pub name: String, + pub params: Vec, + pub body: Box, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum FStringPart { + Literal(String), + Expr(Box), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Expr { + Lit(Literal, Span), + Var(String, Span), + Lambda(Vec, Box, Span), + App(Box, Box, Span), + If(Box, Box, Box, Span), + Let { + name: String, + type_ann: Option, + def: Box, + body: Option>, + span: Span, + }, + Assign(String, Box, Span), + Case(Box, Vec<(Pattern, Box)>, Span), + Try(Box, Span), + Catch(Box, Pattern, Box, Span), + Throw(Box, Span), + DataDef(String, Vec, Vec, Span), + StructDef(String, Vec<(String, String)>, Span), + StructNew(String, Vec<(String, Expr)>, Span), + Constructor(String, Vec, Span), + Record(Vec<(String, Expr)>, Span), + FieldAccess(Box, String, Span), + RecordUpdate(Box, Vec<(String, Expr)>, Span), + List(Vec, Span), + Range(Box, Box, Span), + BinOp(BinOp, Box, Box, Span), + Concat(Box, Box, Span), + Pipe(Box, Box, Span), + Dollar(Box, Box, Span), + LogicalAnd(Box, Box, Span), + LogicalOr(Box, Box, Span), + Not(Box, Span), + Tuple(Vec, Span), + Index(Box, Box, Span), + For(String, Box, Box, Span), + While(Box, Box, Span), + Loop(Box, Span), + Break(Option>, Span), + Block(Vec, Span), + ClassDef { + name: String, + extends: Option, + fields: Vec<(String, Option)>, + methods: Vec, + span: Span, + }, + New(String, Vec, Span), + MethodCall(Box, String, Vec, Span), + MapLiteral(Vec<(Expr, Expr)>, Span), + SetLiteral(Vec, Span), + FString(Vec, Span), + ListComp { + expr: Box, + generators: Vec<(String, Box)>, + filters: Vec>, + span: Span, + }, + // New: type cast expression + Cast(Box, String, Span), + // New: super method call + SuperMethod { method: String, args: Vec, span: Span }, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ConstructorDef { + pub name: String, + pub fields: Vec, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum BinOp { + Add, + Sub, + Mul, + Div, + Mod, + Cons, + Eq, + Neq, + Lt, + Le, + Gt, + Ge, + In, + NotIn, +} diff --git a/src/builtins.rs b/src/builtins.rs new file mode 100644 index 0000000..dcd9412 --- /dev/null +++ b/src/builtins.rs @@ -0,0 +1,2444 @@ +use crate::chat::{ChatState, P2pControl}; +use crate::error::ChatError; +use crate::eval::{ + self, spawn_process, proc_self, proc_send, proc_recv, proc_wait, proc_exit, sleep, after, type_name, +}; +use crate::p2p::{self, P2pMessage}; +use crate::server; +use crate::types::{Environment, Value, JsonValue, FetchResult}; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; +use chrono::{DateTime, Local}; +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use serde_json::Value as SerdeValue; +use sha2::{Sha256, Digest}; +use std::fs; +use pqcrypto_kyber::kyber768::{keypair, encapsulate, decapsulate}; +use pqcrypto_traits::kem::{PublicKey, SecretKey, Ciphertext, SharedSecret}; +use reqwest::blocking::get; + +macro_rules! builtin { + ($name:expr, $arity:expr, $func:expr) => { + Value::BuiltinFunc($name.to_string(), $arity, std::sync::Arc::new($func)) + }; +} + +pub fn populate(env: &mut Environment, state: Arc>, global_env: Arc>) { + env.set( + "sqrt".to_string(), + builtin!("sqrt", 1, |args| { + if let [Value::Num(x)] = &args[..] { + Ok(Value::Num(x.sqrt())) + } else { + Err(ChatError::new("sqrt expects a Num argument", 1)) + } + }), + ); + + env.set( + "sin".to_string(), + builtin!("sin", 1, |args| { + if let [Value::Num(x)] = &args[..] { + Ok(Value::Num(x.sin())) + } else { + Err(ChatError::new("sin expects a Num argument", 1)) + } + }), + ); + + env.set( + "cos".to_string(), + builtin!("cos", 1, |args| { + if let [Value::Num(x)] = &args[..] { + Ok(Value::Num(x.cos())) + } else { + Err(ChatError::new("cos expects a Num argument", 1)) + } + }), + ); + + env.set( + "tan".to_string(), + builtin!("tan", 1, |args| { + if let [Value::Num(x)] = &args[..] { + Ok(Value::Num(x.tan())) + } else { + Err(ChatError::new("tan expects a Num argument", 1)) + } + }), + ); + + env.set( + "asin".to_string(), + builtin!("asin", 1, |args| { + if let [Value::Num(x)] = &args[..] { + Ok(Value::Num(x.asin())) + } else { + Err(ChatError::new("asin expects a Num argument", 1)) + } + }), + ); + + env.set( + "acos".to_string(), + builtin!("acos", 1, |args| { + if let [Value::Num(x)] = &args[..] { + Ok(Value::Num(x.acos())) + } else { + Err(ChatError::new("acos expects a Num argument", 1)) + } + }), + ); + + env.set( + "atan".to_string(), + builtin!("atan", 1, |args| { + if let [Value::Num(x)] = &args[..] { + Ok(Value::Num(x.atan())) + } else { + Err(ChatError::new("atan expects a Num argument", 1)) + } + }), + ); + + // Removed toFloat and toInt. + + env.set( + "show".to_string(), + builtin!("show", 1, |args| { + if args.len() == 1 { + println!("{}", args[0].display()); + Ok(Value::Unit) + } else { + Err(ChatError::new("show expects exactly one argument", 1)) + } + }), + ); + + env.set( + "parseInt".to_string(), + builtin!("parseInt", 1, |args| { + if let [Value::String(s)] = &args[..] { + s.parse::() + .map(|i| Value::Num(i as f64)) + .map_err(|_| ChatError::new("parseInt: invalid integer string", 1)) + } else { + Err(ChatError::new("parseInt expects a String argument", 1)) + } + }), + ); + + env.set( + "parseFloat".to_string(), + builtin!("parseFloat", 1, |args| { + if let [Value::String(s)] = &args[..] { + s.parse::() + .map(Value::Num) + .map_err(|_| ChatError::new("parseFloat: invalid float string", 1)) + } else { + Err(ChatError::new("parseFloat expects a String argument", 1)) + } + }), + ); + + env.set( + "chr".to_string(), + builtin!("chr", 1, |args| { + if let [Value::Num(x)] = &args[..] { + let i = *x as u32; + if let Some(c) = std::char::from_u32(i) { + Ok(Value::Char(c)) + } else { + Err(ChatError::new("chr: invalid Unicode code point", 1)) + } + } else { + Err(ChatError::new("chr expects a Num argument", 1)) + } + }), + ); + + env.set( + "ord".to_string(), + builtin!("ord", 1, |args| { + if let [Value::Char(c)] = &args[..] { + Ok(Value::Num(*c as u32 as f64)) + } else { + Err(ChatError::new("ord expects a Char argument", 1)) + } + }), + ); + + env.set( + "typeof".to_string(), + builtin!("typeof", 1, |args| { + if args.len() == 1 { + Ok(Value::String(type_name(&args[0]))) + } else { + Err(ChatError::new("typeof expects exactly one argument", 1)) + } + }), + ); + + env.set( + "null".to_string(), + builtin!("null", 1, |args| { + if let [Value::List(v)] = &args[..] { + Ok(Value::Bool(v.is_empty())) + } else { + Err(ChatError::new("null expects a List argument", 1)) + } + }), + ); + + env.set( + "length".to_string(), + builtin!("length", 1, |args| { + if let [v] = &args[..] { + match v { + Value::List(l) => Ok(Value::Num(l.len() as f64)), + Value::String(s) => Ok(Value::Num(s.len() as f64)), + Value::ByteString(b) => Ok(Value::Num(b.len() as f64)), + Value::Map(m) => Ok(Value::Num(m.len() as f64)), + Value::Set(s) => Ok(Value::Num(s.len() as f64)), + Value::Tuple(t) => Ok(Value::Num(t.len() as f64)), + _ => Err(ChatError::new( + "length expects a List, String, ByteString, Map, Set, or Tuple", + 1, + )), + } + } else { + Err(ChatError::new("length expects exactly one argument", 1)) + } + }), + ); + + let state_map = state.clone(); + env.set( + "map".to_string(), + builtin!("map", 2, move |args| { + if let [Value::Closure(params, body, env), Value::List(list)] = &args[..] { + let mut result = Vec::new(); + for item in list { + let mut new_env = env.clone(); + new_env.set(params[0].clone(), item.clone()); + let val = eval::eval_expr(&body, &mut new_env, Arc::clone(&state_map))?; + result.push(val); + } + Ok(Value::List(result)) + } else { + Err(ChatError::new("map expects a function and a List", 1)) + } + }), + ); + + let state_filter = state.clone(); + env.set( + "filter".to_string(), + builtin!("filter", 2, move |args| { + if let [Value::Closure(params, body, env), Value::List(list)] = &args[..] { + let mut result = Vec::new(); + for item in list { + let mut new_env = env.clone(); + new_env.set(params[0].clone(), item.clone()); + let val = eval::eval_expr(&body, &mut new_env, Arc::clone(&state_filter))?; + if let Value::Bool(b) = val { + if b { + result.push(item.clone()); + } + } else { + return Err(ChatError::new( + "filter predicate must return a Bool value", + 1, + )); + } + } + Ok(Value::List(result)) + } else { + Err(ChatError::new("filter expects a function and a List", 1)) + } + }), + ); + + let state_foldl = state.clone(); + env.set( + "foldl".to_string(), + builtin!("foldl", 3, move |args| { + if let [Value::Closure(params, body, env), Value::List(list), acc] = &args[..] { + let mut acc_val = acc.clone(); + for item in list { + let mut new_env = env.clone(); + new_env.set(params[0].clone(), acc_val); + new_env.set(params[1].clone(), item.clone()); + acc_val = eval::eval_expr(&body, &mut new_env, Arc::clone(&state_foldl))?; + } + Ok(acc_val) + } else { + Err(ChatError::new( + "foldl expects a function, a List, and an initial accumulator", + 1, + )) + } + }), + ); + + let state_foldr = state.clone(); + env.set( + "foldr".to_string(), + builtin!("foldr", 3, move |args| { + if let [Value::Closure(params, body, env), Value::List(list), acc] = &args[..] { + let mut acc_val = acc.clone(); + for item in list.iter().rev() { + let mut new_env = env.clone(); + new_env.set(params[0].clone(), item.clone()); + new_env.set(params[1].clone(), acc_val); + acc_val = eval::eval_expr(&body, &mut new_env, Arc::clone(&state_foldr))?; + } + Ok(acc_val) + } else { + Err(ChatError::new( + "foldr expects a function, a List, and an initial accumulator", + 1, + )) + } + }), + ); + + env.set( + "take".to_string(), + builtin!("take", 2, |args| { + if let [Value::Num(n), v] = &args[..] { + let n = *n as usize; + match v { + Value::List(l) => { + let taken: Vec = l.iter().take(n).cloned().collect(); + Ok(Value::List(taken)) + } + Value::String(s) => { + let taken: String = s.chars().take(n).collect(); + Ok(Value::String(taken)) + } + Value::ByteString(b) => { + let taken: Vec = b.iter().take(n).cloned().collect(); + Ok(Value::ByteString(taken)) + } + _ => Err(ChatError::new( + "take expects a List, String, or ByteString as second argument", + 1, + )), + } + } else { + Err(ChatError::new( + "take expects a Num (integer) and a collection", + 1, + )) + } + }), + ); + + env.set( + "drop".to_string(), + builtin!("drop", 2, |args| { + if let [Value::Num(n), v] = &args[..] { + let n = *n as usize; + match v { + Value::List(l) => { + if n >= l.len() { + Ok(Value::List(vec![])) + } else { + Ok(Value::List(l[n..].to_vec())) + } + } + Value::String(s) => { + let dropped: String = s.chars().skip(n).collect(); + Ok(Value::String(dropped)) + } + Value::ByteString(b) => { + if n >= b.len() { + Ok(Value::ByteString(vec![])) + } else { + Ok(Value::ByteString(b[n..].to_vec())) + } + } + _ => Err(ChatError::new( + "drop expects a List, String, or ByteString as second argument", + 1, + )), + } + } else { + Err(ChatError::new( + "drop expects a Num (integer) and a collection", + 1, + )) + } + }), + ); + + env.set( + "reverse".to_string(), + builtin!("reverse", 1, |args| { + if let [Value::List(l)] = &args[..] { + let mut rev = l.clone(); + rev.reverse(); + Ok(Value::List(rev)) + } else { + Err(ChatError::new("reverse expects a List argument", 1)) + } + }), + ); + + let state_all = state.clone(); + env.set( + "all".to_string(), + builtin!("all", 2, move |args| { + if let [Value::Closure(params, body, env), Value::List(list)] = &args[..] { + for item in list { + let mut new_env = env.clone(); + new_env.set(params[0].clone(), item.clone()); + let val = eval::eval_expr(&body, &mut new_env, Arc::clone(&state_all))?; + if let Value::Bool(b) = val { + if !b { + return Ok(Value::Bool(false)); + } + } else { + return Err(ChatError::new( + "all predicate must return a Bool value", + 1, + )); + } + } + Ok(Value::Bool(true)) + } else { + Err(ChatError::new("all expects a function and a List", 1)) + } + }), + ); + + let state_any = state.clone(); + env.set( + "any".to_string(), + builtin!("any", 2, move |args| { + if let [Value::Closure(params, body, env), Value::List(list)] = &args[..] { + for item in list { + let mut new_env = env.clone(); + new_env.set(params[0].clone(), item.clone()); + let val = eval::eval_expr(&body, &mut new_env, Arc::clone(&state_any))?; + if let Value::Bool(b) = val { + if b { + return Ok(Value::Bool(true)); + } + } else { + return Err(ChatError::new( + "any predicate must return a Bool value", + 1, + )); + } + } + Ok(Value::Bool(false)) + } else { + Err(ChatError::new("any expects a function and a List", 1)) + } + }), + ); + + let state_find = state.clone(); + env.set( + "find".to_string(), + builtin!("find", 2, move |args| { + if let [Value::Closure(params, body, env), Value::List(list)] = &args[..] { + for item in list { + let mut new_env = env.clone(); + new_env.set(params[0].clone(), item.clone()); + let val = eval::eval_expr(&body, &mut new_env, Arc::clone(&state_find))?; + if let Value::Bool(b) = val { + if b { + return Ok(Value::Maybe(Some(Box::new(item.clone())))); + } + } else { + return Err(ChatError::new( + "find predicate must return a Bool value", + 1, + )); + } + } + Ok(Value::Maybe(None)) + } else { + Err(ChatError::new("find expects a function and a List", 1)) + } + }), + ); + + env.set( + "sort".to_string(), + builtin!("sort", 1, |args| { + if let [Value::List(list)] = &args[..] { + let mut sorted = list.clone(); + sorted.sort_by(|a, b| a.display().cmp(&b.display())); + Ok(Value::List(sorted)) + } else { + Err(ChatError::new("sort expects a List argument", 1)) + } + }), + ); + + let state_sortby = state.clone(); + env.set( + "sortBy".to_string(), + builtin!("sortBy", 2, move |args| { + if let [Value::Closure(params, body, closure_env), Value::List(list)] = &args[..] { + let mut sorted = list.clone(); + sorted.sort_by(|a, b| { + let mut env = closure_env.clone(); + env.set(params[0].clone(), a.clone()); + env.set(params[1].clone(), b.clone()); + let result = + eval::eval_expr(&body, &mut env, Arc::clone(&state_sortby)).unwrap_or(Value::Num(0.0)); + match result { + Value::Num(x) => x.partial_cmp(&0.0).unwrap_or(std::cmp::Ordering::Equal), + _ => std::cmp::Ordering::Equal, + } + }); + Ok(Value::List(sorted)) + } else { + Err(ChatError::new( + "sortBy expects a function (a -> a -> Num) and a List", + 1, + )) + } + }), + ); + + env.set( + "sum".to_string(), + builtin!("sum", 1, |args| { + if let [Value::List(list)] = &args[..] { + let mut total = 0.0; + for item in list { + match item { + Value::Num(x) => total += x, + _ => return Err(ChatError::new("sum expects a list of numbers", 1)), + } + } + Ok(Value::Num(total)) + } else { + Err(ChatError::new("sum expects a List argument", 1)) + } + }), + ); + + env.set( + "concat".to_string(), + builtin!("concat", 1, |args| { + if let [Value::List(lists)] = &args[..] { + let mut result = Vec::new(); + for item in lists { + match item { + Value::List(inner) => result.extend(inner.clone()), + _ => { + return Err(ChatError::new( + "concat expects a list of lists (each element must be a List)", + 1, + )) + } + } + } + Ok(Value::List(result)) + } else { + Err(ChatError::new("concat expects a List argument", 1)) + } + }), + ); + + env.set( + "flatten".to_string(), + builtin!("flatten", 1, |args| { + if let [Value::List(lists)] = &args[..] { + let mut result = Vec::new(); + for item in lists { + match item { + Value::List(inner) => result.extend(inner.clone()), + _ => { + return Err(ChatError::new( + "flatten expects a list of lists (each element must be a List)", + 1, + )) + } + } + } + Ok(Value::List(result)) + } else { + Err(ChatError::new("flatten expects a List argument", 1)) + } + }), + ); + + env.set( + "zip".to_string(), + builtin!("zip", 2, |args| { + if let [Value::List(a), Value::List(b)] = &args[..] { + let min_len = a.len().min(b.len()); + let mut result = Vec::new(); + for i in 0..min_len { + result.push(Value::Tuple(vec![a[i].clone(), b[i].clone()])); + } + Ok(Value::List(result)) + } else { + Err(ChatError::new("zip expects two List arguments", 1)) + } + }), + ); + + let state_zipwith = state.clone(); + env.set( + "zipWith".to_string(), + builtin!("zipWith", 3, move |args| { + if let [Value::Closure(params, body, closure_env), Value::List(a), Value::List(b)] = &args[..] { + let min_len = a.len().min(b.len()); + let mut result = Vec::new(); + for i in 0..min_len { + let mut env = closure_env.clone(); + env.set(params[0].clone(), a[i].clone()); + env.set(params[1].clone(), b[i].clone()); + let val = eval::eval_expr(&body, &mut env, Arc::clone(&state_zipwith))?; + result.push(val); + } + Ok(Value::List(result)) + } else { + Err(ChatError::new( + "zipWith expects a function and two List arguments", + 1, + )) + } + }), + ); + + env.set( + "unzip".to_string(), + builtin!("unzip", 1, |args| { + if let [Value::List(pairs)] = &args[..] { + let mut left = Vec::new(); + let mut right = Vec::new(); + for pair in pairs { + match pair { + Value::Tuple(v) if v.len() == 2 => { + left.push(v[0].clone()); + right.push(v[1].clone()); + } + _ => { + return Err(ChatError::new( + "unzip expects a list of 2‑element tuples", + 1, + )) + } + } + } + Ok(Value::Tuple(vec![Value::List(left), Value::List(right)])) + } else { + Err(ChatError::new("unzip expects a List argument", 1)) + } + }), + ); + + env.set( + "indexOf".to_string(), + builtin!("indexOf", 2, |args| { + if let [Value::List(list), elem] = &args[..] { + for (i, item) in list.iter().enumerate() { + if item.display() == elem.display() { + return Ok(Value::Maybe(Some(Box::new(Value::Num(i as f64))))); + } + } + Ok(Value::Maybe(None)) + } else { + Err(ChatError::new("indexOf expects a List and an element", 1)) + } + }), + ); + + env.set( + "lastIndexOf".to_string(), + builtin!("lastIndexOf", 2, |args| { + if let [Value::List(list), elem] = &args[..] { + for i in (0..list.len()).rev() { + if list[i].display() == elem.display() { + return Ok(Value::Maybe(Some(Box::new(Value::Num(i as f64))))); + } + } + Ok(Value::Maybe(None)) + } else { + Err(ChatError::new("lastIndexOf expects a List and an element", 1)) + } + }), + ); + + env.set( + "split".to_string(), + builtin!("split", 2, |args| { + if let [Value::String(delim), Value::String(s)] = &args[..] { + let parts: Vec = s.split(delim).map(|x| x.to_string()).collect(); + let list = parts.into_iter().map(Value::String).collect(); + Ok(Value::List(list)) + } else { + Err(ChatError::new( + "split expects a String delimiter and a String", + 1, + )) + } + }), + ); + + env.set( + "join".to_string(), + builtin!("join", 2, |args| { + if let [Value::String(delim), Value::List(list)] = &args[..] { + let strings: Result, ChatError> = list + .iter() + .map(|v| { + if let Value::String(s) = v { + Ok(s.clone()) + } else { + Err(ChatError::new("join expects a list of Strings", 1)) + } + }) + .collect(); + let joined = strings?.join(delim); + Ok(Value::String(joined)) + } else { + Err(ChatError::new("join expects a String and a list of Strings", 1)) + } + }), + ); + + env.set( + "startsWith".to_string(), + builtin!("startsWith", 2, |args| { + if let [Value::String(prefix), Value::String(s)] = &args[..] { + Ok(Value::Bool(s.starts_with(prefix))) + } else { + Err(ChatError::new("startsWith expects two String arguments", 1)) + } + }), + ); + + env.set( + "endsWith".to_string(), + builtin!("endsWith", 2, |args| { + if let [Value::String(suffix), Value::String(s)] = &args[..] { + Ok(Value::Bool(s.ends_with(suffix))) + } else { + Err(ChatError::new("endsWith expects two String arguments", 1)) + } + }), + ); + + env.set( + "trim".to_string(), + builtin!("trim", 1, |args| { + if let [Value::String(s)] = &args[..] { + Ok(Value::String(s.trim().to_string())) + } else { + Err(ChatError::new("trim expects a String argument", 1)) + } + }), + ); + + env.set( + "replace".to_string(), + builtin!("replace", 3, |args| { + if let [Value::String(from), Value::String(to), Value::String(s)] = &args[..] { + Ok(Value::String(s.replace(from, to))) + } else { + Err(ChatError::new( + "replace expects three String arguments: from, to, and source", + 1, + )) + } + }), + ); + + env.set( + "substring".to_string(), + builtin!("substring", 3, |args| { + if let [Value::Num(start), Value::Num(len), Value::String(s)] = &args[..] { + let start = *start as usize; + let len = *len as usize; + if start + len > s.len() { + Err(ChatError::new("substring: indices out of bounds", 1)) + } else { + Ok(Value::String(s[start..start + len].to_string())) + } + } else { + Err(ChatError::new( + "substring expects two Num arguments and a String", + 1, + )) + } + }), + ); + + env.set( + "parseJson".to_string(), + builtin!("parseJson", 1, |args| { + if let [Value::String(s)] = &args[..] { + let v: SerdeValue = + serde_json::from_str(s).map_err(|e| ChatError::new(&format!("parseJson: {}", e), 1))?; + Ok(Value::Json(serde_json_to_plic(v))) + } else { + Err(ChatError::new("parseJson expects a String argument", 1)) + } + }), + ); + + env.set( + "encodeJson".to_string(), + builtin!("encodeJson", 1, |args| { + if let [Value::Json(j)] = &args[..] { + let serde_val = plic_json_to_serde(j); + let json_str = serde_json::to_string(&serde_val) + .map_err(|e| ChatError::new(&format!("encodeJson: {}", e), 1))?; + Ok(Value::String(json_str)) + } else { + Err(ChatError::new("encodeJson expects a JsonValue argument", 1)) + } + }), + ); + + env.set( + "lookup".to_string(), + builtin!("lookup", 2, |args| { + if let [Value::String(key), Value::Json(j)] = &args[..] { + match j { + JsonValue::Object(map) => { + if let Some(val) = map.get(key) { + Ok(Value::Json(val.clone())) + } else { + Ok(Value::Maybe(None)) + } + } + _ => Err(ChatError::new( + "lookup expects a String key and a JsonObject", + 1, + )), + } + } else { + Err(ChatError::new( + "lookup expects a String and a JsonValue argument", + 1, + )) + } + }), + ); + + env.set( + "formatTime".to_string(), + builtin!("formatTime", 2, |args| { + if let [Value::String(fmt), Value::DateTime(dt)] = &args[..] { + let formatted = dt.format(&fmt).to_string(); + Ok(Value::String(formatted)) + } else { + Err(ChatError::new( + "formatTime expects a String format and a DateTime", + 1, + )) + } + }), + ); + + env.set( + "parseTime".to_string(), + builtin!("parseTime", 2, |args| { + if let [Value::String(fmt), Value::String(s)] = &args[..] { + let dt = DateTime::parse_from_str(s, &fmt) + .map_err(|e| ChatError::new(&format!("parseTime: {}", e), 1))?; + Ok(Value::DateTime(dt.with_timezone(&Local))) + } else { + Err(ChatError::new("parseTime expects two String arguments", 1)) + } + }), + ); + + env.set( + "addDuration".to_string(), + builtin!("addDuration", 2, |args| { + if let [Value::DateTime(dt), Value::Duration(dur)] = &args[..] { + Ok(Value::DateTime(*dt + *dur)) + } else { + Err(ChatError::new( + "addDuration expects a DateTime and a Duration", + 1, + )) + } + }), + ); + + env.set( + "diffDuration".to_string(), + builtin!("diffDuration", 2, |args| { + if let [Value::DateTime(dt1), Value::DateTime(dt2)] = &args[..] { + let dur = if *dt1 > *dt2 { + *dt1 - *dt2 + } else { + *dt2 - *dt1 + }; + Ok(Value::Duration(dur.to_std().unwrap_or(Duration::from_secs(0)))) + } else { + Err(ChatError::new( + "diffDuration expects two DateTime arguments", + 1, + )) + } + }), + ); + + env.set( + "now".to_string(), + builtin!("now", 0, |_args| Ok(Value::DateTime(Local::now()))), + ); + + env.set( + "packBytes".to_string(), + builtin!("packBytes", 1, |args| { + if let [Value::List(list)] = &args[..] { + let mut bytes = Vec::new(); + for item in list { + if let Value::Num(x) = item { + let i = *x as i64; + if i < 0 || i > 255 { + return Err(ChatError::new("packBytes: byte value out of range (0‑255)", 1)); + } + bytes.push(i as u8); + } else { + return Err(ChatError::new( + "packBytes expects a list of Num values", + 1, + )); + } + } + Ok(Value::ByteString(bytes)) + } else { + Err(ChatError::new("packBytes expects a List argument", 1)) + } + }), + ); + + env.set( + "unpackBytes".to_string(), + builtin!("unpackBytes", 1, |args| { + if let [Value::ByteString(b)] = &args[..] { + let list = b.iter().map(|&x| Value::Num(x as f64)).collect(); + Ok(Value::List(list)) + } else { + Err(ChatError::new("unpackBytes expects a ByteString argument", 1)) + } + }), + ); + + env.set( + "putStrLn".to_string(), + builtin!("putStrLn", 1, |args| { + if let [Value::String(s)] = &args[..] { + println!("{}", s); + Ok(Value::Unit) + } else { + Err(ChatError::new("putStrLn expects a String argument", 1)) + } + }), + ); + + env.set( + "getLine".to_string(), + builtin!("getLine", 0, |_args| { + let mut input = String::new(); + std::io::stdin() + .read_line(&mut input) + .map_err(|e| ChatError::new(&format!("getLine: {}", e), 1))?; + Ok(Value::String(input.trim().to_string())) + }), + ); + + env.set( + "getArgs".to_string(), + builtin!("getArgs", 0, |_args| { + let args: Vec = std::env::args().collect(); + let list = args.into_iter().skip(1).map(Value::String).collect(); + Ok(Value::List(list)) + }), + ); + + env.set( + "readFile".to_string(), + builtin!("readFile", 1, |args| { + if let [Value::String(path)] = &args[..] { + let content = + std::fs::read_to_string(path).map_err(|e| ChatError::new(&format!("readFile: {}", e), 1))?; + Ok(Value::String(content)) + } else { + Err(ChatError::new("readFile expects a String path", 1)) + } + }), + ); + + env.set( + "readBinaryFile".to_string(), + builtin!("readBinaryFile", 1, |args| { + if let [Value::String(path)] = &args[..] { + let bytes = + std::fs::read(path).map_err(|e| ChatError::new(&format!("readBinaryFile: {}", e), 1))?; + Ok(Value::ByteString(bytes)) + } else { + Err(ChatError::new("readBinaryFile expects a String path", 1)) + } + }), + ); + + env.set( + "writeFile".to_string(), + builtin!("writeFile", 2, |args| { + if let [Value::String(path), Value::String(content)] = &args[..] { + std::fs::write(path, content).map_err(|e| ChatError::new(&format!("writeFile: {}", e), 1))?; + Ok(Value::Unit) + } else { + Err(ChatError::new("writeFile expects a String path and String content", 1)) + } + }), + ); + + env.set( + "appendFile".to_string(), + builtin!("appendFile", 2, |args| { + if let [Value::String(path), Value::String(content)] = &args[..] { + use std::io::Write; + let mut file = std::fs::OpenOptions::new() + .append(true) + .create(true) + .open(path) + .map_err(|e| ChatError::new(&format!("appendFile: {}", e), 1))?; + file.write_all(content.as_bytes()) + .map_err(|e| ChatError::new(&format!("appendFile: {}", e), 1))?; + Ok(Value::Unit) + } else { + Err(ChatError::new("appendFile expects a String path and String content", 1)) + } + }), + ); + + env.set( + "writeBinaryFile".to_string(), + builtin!("writeBinaryFile", 2, |args| { + if let [Value::String(path), Value::ByteString(bytes)] = &args[..] { + std::fs::write(path, bytes) + .map_err(|e| ChatError::new(&format!("writeBinaryFile: {}", e), 1))?; + Ok(Value::Unit) + } else { + Err(ChatError::new( + "writeBinaryFile expects a String path and a ByteString", + 1, + )) + } + }), + ); + + env.set( + "fileExists".to_string(), + builtin!("fileExists", 1, |args| { + if let [Value::String(path)] = &args[..] { + Ok(Value::Bool(std::path::Path::new(path).exists())) + } else { + Err(ChatError::new("fileExists expects a String path", 1)) + } + }), + ); + + env.set( + "fileSize".to_string(), + builtin!("fileSize", 1, |args| { + if let [Value::String(path)] = &args[..] { + let meta = + std::fs::metadata(path).map_err(|e| ChatError::new(&format!("fileSize: {}", e), 1))?; + Ok(Value::Num(meta.len() as f64)) + } else { + Err(ChatError::new("fileSize expects a String path", 1)) + } + }), + ); + + env.set( + "listDir".to_string(), + builtin!("listDir", 1, |args| { + if let [Value::String(path)] = &args[..] { + let entries = fs::read_dir(path) + .map_err(|e| ChatError::new(&format!("listDir: {}", e), 1))? + .filter_map(|e| e.ok().map(|e| e.file_name().to_string_lossy().to_string())) + .map(Value::String) + .collect(); + Ok(Value::List(entries)) + } else { + Err(ChatError::new("listDir expects a String path", 1)) + } + }), + ); + + env.set( + "createDir".to_string(), + builtin!("createDir", 1, |args| { + if let [Value::String(path)] = &args[..] { + fs::create_dir_all(path).map_err(|e| ChatError::new(&format!("createDir: {}", e), 1))?; + Ok(Value::Unit) + } else { + Err(ChatError::new("createDir expects a String path", 1)) + } + }), + ); + + env.set( + "removeDir".to_string(), + builtin!("removeDir", 1, |args| { + if let [Value::String(path)] = &args[..] { + fs::remove_dir_all(path).map_err(|e| ChatError::new(&format!("removeDir: {}", e), 1))?; + Ok(Value::Unit) + } else { + Err(ChatError::new("removeDir expects a String path", 1)) + } + }), + ); + + env.set( + "fileMove".to_string(), + builtin!("fileMove", 2, |args| { + if let [Value::String(from), Value::String(to)] = &args[..] { + fs::rename(from, to).map_err(|e| ChatError::new(&format!("fileMove: {}", e), 1))?; + Ok(Value::Unit) + } else { + Err(ChatError::new("fileMove expects two String paths", 1)) + } + }), + ); + + #[cfg(unix)] + env.set( + "filePermissions".to_string(), + builtin!("filePermissions", 1, |args| { + if let [Value::String(path)] = &args[..] { + use std::os::unix::fs::PermissionsExt; + let meta = + fs::metadata(path).map_err(|e| ChatError::new(&format!("filePermissions: {}", e), 1))?; + Ok(Value::Num(meta.permissions().mode() as f64)) + } else { + Err(ChatError::new("filePermissions expects a String path", 1)) + } + }), + ); + #[cfg(not(unix))] + env.set( + "filePermissions".to_string(), + builtin!("filePermissions", 1, |_args| Ok(Value::Num(0.0))), + ); + + #[cfg(unix)] + env.set( + "setFilePermissions".to_string(), + builtin!("setFilePermissions", 2, |args| { + if let [Value::String(path), Value::Num(mode)] = &args[..] { + use std::os::unix::fs::PermissionsExt; + let mut perms = fs::metadata(path) + .map_err(|e| ChatError::new(&format!("setFilePermissions: {}", e), 1))? + .permissions(); + perms.set_mode(*mode as u32); + fs::set_permissions(path, perms) + .map_err(|e| ChatError::new(&format!("setFilePermissions: {}", e), 1))?; + Ok(Value::Unit) + } else { + Err(ChatError::new( + "setFilePermissions expects a String path and a Num mode", + 1, + )) + } + }), + ); + #[cfg(not(unix))] + env.set( + "setFilePermissions".to_string(), + builtin!("setFilePermissions", 2, |_args| Ok(Value::Unit)), + ); + + env.set( + "fetch".to_string(), + builtin!("fetch", 1, |args| { + if let [Value::String(url)] = &args[..] { + let resp = reqwest::blocking::get(url) + .map_err(|e| ChatError::new(&format!("fetch: {}", e), 1))?; + let status = resp.status().as_u16() as i64; + let headers: Vec<(String, String)> = resp + .headers() + .iter() + .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string())) + .collect(); + let body = resp.text().map_err(|e| ChatError::new(&format!("fetch: {}", e), 1))?; + Ok(Value::FetchResult(FetchResult { + status, + body, + headers, + })) + } else { + Err(ChatError::new("fetch expects a String URL", 1)) + } + }), + ); + + env.set( + "fetchOpts".to_string(), + builtin!("fetchOpts", 1, |args| { + if let [Value::FetchOptions(opts)] = &args[..] { + let client = reqwest::blocking::Client::new(); + let mut req = client.request( + opts.method.parse().unwrap_or(reqwest::Method::GET), + &opts.url, + ); + for (k, v) in &opts.headers { + req = req.header(k, v); + } + if let Some(b) = &opts.body { + req = req.body(b.clone()); + } + let resp = req.send().map_err(|e| ChatError::new(&format!("fetchOpts: {}", e), 1))?; + let status = resp.status().as_u16() as i64; + let resp_headers: Vec<(String, String)> = resp + .headers() + .iter() + .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string())) + .collect(); + let body_str = resp.text().map_err(|e| ChatError::new(&format!("fetchOpts: {}", e), 1))?; + Ok(Value::FetchResult(FetchResult { + status, + body: body_str, + headers: resp_headers, + })) + } else { + Err(ChatError::new("fetchOpts expects a FetchOptions value", 1)) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "login".to_string(), + builtin!("login", 1, move |args| { + if let [Value::Uid(uid)] = &args[..] { + state_clone.lock().unwrap().login(uid.clone())?; + Ok(Value::Unit) + } else { + Err(ChatError::new("login expects a Uid argument", 1)) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "logout".to_string(), + builtin!("logout", 0, move |_args| { + state_clone.lock().unwrap().logout()?; + Ok(Value::Unit) + }), + ); + + let state_clone = state.clone(); + env.set( + "deleteUser".to_string(), + builtin!("deleteUser", 1, move |args| { + if let [Value::Uid(uid)] = &args[..] { + state_clone.lock().unwrap().delete_user(uid)?; + Ok(Value::Unit) + } else { + Err(ChatError::new("deleteUser expects a Uid argument", 1)) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "newChat".to_string(), + builtin!("newChat", 2, move |args| { + if let [Value::String(name), Value::List(members)] = &args[..] { + let uids: Vec = members + .iter() + .filter_map(|v| { + if let Value::Uid(u) = v { + Some(u.clone()) + } else { + None + } + }) + .collect(); + let mut state = state_clone.lock().unwrap(); + state.new_chat(name.clone(), uids.clone())?; + let sender = state + .current_user + .clone() + .ok_or(ChatError::new("Not logged in", 1))?; + let control = P2pControl::NewChat { + name: name.clone(), + members: uids.clone(), + from: sender.clone(), + }; + for uid in uids { + if uid != sender { + if let Some(addr) = state.get_contact(&uid) { + let _ = p2p::send_control(&addr, control.clone(), &sender); + } + } + } + Ok(Value::String(name.clone())) + } else { + Err(ChatError::new("newChat expects a String name and a list of Uids", 1)) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "addMember".to_string(), + builtin!("addMember", 2, move |args| { + if let [Value::Uid(uid), Value::String(chat)] = &args[..] { + let mut state = state_clone.lock().unwrap(); + state.add_member(uid.clone(), chat)?; + let sender = state + .current_user + .clone() + .ok_or(ChatError::new("Not logged in", 1))?; + let control = P2pControl::AddMember { + chat: chat.clone(), + uid: uid.clone(), + }; + for member in state.members(chat)? { + if member != sender && member != *uid { + if let Some(addr) = state.get_contact(&member) { + let _ = p2p::send_control(&addr, control.clone(), &sender); + } + } + } + Ok(Value::Unit) + } else { + Err(ChatError::new( + "addMember expects a Uid and a String (chat name)", + 1, + )) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "removeMember".to_string(), + builtin!("removeMember", 2, move |args| { + if let [Value::Uid(uid), Value::String(chat)] = &args[..] { + let mut state = state_clone.lock().unwrap(); + state.remove_member(uid, chat)?; + let sender = state + .current_user + .clone() + .ok_or(ChatError::new("Not logged in", 1))?; + let control = P2pControl::RemoveMember { + chat: chat.clone(), + uid: uid.clone(), + }; + for member in state.members(chat)? { + if member != sender && member != *uid { + if let Some(addr) = state.get_contact(&member) { + let _ = p2p::send_control(&addr, control.clone(), &sender); + } + } + } + Ok(Value::Unit) + } else { + Err(ChatError::new( + "removeMember expects a Uid and a String (chat name)", + 1, + )) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "deleteChat".to_string(), + builtin!("deleteChat", 1, move |args| { + if let [Value::String(chat)] = &args[..] { + let mut state = state_clone.lock().unwrap(); + let members = state.members(chat)?; + state.delete_chat(chat)?; + let sender = state + .current_user + .clone() + .ok_or(ChatError::new("Not logged in", 1))?; + let control = P2pControl::DeleteChat { name: chat.clone() }; + for uid in members { + if uid != sender { + if let Some(addr) = state.get_contact(&uid) { + let _ = p2p::send_control(&addr, control.clone(), &sender); + } + } + } + Ok(Value::Unit) + } else { + Err(ChatError::new("deleteChat expects a String (chat name)", 1)) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "open".to_string(), + builtin!("open", 1, move |args| { + if let [Value::String(chat)] = &args[..] { + state_clone.lock().unwrap().open_chat(chat.clone())?; + Ok(Value::Unit) + } else { + Err(ChatError::new("open expects a String (chat name)", 1)) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "listChats".to_string(), + builtin!("listChats", 0, move |_args| { + let state = state_clone.lock().unwrap(); + let chats = state.list_chats(); + Ok(Value::List(chats.into_iter().map(Value::String).collect())) + }), + ); + + let state_clone = state.clone(); + env.set( + "members".to_string(), + builtin!("members", 1, move |args| { + if let [Value::String(chat)] = &args[..] { + let state = state_clone.lock().unwrap(); + let members = state.members(chat)?; + Ok(Value::List(members.into_iter().map(Value::Uid).collect())) + } else { + Err(ChatError::new("members expects a String (chat name)", 1)) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "send".to_string(), + builtin!("send", 2, move |args| { + if let [Value::Uid(target), Value::String(text)] = &args[..] { + let (current_user, target_addr) = { + let state = state_clone.lock().unwrap(); + let current = state + .current_user + .clone() + .ok_or(ChatError::new("Not logged in", 1))?; + let addr = state + .get_contact(target) + .ok_or_else(|| ChatError::new(&format!("Target '{}' not in contacts", target), 1))?; + (current, addr) + }; + { + let mut state = state_clone.lock().unwrap(); + state.send_message(target, text)?; + } + let msg = P2pMessage { + msg_type: "msg".to_string(), + from: current_user.clone(), + to: target.clone(), + text: Some(text.clone()), + chat: None, + filename: None, + data_base64: None, + timestamp: Some(chrono::Utc::now().timestamp()), + control: None, + }; + if let Err(e) = p2p::send_message_to(&target_addr, msg) { + eprintln!("P2P send error: {}", e); + return Err(ChatError::new(&format!("P2P delivery failed: {}", e), 1)); + } + Ok(Value::Bool(true)) + } else { + Err(ChatError::new("send expects a Uid and a String (message text)", 1)) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "sendFile".to_string(), + builtin!("sendFile", 2, move |args| { + if let [Value::Uid(target), Value::String(path)] = &args[..] { + let bytes = std::fs::read(&path) + .map_err(|e| ChatError::new(&format!("sendFile: {}", e), 1))?; + let b64 = STANDARD.encode(&bytes); + let filename = std::path::Path::new(&path) + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(); + let (current_user, target_addr) = { + let state = state_clone.lock().unwrap(); + let current = state + .current_user + .clone() + .ok_or(ChatError::new("Not logged in", 1))?; + let addr = state + .get_contact(target) + .ok_or_else(|| ChatError::new(&format!("Target '{}' not in contacts", target), 1))?; + (current, addr) + }; + let msg = P2pMessage { + msg_type: "file".to_string(), + from: current_user, + to: target.clone(), + text: None, + chat: None, + filename: Some(filename.clone()), + data_base64: Some(b64), + timestamp: Some(chrono::Utc::now().timestamp()), + control: None, + }; + if let Err(e) = p2p::send_message_to(&target_addr, msg) { + eprintln!("P2P send file error: {}", e); + return Err(ChatError::new(&format!("P2P delivery failed: {}", e), 1)); + } + Ok(Value::Bool(true)) + } else { + Err(ChatError::new("sendFile expects a Uid and a String (file path)", 1)) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "sendChat".to_string(), + builtin!("sendChat", 2, move |args| { + if let [Value::String(chat), Value::String(text)] = &args[..] { + let mut state = state_clone.lock().unwrap(); + state.send_to_chat(chat, text)?; + let members = state.members(chat)?; + let sender = state + .current_user + .clone() + .ok_or(ChatError::new("Not logged in", 1))?; + let timestamp = chrono::Utc::now().timestamp(); + let control = P2pControl::ChatMessage { + chat: chat.clone(), + from: sender.clone(), + text: text.clone(), + timestamp, + }; + for uid in members { + if uid != sender { + if let Some(addr) = state.get_contact(&uid) { + let _ = p2p::send_control(&addr, control.clone(), &sender); + } + if text.contains(&format!("@{}", uid)) { + state.deliver_to_user( + uid.clone(), + crate::chat::Message { + from: sender.clone(), + text: text.clone(), + chat: chat.clone(), + timestamp, + }, + ); + } + } + } + Ok(Value::Bool(true)) + } else { + Err(ChatError::new( + "sendChat expects a String (chat name) and a String (message text)", + 1, + )) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "sendFileToChat".to_string(), + builtin!("sendFileToChat", 2, move |args| { + if let [Value::String(chat), Value::String(path)] = &args[..] { + let bytes = std::fs::read(&path) + .map_err(|e| ChatError::new(&format!("sendFileToChat: {}", e), 1))?; + let b64 = STANDARD.encode(&bytes); + let filename = std::path::Path::new(&path) + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(); + let state = state_clone.lock().unwrap(); + let members = state.members(chat)?; + let sender = state + .current_user + .clone() + .ok_or(ChatError::new("Not logged in", 1))?; + let msg = P2pMessage { + msg_type: "file".to_string(), + from: sender.clone(), + to: "".to_string(), + text: None, + chat: Some(chat.clone()), + filename: Some(filename.clone()), + data_base64: Some(b64), + timestamp: Some(chrono::Utc::now().timestamp()), + control: None, + }; + for uid in members { + if uid != sender { + if let Some(addr) = state.get_contact(&uid) { + let _ = p2p::send_message_to(&addr, msg.clone()); + } + } + } + Ok(Value::Bool(true)) + } else { + Err(ChatError::new( + "sendFileToChat expects a String (chat name) and a String (file path)", + 1, + )) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "inbox".to_string(), + builtin!("inbox", 0, move |_args| { + let state = state_clone.lock().unwrap(); + let user = state + .current_user + .clone() + .ok_or(ChatError::new("Not logged in", 1))?; + let msgs = state.get_inbox(&user); + Ok(Value::List(msgs)) + }), + ); + + let state_clone = state.clone(); + env.set( + "history".to_string(), + builtin!("history", 1, move |args| { + if let [Value::String(chat)] = &args[..] { + let msgs = state_clone.lock().unwrap().get_history(chat); + Ok(Value::List(msgs)) + } else { + Err(ChatError::new("history expects a String (chat name)", 1)) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "downloads".to_string(), + builtin!("downloads", 0, move |_args| { + let state = state_clone.lock().unwrap(); + let downloads = state.downloads.clone(); + Ok(Value::List(downloads)) + }), + ); + + let state_clone = state.clone(); + env.set( + "saveFile".to_string(), + builtin!("saveFile", 2, move |args| { + if let [Value::Num(index), Value::String(path)] = &args[..] { + let mut state = state_clone.lock().unwrap(); + state.save_file(*index as usize, path)?; + Ok(Value::Bool(true)) + } else { + Err(ChatError::new( + "saveFile expects a Num index and a String path", + 1, + )) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "serverStart".to_string(), + builtin!("serverStart", 2, move |args| { + let (addr, password) = match &args[..] { + [Value::String(addr), Value::String(pass)] => (addr.clone(), Some(pass.clone())), + [Value::String(addr)] => (addr.clone(), None), + _ => { + return Err(ChatError::new( + "serverStart expects a String address and optional String password", + 1, + )) + } + }; + let mut state = state_clone.lock().unwrap(); + if state.server_handle.is_some() { + return Ok(Value::Unit); + } + match server::start_contacts_server(&addr, password) { + Ok((_db, handle, stop_tx)) => { + state.server_handle = Some(handle); + state.server_stop = Some(stop_tx); + state.contact_server_addr = Some(addr); + Ok(Value::Unit) + } + Err(e) => Err(ChatError::new(&format!("Failed to start server: {}", e), 1)), + } + }), + ); + + let state_clone = state.clone(); + env.set( + "serverStop".to_string(), + builtin!("serverStop", 0, move |_args| { + let mut state = state_clone.lock().unwrap(); + if let Some(stop_tx) = state.server_stop.take() { + let _ = stop_tx.send(()); + if let Some(handle) = state.server_handle.take() { + let _ = handle.join(); + } + state.contact_server_addr = None; + } + Ok(Value::Unit) + }), + ); + + let state_clone = state.clone(); + env.set( + "connect".to_string(), + builtin!("connect", 3, move |args| { + let (host, uid, password) = match &args[..] { + [Value::String(host), Value::Uid(uid), Value::String(pass)] => { + (host.clone(), uid.clone(), Some(pass.clone())) + } + [Value::String(host), Value::Uid(uid)] => (host.clone(), uid.clone(), None), + _ => { + return Err(ChatError::new( + "connect expects a String host, a Uid, and optional String password", + 1, + )) + } + }; + use std::io::{BufRead, Write}; + let stream = std::net::TcpStream::connect(&host) + .map_err(|e| ChatError::new(&format!("connect: {}", e), 1))?; + let connector = p2p::get_tls_connector(); + let mut stream = connector + .connect("localhost", stream) + .map_err(|e| ChatError::new(&format!("connect: {}", e), 1))?; + if let Some(pass) = password { + writeln!(stream, "PASSWORD {}", pass) + .map_err(|e| ChatError::new(&format!("connect: {}", e), 1))?; + let mut response = String::new(); + let mut reader = std::io::BufReader::new(&mut stream); + reader + .read_line(&mut response) + .map_err(|e| ChatError::new(&format!("connect: {}", e), 1))?; + if !response.trim().starts_with("OK") { + return Err(ChatError::new("Authentication failed", 1)); + } + } + let p2p_port = state_clone.lock().unwrap().p2p_port; + let own_addr = { + let state = state_clone.lock().unwrap(); + if let Some(ip) = &state.external_ip { + format!("{}:{}", ip, p2p_port) + } else { + format!("127.0.0.1:{}", p2p_port) + } + }; + writeln!(stream, "REGISTER {} {}", uid, own_addr) + .map_err(|e| ChatError::new(&format!("connect: {}", e), 1))?; + writeln!(stream, "GET").map_err(|e| ChatError::new(&format!("connect: {}", e), 1))?; + let mut response = String::new(); + let mut reader = std::io::BufReader::new(stream); + while let Ok(n) = reader.read_line(&mut response) { + if n == 0 { + break; + } + } + let mut state = state_clone.lock().unwrap(); + for line in response.lines() { + let parts: Vec<&str> = line.splitn(2, ' ').collect(); + if parts.len() == 2 { + state + .contacts + .insert(parts[0].to_string(), parts[1].to_string()); + } + } + let count = state.contacts.len() as i64; + Ok(Value::Num(count as f64)) + }), + ); + + let state_clone = state.clone(); + env.set( + "addContact".to_string(), + builtin!("addContact", 2, move |args| { + if let [Value::Uid(uid), Value::String(addr)] = &args[..] { + state_clone.lock().unwrap().add_contact(uid.clone(), addr.clone()); + Ok(Value::Unit) + } else { + Err(ChatError::new("addContact expects a Uid and a String address", 1)) + } + }), + ); + + let state_clone = state.clone(); + env.set( + "removeContact".to_string(), + builtin!("removeContact", 1, move |args| { + if let [Value::Uid(uid)] = &args[..] { + state_clone.lock().unwrap().remove_contact(uid); + Ok(Value::Unit) + } else { + Err(ChatError::new("removeContact expects a Uid argument", 1)) + } + }), + ); + + env.set( + "getPublicIP".to_string(), + builtin!("getPublicIP", 0, |_args| { + match get("https://api.ipify.org") { + Ok(resp) => { + if resp.status().is_success() { + if let Ok(ip) = resp.text() { + return Ok(Value::Maybe(Some(Box::new(Value::String( + ip.trim().to_string(), + ))))); + } + } + Ok(Value::Maybe(None)) + } + Err(_) => Ok(Value::Maybe(None)), + } + }), + ); + + let state_ip = state.clone(); + env.set( + "setExternalIP".to_string(), + builtin!("setExternalIP", 1, move |args| { + if let [Value::String(ip)] = &args[..] { + let mut state = state_ip.lock().unwrap(); + state.external_ip = Some(ip.clone()); + Ok(Value::Unit) + } else { + Err(ChatError::new("setExternalIP expects a String IP address", 1)) + } + }), + ); + + let state_spawn = state.clone(); + env.set( + "spawn".to_string(), + builtin!("spawn", 1, move |args| { + if let [closure] = &args[..] { + spawn_process(closure.clone(), Arc::clone(&state_spawn)) + } else { + Err(ChatError::new("spawn expects a function", 1)) + } + }), + ); + + env.set( + "procSelf".to_string(), + builtin!("procSelf", 0, |_args| proc_self()), + ); + + env.set( + "procSend".to_string(), + builtin!("procSend", 2, |args| { + if let [Value::Pid(pid), val] = &args[..] { + proc_send(*pid, val.clone()) + } else { + Err(ChatError::new("procSend expects a Pid and a value", 1)) + } + }), + ); + + env.set( + "procRecv".to_string(), + builtin!("procRecv", 0, |_args| proc_recv()), + ); + + env.set( + "procWait".to_string(), + builtin!("procWait", 1, |args| { + if let [Value::Pid(pid)] = &args[..] { + proc_wait(*pid) + } else { + Err(ChatError::new("procWait expects a Pid argument", 1)) + } + }), + ); + + env.set( + "procExit".to_string(), + builtin!("procExit", 1, |args| { + if let [val] = &args[..] { + proc_exit(val.clone()) + } else { + Err(ChatError::new("procExit expects a value", 1)) + } + }), + ); + + env.set( + "sleep".to_string(), + builtin!("sleep", 1, |args| { + if let [Value::Duration(dur)] = &args[..] { + sleep(*dur) + } else { + Err(ChatError::new("sleep expects a Duration argument", 1)) + } + }), + ); + + let state_after = state.clone(); + env.set( + "after".to_string(), + builtin!("after", 2, move |args| { + if let [Value::Duration(dur), closure] = &args[..] { + after(*dur, closure.clone(), Arc::clone(&state_after)) + } else { + Err(ChatError::new( + "after expects a Duration and a nullary function", + 1, + )) + } + }), + ); + + env.set("Nothing".to_string(), Value::Maybe(None)); + env.set( + "Just".to_string(), + builtin!("Just", 1, |args| { + if args.len() == 1 { + Ok(Value::Maybe(Some(Box::new(args[0].clone())))) + } else { + Err(ChatError::new("Just expects exactly one argument", 1)) + } + }), + ); + + let state_maybe = state.clone(); + env.set( + "maybe".to_string(), + builtin!("maybe", 3, move |args| { + if let [Value::Closure(params, body, env), default, Value::Maybe(maybe_val)] = &args[..] { + let val = match maybe_val { + Some(v) => *v.clone(), + None => default.clone(), + }; + let mut new_env = env.clone(); + new_env.set(params[0].clone(), val); + eval::eval_expr(&body, &mut new_env, Arc::clone(&state_maybe)) + } else { + Err(ChatError::new( + "maybe expects a function, a default value, and a Maybe", + 1, + )) + } + }), + ); + + env.set( + "mapGet".to_string(), + builtin!("mapGet", 2, |args| { + if let [Value::Map(map), key] = &args[..] { + let key_str = key.display(); + Ok(map.get(&key_str).cloned().unwrap_or(Value::Unit)) + } else { + Err(ChatError::new("mapGet expects a Map and a key", 1)) + } + }), + ); + + env.set( + "mapSet".to_string(), + builtin!("mapSet", 3, |args| { + if let [Value::Map(map), key, val] = &args[..] { + let mut new_map = map.clone(); + new_map.insert(key.display(), val.clone()); + Ok(Value::Map(new_map)) + } else { + Err(ChatError::new("mapSet expects a Map, a key, and a value", 1)) + } + }), + ); + + env.set( + "mapRemove".to_string(), + builtin!("mapRemove", 2, |args| { + if let [Value::Map(map), key] = &args[..] { + let mut new_map = map.clone(); + new_map.remove(&key.display()); + Ok(Value::Map(new_map)) + } else { + Err(ChatError::new("mapRemove expects a Map and a key", 1)) + } + }), + ); + + env.set( + "mapKeys".to_string(), + builtin!("mapKeys", 1, |args| { + if let [Value::Map(map)] = &args[..] { + let keys: Vec = map.keys().map(|s| Value::String(s.clone())).collect(); + Ok(Value::List(keys)) + } else { + Err(ChatError::new("mapKeys expects a Map argument", 1)) + } + }), + ); + + env.set( + "mapValues".to_string(), + builtin!("mapValues", 1, |args| { + if let [Value::Map(map)] = &args[..] { + let values: Vec = map.values().cloned().collect(); + Ok(Value::List(values)) + } else { + Err(ChatError::new("mapValues expects a Map argument", 1)) + } + }), + ); + + env.set( + "mapEntries".to_string(), + builtin!("mapEntries", 1, |args| { + if let [Value::Map(map)] = &args[..] { + let entries: Vec = map + .iter() + .map(|(k, v)| Value::Tuple(vec![Value::String(k.clone()), v.clone()])) + .collect(); + Ok(Value::List(entries)) + } else { + Err(ChatError::new("mapEntries expects a Map argument", 1)) + } + }), + ); + + env.set( + "mapContains".to_string(), + builtin!("mapContains", 2, |args| { + if let [Value::Map(map), key] = &args[..] { + Ok(Value::Bool(map.contains_key(&key.display()))) + } else { + Err(ChatError::new("mapContains expects a Map and a key", 1)) + } + }), + ); + + env.set( + "mapSize".to_string(), + builtin!("mapSize", 1, |args| { + if let [Value::Map(map)] = &args[..] { + Ok(Value::Num(map.len() as f64)) + } else { + Err(ChatError::new("mapSize expects a Map argument", 1)) + } + }), + ); + + let state_mf = state.clone(); + env.set( + "mapFilter".to_string(), + Value::BuiltinFunc( + "mapFilter".to_string(), + 2, + Arc::new(move |args| { + if let [Value::Closure(params, body, closure_env), Value::Map(map)] = &args[..] { + let mut new_map = BTreeMap::new(); + let state_ref = state_mf.clone(); + for (k, v) in map { + let mut env = closure_env.clone(); + env.set(params[0].clone(), Value::String(k.clone())); + env.set(params[1].clone(), v.clone()); + let val = eval::eval_expr(&body, &mut env, state_ref.clone())?; + if let Value::Bool(b) = val { + if b { + new_map.insert(k.clone(), v.clone()); + } + } else { + return Err(ChatError::new( + "mapFilter predicate must return a Bool value", + 1, + )); + } + } + Ok(Value::Map(new_map)) + } else { + Err(ChatError::new( + "mapFilter expects a function (k -> v -> Bool) and a Map", + 1, + )) + } + }), + ), + ); + + env.set( + "mapMerge".to_string(), + builtin!("mapMerge", 2, |args| { + if let [Value::Map(a), Value::Map(b)] = &args[..] { + let mut merged = a.clone(); + for (k, v) in b { + merged.insert(k.clone(), v.clone()); + } + Ok(Value::Map(merged)) + } else { + Err(ChatError::new("mapMerge expects two Map arguments", 1)) + } + }), + ); + + env.set( + "setAdd".to_string(), + builtin!("setAdd", 2, |args| { + if let [Value::Set(set), elem] = &args[..] { + let mut new_set = set.clone(); + new_set.insert(elem.display()); + Ok(Value::Set(new_set)) + } else { + Err(ChatError::new("setAdd expects a Set and an element", 1)) + } + }), + ); + + env.set( + "setRemove".to_string(), + builtin!("setRemove", 2, |args| { + if let [Value::Set(set), elem] = &args[..] { + let mut new_set = set.clone(); + new_set.remove(&elem.display()); + Ok(Value::Set(new_set)) + } else { + Err(ChatError::new("setRemove expects a Set and an element", 1)) + } + }), + ); + + env.set( + "setContains".to_string(), + builtin!("setContains", 2, |args| { + if let [Value::Set(set), elem] = &args[..] { + Ok(Value::Bool(set.contains(&elem.display()))) + } else { + Err(ChatError::new("setContains expects a Set and an element", 1)) + } + }), + ); + + env.set( + "setUnion".to_string(), + builtin!("setUnion", 2, |args| { + if let [Value::Set(a), Value::Set(b)] = &args[..] { + let union: BTreeSet<_> = a.union(b).cloned().collect(); + Ok(Value::Set(union)) + } else { + Err(ChatError::new("setUnion expects two Set arguments", 1)) + } + }), + ); + + env.set( + "setIntersection".to_string(), + builtin!("setIntersection", 2, |args| { + if let [Value::Set(a), Value::Set(b)] = &args[..] { + let inter: BTreeSet<_> = a.intersection(b).cloned().collect(); + Ok(Value::Set(inter)) + } else { + Err(ChatError::new("setIntersection expects two Set arguments", 1)) + } + }), + ); + + env.set( + "setDifference".to_string(), + builtin!("setDifference", 2, |args| { + if let [Value::Set(a), Value::Set(b)] = &args[..] { + let diff: BTreeSet<_> = a.difference(b).cloned().collect(); + Ok(Value::Set(diff)) + } else { + Err(ChatError::new("setDifference expects two Set arguments", 1)) + } + }), + ); + + env.set( + "setSize".to_string(), + builtin!("setSize", 1, |args| { + if let [Value::Set(set)] = &args[..] { + Ok(Value::Num(set.len() as f64)) + } else { + Err(ChatError::new("setSize expects a Set argument", 1)) + } + }), + ); + + let state_sf = state.clone(); + env.set( + "setFilter".to_string(), + Value::BuiltinFunc( + "setFilter".to_string(), + 2, + Arc::new(move |args| { + if let [Value::Closure(params, body, closure_env), Value::Set(set)] = &args[..] { + let mut new_set = BTreeSet::new(); + let state_ref = state_sf.clone(); + for item in set { + let mut env = closure_env.clone(); + env.set(params[0].clone(), Value::String(item.clone())); + let val = eval::eval_expr(&body, &mut env, state_ref.clone())?; + if let Value::Bool(b) = val { + if b { + new_set.insert(item.clone()); + } + } else { + return Err(ChatError::new( + "setFilter predicate must return a Bool value", + 1, + )); + } + } + Ok(Value::Set(new_set)) + } else { + Err(ChatError::new( + "setFilter expects a function (a -> Bool) and a Set", + 1, + )) + } + }), + ), + ); + + let state_sm = state.clone(); + env.set( + "setMap".to_string(), + Value::BuiltinFunc( + "setMap".to_string(), + 2, + Arc::new(move |args| { + if let [Value::Closure(params, body, closure_env), Value::Set(set)] = &args[..] { + let mut new_set = BTreeSet::new(); + let state_ref = state_sm.clone(); + for item in set { + let mut env = closure_env.clone(); + env.set(params[0].clone(), Value::String(item.clone())); + let val = eval::eval_expr(&body, &mut env, state_ref.clone())?; + new_set.insert(val.display()); + } + Ok(Value::Set(new_set)) + } else { + Err(ChatError::new( + "setMap expects a function (a -> b) and a Set", + 1, + )) + } + }), + ), + ); + + env.set( + "listToSet".to_string(), + builtin!("listToSet", 1, |args| { + if let [Value::List(list)] = &args[..] { + let set: BTreeSet<_> = list.iter().map(|v| v.display()).collect(); + Ok(Value::Set(set)) + } else { + Err(ChatError::new("listToSet expects a List argument", 1)) + } + }), + ); + + env.set( + "mapToList".to_string(), + builtin!("mapToList", 1, |args| { + if let [Value::Map(map)] = &args[..] { + let list: Vec = map + .iter() + .map(|(k, v)| Value::Tuple(vec![Value::String(k.clone()), v.clone()])) + .collect(); + Ok(Value::List(list)) + } else { + Err(ChatError::new("mapToList expects a Map argument", 1)) + } + }), + ); + + env.set( + "sha256".to_string(), + builtin!("sha256", 1, |args| { + if let [Value::ByteString(data)] = &args[..] { + let hash = Sha256::digest(data); + Ok(Value::ByteString(hash.to_vec())) + } else { + Err(ChatError::new("sha256 expects a ByteString argument", 1)) + } + }), + ); + + env.set( + "sha256String".to_string(), + builtin!("sha256String", 1, |args| { + if let [Value::String(s)] = &args[..] { + let hash = Sha256::digest(s.as_bytes()); + Ok(Value::String(hex::encode(hash))) + } else { + Err(ChatError::new("sha256String expects a String argument", 1)) + } + }), + ); + + env.set( + "kyberKeyPair".to_string(), + builtin!("kyberKeyPair", 0, |_args| { + let (pk, sk) = keypair(); + Ok(Value::Tuple(vec![ + Value::ByteString(pk.as_bytes().to_vec()), + Value::ByteString(sk.as_bytes().to_vec()), + ])) + }), + ); + + env.set( + "kyberEncapsulate".to_string(), + builtin!("kyberEncapsulate", 1, |args| { + if let [Value::ByteString(pk_bytes)] = &args[..] { + let pk = pqcrypto_kyber::kyber768::PublicKey::from_bytes(pk_bytes) + .map_err(|_| ChatError::new("Invalid Kyber public key", 1))?; + let (ciphertext, shared_secret) = encapsulate(&pk); + Ok(Value::Tuple(vec![ + Value::ByteString(ciphertext.as_bytes().to_vec()), + Value::ByteString(shared_secret.as_bytes().to_vec()), + ])) + } else { + Err(ChatError::new( + "kyberEncapsulate expects a ByteString (public key)", + 1, + )) + } + }), + ); + + env.set( + "kyberDecapsulate".to_string(), + builtin!("kyberDecapsulate", 2, |args| { + if let [Value::ByteString(sk_bytes), Value::ByteString(ciphertext_bytes)] = &args[..] { + let sk = pqcrypto_kyber::kyber768::SecretKey::from_bytes(sk_bytes) + .map_err(|_| ChatError::new("Invalid Kyber secret key", 1))?; + let ciphertext = pqcrypto_kyber::kyber768::Ciphertext::from_bytes(ciphertext_bytes) + .map_err(|_| ChatError::new("Invalid Kyber ciphertext", 1))?; + let shared_secret = decapsulate(&ciphertext, &sk); + Ok(Value::ByteString(shared_secret.as_bytes().to_vec())) + } else { + Err(ChatError::new( + "kyberDecapsulate expects SecretKey and Ciphertext as ByteStrings", + 1, + )) + } + }), + ); + + env.set( + "exit".to_string(), + builtin!("exit", 0, |_args| { + std::process::exit(0); + }), + ); + + let state_load = state.clone(); + let global_env_load = global_env.clone(); + env.set( + "load".to_string(), + builtin!("load", 1, move |args| { + if let [Value::String(path)] = &args[..] { + let content = match fs::read_to_string(&path) { + Ok(c) => c, + Err(e) => return Err(ChatError::new(&format!("Failed to read file: {}", e), 1)), + }; + let expr = match crate::parser::parse_script(&content) { + Ok(e) => e, + Err(e) => return Err(ChatError::new(&format!("Parse error: {}", e), 1)), + }; + let mut env_guard = global_env_load.lock().unwrap(); + match crate::eval::eval_expr(&expr, &mut env_guard, state_load.clone()) { + Ok(_) => Ok(Value::Unit), + Err(e) => Err(ChatError::new(&format!("Execution error: {}", e), 1)), + } + } else { + Err(ChatError::new("load expects a String (file path)", 1)) + } + }), + ); + + // Removed 'del' builtin. + + // p2pPort builtin + let state_for_p2p_port = state.clone(); + env.set( + "p2pPort".to_string(), + builtin!("p2pPort", 0, move |_args| { + let port = state_for_p2p_port.lock().unwrap().p2p_port; + Ok(Value::Num(port as f64)) + }), + ); + + // Start P2P listener, but without printing anything. + { + let mut state_guard = state.lock().unwrap(); + if state_guard.p2p_port == 0 { + state_guard.p2p_port = 19000; + drop(state_guard); + p2p::start_p2p_listener(19000, state.clone()); + } + } +} + +fn serde_json_to_plic(v: SerdeValue) -> JsonValue { + match v { + SerdeValue::Null => JsonValue::Null, + SerdeValue::Bool(b) => JsonValue::Bool(b), + SerdeValue::Number(n) => JsonValue::Number(n.as_f64().unwrap_or(0.0)), + SerdeValue::String(s) => JsonValue::String(s), + SerdeValue::Array(arr) => { + let items: Vec = arr.into_iter().map(serde_json_to_plic).collect(); + JsonValue::Array(items) + } + SerdeValue::Object(map) => { + let mut obj = BTreeMap::new(); + for (k, v) in map { + obj.insert(k, serde_json_to_plic(v)); + } + JsonValue::Object(obj) + } + } +} + +fn plic_json_to_serde(j: &JsonValue) -> SerdeValue { + match j { + JsonValue::Null => SerdeValue::Null, + JsonValue::Bool(b) => SerdeValue::Bool(*b), + JsonValue::Number(n) => { + SerdeValue::Number(serde_json::Number::from_f64(*n).unwrap_or(serde_json::Number::from(0))) + } + JsonValue::String(s) => SerdeValue::String(s.clone()), + JsonValue::Array(arr) => { + let items: Vec = arr.iter().map(plic_json_to_serde).collect(); + SerdeValue::Array(items) + } + JsonValue::Object(map) => { + let mut obj = serde_json::Map::new(); + for (k, v) in map { + obj.insert(k.clone(), plic_json_to_serde(v)); + } + SerdeValue::Object(obj) + } + } +} diff --git a/src/chat.rs b/src/chat.rs new file mode 100644 index 0000000..9c68142 --- /dev/null +++ b/src/chat.rs @@ -0,0 +1,292 @@ +//! Chat state and logic with control messages. + +use crate::error::ChatError; +use crate::types::Value; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use std::sync::mpsc::Sender; +use std::thread::JoinHandle; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct User { + pub uid: String, + pub addr: Option, + pub online: bool, +} + +#[derive(Debug, Clone)] +pub struct Message { + pub from: String, + pub text: String, + pub chat: String, + pub timestamp: i64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum P2pControl { + NewChat { name: String, members: Vec, from: String }, + AddMember { chat: String, uid: String }, + RemoveMember { chat: String, uid: String }, + DeleteChat { name: String }, + ChatMessage { chat: String, from: String, text: String, timestamp: i64 }, +} + +pub struct ChatState { + pub users: BTreeMap, + pub chats: BTreeMap>, + pub messages: BTreeMap>, + pub inbox: BTreeMap>, + pub current_user: Option, + pub p2p_port: u16, + pub contact_server_addr: Option, + pub contacts: BTreeMap, + pub downloads: Vec, + pub server_handle: Option>, + pub server_stop: Option>, + pub external_ip: Option, +} + +impl ChatState { + pub fn new() -> Self { + ChatState { + users: BTreeMap::new(), + chats: BTreeMap::new(), + messages: BTreeMap::new(), + inbox: BTreeMap::new(), + current_user: None, + p2p_port: 0, + contact_server_addr: None, + contacts: BTreeMap::new(), + downloads: Vec::new(), + server_handle: None, + server_stop: None, + external_ip: None, + } + } + + pub fn login(&mut self, uid: String) -> Result<(), ChatError> { + if !uid.starts_with('@') { + return Err(ChatError::new("Invalid UID (must start with @)", 1)); + } + self.users.entry(uid.clone()).or_insert(User { + uid: uid.clone(), + addr: None, + online: true, + }); + self.current_user = Some(uid); + Ok(()) + } + + pub fn logout(&mut self) -> Result<(), ChatError> { + if let Some(uid) = &self.current_user { + if let Some(user) = self.users.get_mut(uid) { + user.online = false; + } + self.current_user = None; + Ok(()) + } else { + Err(ChatError::new("Not logged in", 1)) + } + } + + pub fn delete_user(&mut self, uid: &str) -> Result<(), ChatError> { + if self.current_user.as_deref() == Some(uid) { + self.logout()?; + } + if self.users.remove(uid).is_some() { + for members in self.chats.values_mut() { + members.retain(|u| u != uid); + } + self.contacts.remove(uid); + self.inbox.remove(uid); + Ok(()) + } else { + Err(ChatError::new("User not found", 1)) + } + } + + pub fn new_chat(&mut self, name: String, members: Vec) -> Result<(), ChatError> { + if self.chats.contains_key(&name) { + return Err(ChatError::new("Chat already exists", 1)); + } + self.chats.insert(name.clone(), members); + self.messages.entry(name).or_default(); + Ok(()) + } + + pub fn add_member(&mut self, uid: String, chat: &str) -> Result<(), ChatError> { + if let Some(members) = self.chats.get_mut(chat) { + if !members.contains(&uid) { + members.push(uid); + } + Ok(()) + } else { + Err(ChatError::new("Chat not found", 1)) + } + } + + pub fn remove_member(&mut self, uid: &str, chat: &str) -> Result<(), ChatError> { + if let Some(members) = self.chats.get_mut(chat) { + members.retain(|u| u != uid); + Ok(()) + } else { + Err(ChatError::new("Chat not found", 1)) + } + } + + pub fn delete_chat(&mut self, name: &str) -> Result<(), ChatError> { + if self.chats.remove(name).is_some() { + self.messages.remove(name); + Ok(()) + } else { + Err(ChatError::new("Chat not found", 1)) + } + } + + pub fn list_chats(&self) -> Vec { + self.chats.keys().cloned().collect() + } + + pub fn members(&self, chat: &str) -> Result, ChatError> { + self.chats.get(chat).cloned().ok_or_else(|| ChatError::new("Chat not found", 1)) + } + + pub fn open_chat(&mut self, chat: String) -> Result<(), ChatError> { + if self.chats.contains_key(&chat) { + Ok(()) + } else { + Err(ChatError::new("Chat not found", 1)) + } + } + + pub fn send_message(&mut self, target: &str, text: &str) -> Result<(), ChatError> { + let sender = self.current_user.clone().ok_or(ChatError::new("Not logged in", 1))?; + let msg = Message { + from: sender.clone(), + text: text.to_string(), + chat: String::new(), + timestamp: chrono::Utc::now().timestamp(), + }; + if target == "@everyone" { + let all_recipients: Vec = self.chats.values() + .flat_map(|members| members.iter().cloned()) + .collect(); + for uid in all_recipients { + self.deliver_to_user(uid, msg.clone()); + } + } else { + self.deliver_to_user(target.to_string(), msg); + } + Ok(()) + } + + pub fn send_to_chat(&mut self, chat: &str, text: &str) -> Result<(), ChatError> { + let sender = self.current_user.clone().ok_or(ChatError::new("Not logged in", 1))?; + let members = self.chats.get(chat) + .ok_or(ChatError::new("Chat not found", 1))? + .clone(); + let msg = Message { + from: sender.clone(), + text: text.to_string(), + chat: chat.to_string(), + timestamp: chrono::Utc::now().timestamp(), + }; + self.messages.entry(chat.to_string()).or_default().push(msg.clone()); + for uid in members { + if uid != sender { + self.deliver_to_user(uid, msg.clone()); + } + } + Ok(()) + } + + pub fn deliver_to_user(&mut self, uid: String, msg: Message) { + self.inbox.entry(uid).or_default().push(msg); + } + + pub fn get_inbox(&self, user: &str) -> Vec { + self.inbox.get(user).cloned().unwrap_or_default().into_iter().map(|m| Value::ChatMsg { + from: m.from, + text: m.text, + chat: m.chat, + attachment: None, + }).collect() + } + + pub fn get_history(&self, chat: &str) -> Vec { + self.messages.get(chat).cloned().unwrap_or_default().into_iter().map(|m| Value::ChatMsg { + from: m.from, + text: m.text, + chat: m.chat, + attachment: None, + }).collect() + } + + pub fn save_file(&mut self, index: usize, path: &str) -> Result<(), ChatError> { + if index >= self.downloads.len() { + return Err(ChatError::new("Index out of bounds", 1)); + } + let val = &self.downloads[index]; + if let Value::FileTransfer { data, .. } = val { + std::fs::write(path, data).map_err(|e| ChatError::new(&e.to_string(), 1))?; + Ok(()) + } else { + Err(ChatError::new("Not a file transfer", 1)) + } + } + + pub fn add_contact(&mut self, uid: String, addr: String) { + self.contacts.insert(uid, addr); + } + + pub fn remove_contact(&mut self, uid: &str) { + self.contacts.remove(uid); + } + + pub fn get_contact(&self, uid: &str) -> Option { + self.contacts.get(uid).cloned() + } + + pub fn handle_control(&mut self, control: &P2pControl) -> Result<(), ChatError> { + match control { + P2pControl::NewChat { name, members, from: _ } => { + if !self.chats.contains_key(name) { + self.chats.insert(name.clone(), members.clone()); + self.messages.entry(name.clone()).or_default(); + } + } + P2pControl::AddMember { chat, uid } => { + if let Some(m) = self.chats.get_mut(chat) { + if !m.contains(uid) { + m.push(uid.clone()); + } + } + } + P2pControl::RemoveMember { chat, uid } => { + if let Some(m) = self.chats.get_mut(chat) { + m.retain(|u| u != uid); + } + } + P2pControl::DeleteChat { name } => { + self.chats.remove(name); + self.messages.remove(name); + } + P2pControl::ChatMessage { chat, from, text, timestamp } => { + let msg = Message { + from: from.clone(), + text: text.clone(), + chat: chat.clone(), + timestamp: *timestamp, + }; + self.messages.entry(chat.clone()).or_default().push(msg.clone()); + // Also deliver to inbox if mentioned + if let Some(current) = &self.current_user { + if text.contains(&format!("@{}", current)) { + self.deliver_to_user(current.clone(), msg); + } + } + } + } + Ok(()) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..a3a5b5d --- /dev/null +++ b/src/error.rs @@ -0,0 +1,46 @@ +use crate::ast::Span; +use std::fmt; + +#[derive(Debug, Clone)] +pub struct ChatError { + pub message: String, + pub code: i32, + pub span: Option, + pub file: Option, +} + +impl ChatError { + pub fn new(msg: &str, code: i32) -> Self { + ChatError { message: msg.to_string(), code, span: None, file: None } + } + pub fn with_span(msg: &str, code: i32, span: Span) -> Self { + ChatError { message: msg.to_string(), code, span: Some(span), file: None } + } + pub fn with_file(msg: &str, code: i32, file: String) -> Self { + ChatError { message: msg.to_string(), code, span: None, file: Some(file) } + } +} + +impl fmt::Display for ChatError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(span) = self.span { + write!(f, "{} at {:?}", self.message, span) + } else if let Some(file) = &self.file { + write!(f, "{} in file {}", self.message, file) + } else { + write!(f, "{} (code {})", self.message, self.code) + } + } +} + +impl From for ChatError { + fn from(e: std::io::Error) -> Self { + ChatError::new(&e.to_string(), 2) + } +} + +impl From for ChatError { + fn from(e: reqwest::Error) -> Self { + ChatError::new(&e.to_string(), 4) + } +} diff --git a/src/eval.rs b/src/eval.rs new file mode 100644 index 0000000..655706b --- /dev/null +++ b/src/eval.rs @@ -0,0 +1,1258 @@ +use crate::ast::*; +use crate::chat::ChatState; +use crate::error::ChatError; +use crate::types::{Environment, Value}; +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::sync::{Arc, Mutex, atomic::Ordering}; +use std::thread; +use std::sync::mpsc::{self, Sender, Receiver}; +use once_cell::sync::Lazy; +use std::time::Duration; +use std::sync::atomic; + +pub struct Process { + pub sender: Sender, + pub exit_sender: Sender, + pub exit_receiver: Receiver, + pub thread: Option>, +} + +pub static PROCESS_MANAGER: Lazy>> = Lazy::new(|| Mutex::new(BTreeMap::new())); +pub static NEXT_PID: Lazy> = Lazy::new(|| Mutex::new(1)); + +thread_local! { + static CURRENT_PID: std::cell::RefCell> = const { std::cell::RefCell::new(None) }; + static CURRENT_RECEIVER: std::cell::RefCell>> = const { std::cell::RefCell::new(None) }; +} + +static INTERRUPTED: atomic::AtomicBool = atomic::AtomicBool::new(false); + +/// Set the interruption flag (called from signal handler). +pub fn set_interrupted() { + INTERRUPTED.store(true, Ordering::SeqCst); +} + +/// Evaluate an expression in the given environment and chat state. +pub fn eval_expr( + expr: &Expr, + env: &mut Environment, + state: Arc>, +) -> Result { + match expr { + Expr::Lit(lit, _) => eval_literal(lit), + Expr::Var(name, span) => { + let val = env.get(name).ok_or_else(|| ChatError::with_span(&format!("Undefined variable '{}'", name), 1, *span))?; + if let Value::BuiltinFunc(_, 0, f) = val { + f(vec![]) + } else { + Ok(val) + } + } + Expr::Lambda(params, body, _) => Ok(Value::Closure(params.clone(), *body.clone(), env.clone())), + Expr::App(func, arg, span) => { + let f = eval_expr(func, env, Arc::clone(&state))?; + let a = eval_expr(arg, env, Arc::clone(&state))?; + apply(f, a, env, state, *span) + } + Expr::If(cond, then_expr, else_expr, span) => { + let c = eval_expr(cond, env, Arc::clone(&state))?; + match c { + Value::Bool(true) => eval_expr(then_expr, env, Arc::clone(&state)), + Value::Bool(false) => eval_expr(else_expr, env, Arc::clone(&state)), + _ => Err(ChatError::with_span("Condition must be Bool", 1, *span)), + } + } + Expr::Let { name, type_ann, def, body, span } => { + if env.vars.contains_key(name) { + return Err(ChatError::with_span(&format!("variable '{}' already defined", name), 1, *span)); + } + let val = eval_expr(def, env, Arc::clone(&state))?; + if let Some(ann) = type_ann { + let actual_type = type_name(&val); + if *ann != actual_type { + return Err(ChatError::with_span(&format!("Type mismatch: expected '{}', got '{}'", ann, actual_type), 1, *span)); + } + env.type_map.insert(name.clone(), ann.clone()); + } + env.set(name.clone(), val.clone()); + match body { + Some(b) => eval_expr(b, env, Arc::clone(&state)), + None => Ok(val), + } + } + Expr::Assign(name, expr, span) => { + let val = eval_expr(expr, env, Arc::clone(&state))?; + if !env.vars.contains_key(name) { + return Err(ChatError::with_span(&format!("Variable '{}' not defined for assignment", name), 1, *span)); + } + if let Some(expected_type) = env.type_map.get(name) { + let actual_type = type_name(&val); + if expected_type != &actual_type { + return Err(ChatError::with_span(&format!("Type mismatch: expected '{}', got '{}'", expected_type, actual_type), 1, *span)); + } + } + env.set(name.clone(), val.clone()); + Ok(val) + } + Expr::Case(scrut, arms, span) => { + let val = eval_expr(scrut, env, Arc::clone(&state))?; + for (pat, expr) in arms { + if let Some(bindings) = match_pattern(pat, &val) { + let mut new_env = env.clone(); + for (k, v) in bindings { + new_env.set(k, v); + } + return eval_expr(expr, &mut new_env, Arc::clone(&state)); + } + } + Err(ChatError::with_span("Non-exhaustive patterns", 1, *span)) + } + Expr::Try(body, _span) => { + match eval_expr(body, env, Arc::clone(&state)) { + Ok(v) => Ok(v), + Err(e) => Err(e), + } + } + Expr::Catch(body, pat, handler, _span) => { + match eval_expr(body, env, Arc::clone(&state)) { + Ok(v) => Ok(v), + Err(err) => { + let err_val = Value::String(err.message.clone()); + if let Some(bindings) = match_pattern(pat, &err_val) { + let mut new_env = env.clone(); + for (k, v) in bindings { + new_env.set(k, v); + } + eval_expr(handler, &mut new_env, Arc::clone(&state)) + } else { + Err(err) + } + } + } + } + Expr::Throw(msg, span) => { + let msg_val = eval_expr(msg, env, Arc::clone(&state))?; + Err(ChatError::with_span(&msg_val.display(), 1, *span)) + } + Expr::DataDef(_, _, _, _) => Ok(Value::Unit), + Expr::StructDef(_, _, _) => Ok(Value::Unit), + Expr::StructNew(_name, fields, _span) => { + let mut map = BTreeMap::new(); + for (f, e) in fields { + map.insert(f.clone(), eval_expr(e, env, Arc::clone(&state))?); + } + Ok(Value::Record(map)) + } + Expr::Constructor(_name, args, _span) => { + let mut vals = Vec::new(); + for arg in args { + vals.push(eval_expr(arg, env, Arc::clone(&state))?); + } + Ok(Value::Custom(_name.clone(), vals)) + } + Expr::Record(fields, _span) => { + let mut map = BTreeMap::new(); + for (k, v) in fields { + map.insert(k.clone(), eval_expr(v, env, Arc::clone(&state))?); + } + Ok(Value::Record(map)) + } + Expr::FieldAccess(expr, field, span) => { + let val = eval_expr(expr, env, Arc::clone(&state))?; + match val { + Value::Record(map) => { + map.get(field).cloned().ok_or_else(|| ChatError::with_span(&format!("Field '{}' not found", field), 1, *span)) + } + Value::ClassInstance { fields, .. } => { + fields.get(field).cloned().ok_or_else(|| ChatError::with_span(&format!("Field '{}' not found", field), 1, *span)) + } + _ => Err(ChatError::with_span("Field access on non-record or non-class", 1, *span)), + } + } + Expr::RecordUpdate(expr, updates, span) => { + let val = eval_expr(expr, env, Arc::clone(&state))?; + match val { + Value::Record(mut map) => { + for (k, v) in updates { + map.insert(k.clone(), eval_expr(v, env, Arc::clone(&state))?); + } + Ok(Value::Record(map)) + } + Value::ClassInstance { mut fields, class } => { + for (k, v) in updates { + fields.insert(k.clone(), eval_expr(v, env, Arc::clone(&state))?); + } + Ok(Value::ClassInstance { class, fields }) + } + _ => Err(ChatError::with_span("Record update on non-record or non-class instance", 1, *span)), + } + } + Expr::BinOp(op, l, r, span) => { + let left = eval_expr(l, env, Arc::clone(&state))?; + let right = eval_expr(r, env, Arc::clone(&state))?; + eval_binop(op, left, right, *span) + } + Expr::Concat(l, r, span) => { + let left = eval_expr(l, env, Arc::clone(&state))?; + let right = eval_expr(r, env, Arc::clone(&state))?; + match (left, right) { + (Value::String(a), Value::String(b)) => Ok(Value::String(a + &b)), + (Value::List(mut a), Value::List(b)) => { a.extend(b); Ok(Value::List(a)) }, + _ => Err(ChatError::with_span("++ works on strings or lists", 1, *span)), + } + } + Expr::FString(parts, _span) => { + let mut result = String::new(); + for part in parts { + match part { + FStringPart::Literal(s) => result.push_str(&s), + FStringPart::Expr(e) => { + let val = eval_expr(e, env, Arc::clone(&state))?; + result.push_str(&val.display()); + } + } + } + Ok(Value::String(result)) + } + Expr::List(elems, _span) => { + let vals: Result, _> = elems.iter().map(|e| eval_expr(e, env, Arc::clone(&state))).collect(); + Ok(Value::List(vals?)) + } + Expr::Range(start, end, span) => { + let s = eval_expr(start, env, Arc::clone(&state))?; + let e = eval_expr(end, env, Arc::clone(&state))?; + match (s, e) { + (Value::Num(a), Value::Num(b)) => { + let a = a as i64; + let b = b as i64; + let mut list = Vec::new(); + for i in a..b { + list.push(Value::Num(i as f64)); + } + Ok(Value::List(list)) + } + _ => Err(ChatError::with_span("Range requires Num (integer values)", 1, *span)), + } + } + Expr::Pipe(left, right, span) => { + let l = eval_expr(left, env, Arc::clone(&state))?; + apply(eval_expr(right, env, Arc::clone(&state))?, l, env, state, *span) + } + Expr::Dollar(func, arg, span) => { + let f = eval_expr(func, env, Arc::clone(&state))?; + let a = eval_expr(arg, env, Arc::clone(&state))?; + apply(f, a, env, state, *span) + } + Expr::LogicalAnd(l, r, span) => { + match eval_expr(l, env, Arc::clone(&state))? { + Value::Bool(true) => eval_expr(r, env, Arc::clone(&state)), + Value::Bool(false) => Ok(Value::Bool(false)), + _ => Err(ChatError::with_span("and requires Bool", 1, *span)), + } + } + Expr::LogicalOr(l, r, span) => { + match eval_expr(l, env, Arc::clone(&state))? { + Value::Bool(true) => Ok(Value::Bool(true)), + Value::Bool(false) => eval_expr(r, env, Arc::clone(&state)), + _ => Err(ChatError::with_span("or requires Bool", 1, *span)), + } + } + Expr::Not(expr, span) => { + match eval_expr(expr, env, Arc::clone(&state))? { + Value::Bool(b) => Ok(Value::Bool(!b)), + _ => Err(ChatError::with_span("not requires Bool", 1, *span)), + } + } + Expr::Tuple(elems, _span) => { + let vals: Result, _> = elems.iter().map(|e| eval_expr(e, env, Arc::clone(&state))).collect(); + Ok(Value::Tuple(vals?)) + } + Expr::Index(list, idx, span) => { + let coll = eval_expr(list, env, Arc::clone(&state))?; + let index = eval_expr(idx, env, Arc::clone(&state))?; + let index_int = match index { + Value::Num(x) => x as i64, + _ => return Err(ChatError::with_span("Index must be Num (integer)", 1, *span)), + }; + match coll { + Value::Map(map) => { + let key_str = index.display(); + if let Some(val) = map.get(&key_str) { + Ok(val.clone()) + } else { + Ok(Value::Unit) + } + } + Value::Set(set) => { + Ok(Value::Bool(set.contains(&index.display()))) + } + Value::List(v) => { + let i = index_int; + if i < 0 || i as usize >= v.len() { + Err(ChatError::with_span("Index out of bounds", 1, *span)) + } else { + Ok(v[i as usize].clone()) + } + } + Value::String(s) => { + let i = index_int; + if i < 0 || i as usize >= s.len() { + Err(ChatError::with_span("Index out of bounds", 1, *span)) + } else { + Ok(Value::Char(s.chars().nth(i as usize).unwrap())) + } + } + Value::ByteString(b) => { + let i = index_int; + if i < 0 || i as usize >= b.len() { + Err(ChatError::with_span("Index out of bounds", 1, *span)) + } else { + Ok(Value::Num(b[i as usize] as f64)) + } + } + _ => Err(ChatError::with_span("Indexing requires list, string, byte string, map, or set", 1, *span)), + } + } + Expr::For(var, iterable, body, span) => { + let iter_val = eval_expr(iterable, env, Arc::clone(&state))?; + match iter_val { + Value::List(list) => { + for item in list { + if INTERRUPTED.load(Ordering::SeqCst) { + return Err(ChatError::with_span("Execution interrupted by user", 1, *span)); + } + let mut new_env = env.clone(); + new_env.set(var.clone(), item); + eval_expr(body, &mut new_env, Arc::clone(&state))?; + } + Ok(Value::Unit) + } + Value::Set(set) => { + for item in set { + if INTERRUPTED.load(Ordering::SeqCst) { + return Err(ChatError::with_span("Execution interrupted by user", 1, *span)); + } + let mut new_env = env.clone(); + new_env.set(var.clone(), Value::String(item)); + eval_expr(body, &mut new_env, Arc::clone(&state))?; + } + Ok(Value::Unit) + } + Value::Map(map) => { + for (k, v) in map { + if INTERRUPTED.load(Ordering::SeqCst) { + return Err(ChatError::with_span("Execution interrupted by user", 1, *span)); + } + let mut new_env = env.clone(); + new_env.set(var.clone(), Value::Tuple(vec![Value::String(k), v])); + eval_expr(body, &mut new_env, Arc::clone(&state))?; + } + Ok(Value::Unit) + } + _ => Err(ChatError::with_span("for requires list, set, or map", 1, *span)), + } + } + Expr::While(cond, body, span) => { + loop { + if INTERRUPTED.load(Ordering::SeqCst) { + return Err(ChatError::with_span("Execution interrupted by user", 1, *span)); + } + let c = eval_expr(cond, env, Arc::clone(&state))?; + match c { + Value::Bool(true) => { + eval_expr(body, env, Arc::clone(&state))?; + } + Value::Bool(false) => break, + _ => return Err(ChatError::with_span("while condition must be Bool", 1, *span)), + } + } + Ok(Value::Unit) + } + Expr::Loop(body, span) => { + loop { + if INTERRUPTED.load(Ordering::SeqCst) { + return Err(ChatError::with_span("Execution interrupted by user", 1, *span)); + } + match eval_expr(body, env, Arc::clone(&state)) { + Ok(Value::Break(Some(val))) => return Ok(*val), + Ok(Value::Break(None)) => return Ok(Value::Unit), + Ok(_) => continue, + Err(e) => return Err(e), + } + } + } + Expr::Break(opt, _span) => { + let val = match opt { + Some(e) => eval_expr(e, env, Arc::clone(&state))?, + None => Value::Unit, + }; + Ok(Value::Break(Some(Box::new(val)))) + } + Expr::Block(exprs, _span) => { + let mut block_env = env.clone(); + let mut result = Value::Unit; + for e in exprs { + result = eval_expr(e, &mut block_env, Arc::clone(&state))?; + } + for (k, v) in block_env.vars { + if !env.vars.contains_key(&k) { + env.set(k, v); + } + } + Ok(result) + } + Expr::ClassDef { name, extends, fields, methods, span: _ } => { + let mut class_info = BTreeMap::new(); + if let Some(parent) = extends { + class_info.insert("extends".to_string(), Value::String(parent.clone())); + } else { + class_info.insert("extends".to_string(), Value::Unit); + } + let mut method_env = Environment::new(); + for m in methods { + let closure = Value::Closure( + m.params.clone(), + *m.body.clone(), + env.clone(), + ); + method_env.set(m.name.clone(), closure); + } + let field_names: Vec = fields.iter().map(|(n, _)| Value::String(n.clone())).collect(); + class_info.insert("fields".to_string(), Value::List(field_names)); + let mut method_map = BTreeMap::new(); + for (k, v) in method_env.vars { + method_map.insert(k, v); + } + class_info.insert("methods".to_string(), Value::Record(method_map)); + env.set(name.clone(), Value::Record(class_info)); + Ok(Value::Unit) + } + Expr::New(class_name, args, span) => { + // Create instance with fields from args (by position) + let class_def = env.get(class_name) + .ok_or_else(|| ChatError::with_span(&format!("Class '{}' not defined", class_name), 1, *span))?; + if let Value::Record(ref info) = class_def { + let fields = if let Some(Value::List(field_list)) = info.get("fields") { + field_list.iter().filter_map(|v| { + if let Value::String(s) = v { Some(s.clone()) } else { None } + }).collect::>() + } else { + Vec::new() + }; + let mut field_values = BTreeMap::new(); + for (i, field) in fields.iter().enumerate() { + if i < args.len() { + let val = eval_expr(&args[i], env, Arc::clone(&state))?; + field_values.insert(field.clone(), val); + } else { + field_values.insert(field.clone(), Value::Unit); + } + } + let mut instance = Value::ClassInstance { + class: class_name.clone(), + fields: field_values, + }; + // If there is an init method, call it with the arguments + if let Some(init_method) = find_method(&class_def, "init", env).ok() { + // Call init on the instance with the same args + let mut call_args = vec![instance.clone()]; + for arg in args { + call_args.push(eval_expr(arg, env, Arc::clone(&state))?); + } + // Evaluate init method (it may modify fields via self) + match init_method { + Value::Closure(params, body, closure_env) => { + let mut new_env = closure_env.clone(); + if params.len() != call_args.len() { + return Err(ChatError::with_span("Wrong number of arguments to init", 1, *span)); + } + for (p, a) in params.iter().zip(call_args) { + new_env.set(p.clone(), a); + } + let _result = eval_expr(&body, &mut new_env, Arc::clone(&state))?; + // After init, the instance might be updated; we need to reflect changes. + // Since we passed instance as a value, it was cloned; but we need to get updated fields. + // We can retrieve self from the environment (if named "self") and use it. + if let Some(Value::ClassInstance { fields: updated_fields, .. }) = new_env.get("self") { + instance = Value::ClassInstance { + class: class_name.clone(), + fields: updated_fields.clone(), + }; + } + // Alternatively, we could require init to return the instance, but we'll rely on self mutation. + } + _ => return Err(ChatError::with_span("init is not a function", 1, *span)), + } + } + Ok(instance) + } else { + Err(ChatError::with_span("Invalid class definition", 1, *span)) + } + } + Expr::MethodCall(obj, method, args, span) => { + let obj_val = eval_expr(obj, env, Arc::clone(&state))?; + match obj_val { + Value::ClassInstance { ref class, .. } => { + let class_def = env.get(class) + .ok_or_else(|| ChatError::with_span(&format!("Class '{}' not found", class), 1, *span))?; + let method_val = find_method(&class_def, method, env)?; + let mut all_args = vec![obj_val.clone()]; + for a in args { + all_args.push(eval_expr(a, env, Arc::clone(&state))?); + } + match method_val { + Value::Closure(params, body, closure_env) => { + let mut new_env = closure_env.clone(); + if params.len() != all_args.len() { + return Err(ChatError::with_span("Wrong number of arguments", 1, *span)); + } + for (p, a) in params.iter().zip(all_args) { + new_env.set(p.clone(), a); + } + eval_expr(&body, &mut new_env, state) + } + _ => Err(ChatError::with_span("Method is not a function", 1, *span)), + } + } + _ => Err(ChatError::with_span("Method call on non-class instance", 1, *span)), + } + } + Expr::MapLiteral(entries, _span) => { + let mut map = BTreeMap::new(); + for (k, v) in entries { + let key = eval_expr(k, env, Arc::clone(&state))?; + let value = eval_expr(v, env, Arc::clone(&state))?; + map.insert(key.display(), value); + } + Ok(Value::Map(map)) + } + Expr::SetLiteral(elems, _span) => { + let mut set = BTreeSet::new(); + for e in elems { + let val = eval_expr(e, env, Arc::clone(&state))?; + set.insert(val.display()); + } + Ok(Value::Set(set)) + } + Expr::ListComp { expr, generators, filters, span: _ } => { + let mut result = Vec::new(); + fn eval_comp( + expr: &Expr, + gens: &[(String, Box)], + filters: &[Box], + env: &mut Environment, + state: Arc>, + acc: &mut Vec, + ) -> Result<(), ChatError> { + if gens.is_empty() { + for filter in filters { + let cond = eval_expr(filter, env, Arc::clone(&state))?; + if let Value::Bool(b) = cond { + if !b { return Ok(()); } + } else { + return Err(ChatError::with_span("Filter must be Bool", 1, e_span(filter))); + } + } + let val = eval_expr(expr, env, Arc::clone(&state))?; + acc.push(val); + return Ok(()); + } + let (var, iter_expr) = &gens[0]; + let iter_val = eval_expr(iter_expr, env, Arc::clone(&state))?; + let rest_gens = &gens[1..]; + match iter_val { + Value::List(list) => { + for item in list { + let mut new_env = env.clone(); + new_env.set(var.clone(), item); + eval_comp(expr, rest_gens, filters, &mut new_env, Arc::clone(&state), acc)?; + } + } + Value::Set(set) => { + for item in set { + let mut new_env = env.clone(); + new_env.set(var.clone(), Value::String(item)); + eval_comp(expr, rest_gens, filters, &mut new_env, Arc::clone(&state), acc)?; + } + } + Value::Map(map) => { + for (k, v) in map { + let mut new_env = env.clone(); + new_env.set(var.clone(), Value::Tuple(vec![Value::String(k), v])); + eval_comp(expr, rest_gens, filters, &mut new_env, Arc::clone(&state), acc)?; + } + } + _ => return Err(ChatError::with_span("Comprehension iterable must be list, set, or map", 1, e_span(iter_expr))), + } + Ok(()) + } + eval_comp(expr, generators, filters, env, Arc::clone(&state), &mut result)?; + Ok(Value::List(result)) + } + Expr::Cast(expr, target_type, span) => { + let val = eval_expr(expr, env, Arc::clone(&state))?; + cast_value(val, target_type, *span) + } + Expr::SuperMethod { method, args, span } => { + let self_val = env.get("self") + .ok_or_else(|| ChatError::with_span("super used outside of method (no self)", 1, *span))?; + match self_val { + Value::ClassInstance { ref class, fields: _ } => { + // Find parent class + let class_def = env.get(&class) + .ok_or_else(|| ChatError::with_span(&format!("Class '{}' not found", class), 1, *span))?; + let parent_name = if let Value::Record(info) = class_def { + if let Some(Value::String(parent)) = info.get("extends") { + if parent.is_empty() { + return Err(ChatError::with_span("No superclass", 1, *span)); + } + parent.clone() + } else { + return Err(ChatError::with_span("No superclass", 1, *span)); + } + } else { + return Err(ChatError::with_span("Invalid class definition", 1, *span)); + }; + // Find method in parent hierarchy (starting from parent) + let parent_def = env.get(&parent_name) + .ok_or_else(|| ChatError::with_span(&format!("Parent class '{}' not found", parent_name), 1, *span))?; + let method_val = find_method(&parent_def, method, env)?; + let mut all_args = vec![self_val.clone()]; + for a in args { + all_args.push(eval_expr(a, env, Arc::clone(&state))?); + } + match method_val { + Value::Closure(params, body, closure_env) => { + let mut new_env = closure_env.clone(); + if params.len() != all_args.len() { + return Err(ChatError::with_span("Wrong number of arguments to super method", 1, *span)); + } + for (p, a) in params.iter().zip(all_args) { + new_env.set(p.clone(), a); + } + eval_expr(&body, &mut new_env, state) + } + _ => Err(ChatError::with_span("Super method is not a function", 1, *span)), + } + } + _ => Err(ChatError::with_span("super called on non-class instance", 1, *span)), + } + } + } +} + +/// Helper to extract span from expression. +fn e_span(e: &Expr) -> Span { + match e { + Expr::Lit(_, s) => *s, + Expr::Var(_, s) => *s, + Expr::Lambda(_, _, s) => *s, + Expr::App(_, _, s) => *s, + Expr::If(_, _, _, s) => *s, + Expr::Let { span, .. } => *span, + Expr::Assign(_, _, s) => *s, + Expr::Case(_, _, s) => *s, + Expr::Try(_, s) => *s, + Expr::Catch(_, _, _, s) => *s, + Expr::Throw(_, s) => *s, + Expr::DataDef(_, _, _, s) => *s, + Expr::StructDef(_, _, s) => *s, + Expr::StructNew(_, _, s) => *s, + Expr::Constructor(_, _, s) => *s, + Expr::Record(_, s) => *s, + Expr::FieldAccess(_, _, s) => *s, + Expr::RecordUpdate(_, _, s) => *s, + Expr::List(_, s) => *s, + Expr::Range(_, _, s) => *s, + Expr::BinOp(_, _, _, s) => *s, + Expr::Concat(_, _, s) => *s, + Expr::Pipe(_, _, s) => *s, + Expr::Dollar(_, _, s) => *s, + Expr::LogicalAnd(_, _, s) => *s, + Expr::LogicalOr(_, _, s) => *s, + Expr::Not(_, s) => *s, + Expr::Tuple(_, s) => *s, + Expr::Index(_, _, s) => *s, + Expr::For(_, _, _, s) => *s, + Expr::While(_, _, s) => *s, + Expr::Loop(_, s) => *s, + Expr::Break(_, s) => *s, + Expr::Block(_, s) => *s, + Expr::ClassDef { span, .. } => *span, + Expr::New(_, _, s) => *s, + Expr::MethodCall(_, _, _, s) => *s, + Expr::MapLiteral(_, s) => *s, + Expr::SetLiteral(_, s) => *s, + Expr::FString(_, s) => *s, + Expr::ListComp { span, .. } => *span, + Expr::Cast(_, _, s) => *s, + Expr::SuperMethod { span, .. } => *span, + } +} + +fn eval_literal(lit: &Literal) -> Result { + Ok(match lit { + Literal::Int(i) => Value::Num(*i as f64), + Literal::Float(f) => Value::Num(*f), + Literal::Char(c) => Value::Char(*c), + Literal::String(s) => Value::String(s.clone()), + Literal::Bool(b) => Value::Bool(*b), + Literal::Unit => Value::Unit, + Literal::Uid(u) => Value::Uid(u.clone()), + Literal::ByteString(b) => Value::ByteString(b.clone()), + Literal::Duration(dur) => Value::Duration(*dur), + }) +} + +fn apply( + func: Value, + arg: Value, + _env: &mut Environment, + state: Arc>, + span: Span, +) -> Result { + match func { + Value::Closure(params, body, closure_env) => { + if params.is_empty() { + return Err(ChatError::with_span("Cannot apply nullary function", 1, span)); + } + let mut new_env = closure_env.clone(); + new_env.set(params[0].clone(), arg); + if params.len() == 1 { + eval_expr(&body, &mut new_env, state) + } else { + let remaining = params[1..].to_vec(); + Ok(Value::Closure(remaining, body, new_env)) + } + } + Value::BuiltinFunc(name, arity, f) => { + if arity == 0 { + f(vec![]) + } else if arity == 1 { + f(vec![arg]) + } else { + Ok(Value::Curry { + name, + arity, + f, + args: vec![arg], + }) + } + } + Value::Curry { name, arity, f, args } => { + let mut new_args = args; + new_args.push(arg); + if new_args.len() == arity { + f(new_args) + } else { + Ok(Value::Curry { + name, + arity, + f, + args: new_args, + }) + } + } + _ => Err(ChatError::with_span("Not a function", 1, span)), + } +} + +fn eval_binop(op: &BinOp, left: Value, right: Value, span: Span) -> Result { + match op { + BinOp::Add => { + if let (Value::String(_), _) = (&left, &right) { + return Err(ChatError::with_span("Use '++' to concatenate strings, not '+'", 1, span)); + } + if let (_, Value::String(_)) = (&left, &right) { + return Err(ChatError::with_span("Use '++' to concatenate strings, not '+'", 1, span)); + } + num_op(left, right, |a,b| a+b, span) + } + BinOp::Sub => num_op(left, right, |a,b| a-b, span), + BinOp::Mul => num_op(left, right, |a,b| a*b, span), + BinOp::Div => { + match (left, right) { + (Value::Num(a), Value::Num(b)) => { + if b == 0.0 { Err(ChatError::with_span("Division by zero", 1, span)) } + else { Ok(Value::Num(a / b)) } + } + _ => Err(ChatError::with_span("Division requires Num", 1, span)), + } + } + BinOp::Mod => { + match (left, right) { + (Value::Num(a), Value::Num(b)) => { + if b == 0.0 { Err(ChatError::with_span("Modulo by zero", 1, span)) } + else { Ok(Value::Num(a % b)) } + } + _ => Err(ChatError::with_span("Modulo requires Num (integers)", 1, span)), + } + } + BinOp::Eq => Ok(Value::Bool(left.display() == right.display())), + BinOp::Neq => Ok(Value::Bool(left.display() != right.display())), + BinOp::Lt => cmp_op(left, right, |a,b| a < b, span), + BinOp::Le => cmp_op(left, right, |a,b| a <= b, span), + BinOp::Gt => cmp_op(left, right, |a,b| a > b, span), + BinOp::Ge => cmp_op(left, right, |a,b| a >= b, span), + BinOp::Cons => match (left, right) { + (v, Value::List(mut list)) => { list.insert(0, v); Ok(Value::List(list)) } + _ => Err(ChatError::with_span("(:) requires element and list", 1, span)), + }, + BinOp::In => { + match right { + Value::List(list) => { + Ok(Value::Bool(list.iter().any(|x| x.display() == left.display()))) + } + Value::String(s) => { + if let Value::Char(c) = left { + Ok(Value::Bool(s.contains(c))) + } else { + Err(ChatError::with_span("in for string requires Char", 1, span)) + } + } + Value::ByteString(b) => { + if let Value::Num(i) = left { + Ok(Value::Bool(b.contains(&(i as u8)))) + } else { + Err(ChatError::with_span("in for ByteString requires Num (integer)", 1, span)) + } + } + Value::Set(set) => { + Ok(Value::Bool(set.contains(&left.display()))) + } + Value::Map(map) => { + Ok(Value::Bool(map.contains_key(&left.display()))) + } + _ => Err(ChatError::with_span("in requires list, string, byte string, set, or map", 1, span)), + } + } + BinOp::NotIn => { + match eval_binop(&BinOp::In, left, right, span) { + Ok(Value::Bool(b)) => Ok(Value::Bool(!b)), + Err(e) => Err(e), + _ => Err(ChatError::with_span("not in failed", 1, span)), + } + } + } +} + +fn num_op(l: Value, r: Value, op: fn(f64, f64) -> f64, span: Span) -> Result { + match (l, r) { + (Value::Num(a), Value::Num(b)) => Ok(Value::Num(op(a, b))), + _ => Err(ChatError::with_span("Arithmetic requires Num", 1, span)), + } +} + +fn cmp_op(l: Value, r: Value, f: fn(f64, f64) -> bool, _span: Span) -> Result { + let lf = to_f64(&l)?; + let rf = to_f64(&r)?; + Ok(Value::Bool(f(lf, rf))) +} + +fn to_f64(v: &Value) -> Result { + match v { + Value::Num(x) => Ok(*x), + _ => Err(ChatError::new("Comparison requires numeric value", 1)), + } +} + +pub fn match_pattern(pat: &Pattern, val: &Value) -> Option> { + match pat { + Pattern::Wildcard(_) => Some(BTreeMap::new()), + Pattern::Var(name, _) => { + let mut map = BTreeMap::new(); + map.insert(name.clone(), val.clone()); + Some(map) + } + Pattern::Literal(lit, _) => { + if let Ok(v) = eval_literal(lit) { + if v.display() == val.display() { + Some(BTreeMap::new()) + } else { None } + } else { None } + } + Pattern::Constructor(name, pats, _) => { + match val { + Value::Custom(cname, args) if cname == name => { + if pats.len() == args.len() { + let mut map = BTreeMap::new(); + for (p, a) in pats.iter().zip(args) { + if let Some(sub) = match_pattern(p, a) { + map.extend(sub); + } else { return None; } + } + Some(map) + } else { None } + } + _ => None, + } + } + Pattern::List(pats, _) => { + match val { + Value::List(vals) if pats.len() == vals.len() => { + let mut map = BTreeMap::new(); + for (p, v) in pats.iter().zip(vals) { + if let Some(sub) = match_pattern(p, v) { + map.extend(sub); + } else { return None; } + } + Some(map) + } + _ => None, + } + } + Pattern::Tuple(pats, _) => { + match val { + Value::Tuple(vals) if pats.len() == vals.len() => { + let mut map = BTreeMap::new(); + for (p, v) in pats.iter().zip(vals) { + if let Some(sub) = match_pattern(p, v) { + map.extend(sub); + } else { return None; } + } + Some(map) + } + _ => None, + } + } + Pattern::Record(field_pats, _) => { + match val { + Value::Record(map) => { + let mut bindings = BTreeMap::new(); + for (field, pat) in field_pats { + if let Some(v) = map.get(field) { + if let Some(sub) = match_pattern(pat, v) { + bindings.extend(sub); + } else { return None; } + } else { return None; } + } + Some(bindings) + } + Value::ClassInstance { fields, .. } => { + let mut bindings = BTreeMap::new(); + for (field, pat) in field_pats { + if let Some(v) = fields.get(field) { + if let Some(sub) = match_pattern(pat, v) { + bindings.extend(sub); + } else { return None; } + } else { return None; } + } + Some(bindings) + } + _ => None, + } + } + } +} + +/// Spawn a new process. +pub fn spawn_process(closure: Value, state: Arc>) -> Result { + match closure { + Value::Closure(params, body, env) if params.is_empty() => { + let (sender, receiver) = mpsc::channel(); + let (exit_sender, exit_receiver) = mpsc::channel(); + let pid = { + let mut next = NEXT_PID.lock().unwrap(); + let id = *next; + *next += 1; + id + }; + + let exit_sender_clone = exit_sender.clone(); + let handle = thread::spawn(move || { + CURRENT_PID.with(|cell| { + *cell.borrow_mut() = Some(pid); + }); + CURRENT_RECEIVER.with(|cell| { + *cell.borrow_mut() = Some(receiver); + }); + + let mut new_env = env; + let result = eval_expr(&body, &mut new_env, state); + let exit_val = match result { + Ok(v) => v, + Err(e) => Value::String(e.message), + }; + let _ = exit_sender_clone.send(exit_val); + }); + + let proc = Process { + sender, + exit_sender, + exit_receiver, + thread: Some(handle), + }; + PROCESS_MANAGER.lock().unwrap().insert(pid, proc); + Ok(Value::Pid(pid)) + } + _ => Err(ChatError::new("spawn expects a nullary function", 1)), + } +} + +pub fn proc_self() -> Result { + CURRENT_PID.with(|cell| { + if let Some(pid) = *cell.borrow() { + Ok(Value::Pid(pid)) + } else { + Err(ChatError::new("Not inside a process", 1)) + } + }) +} + +pub fn proc_send(pid: usize, val: Value) -> Result { + let mut map = PROCESS_MANAGER.lock().unwrap(); + if let Some(proc) = map.get_mut(&pid) { + proc.sender.send(val).map_err(|_| ChatError::new("Process receiver dead", 1))?; + Ok(Value::Unit) + } else { + Err(ChatError::new("Process not found", 1)) + } +} + +pub fn proc_recv() -> Result { + CURRENT_RECEIVER.with(|cell| { + let receiver_opt = cell.borrow_mut().take(); + if let Some(receiver) = receiver_opt { + let result = receiver.recv().map_err(|_| ChatError::new("Process mailbox closed", 1)); + *cell.borrow_mut() = Some(receiver); + result + } else { + Err(ChatError::new("Not inside a process or no receiver", 1)) + } + }) +} + +pub fn proc_wait(pid: usize) -> Result { + let mut map = PROCESS_MANAGER.lock().unwrap(); + if let Some(proc) = map.remove(&pid) { + drop(map); + let exit_val = proc.exit_receiver.recv() + .map_err(|_| ChatError::new("Process exited without sending value", 1))?; + if let Some(handle) = proc.thread { + let _ = handle.join(); + } + Ok(exit_val) + } else { + Err(ChatError::new("Process not found", 1)) + } +} + +pub fn proc_exit(val: Value) -> Result { + let pid = CURRENT_PID.with(|cell| *cell.borrow()); + if let Some(pid) = pid { + let map = PROCESS_MANAGER.lock().unwrap(); + if let Some(proc) = map.get(&pid) { + let exit_sender = proc.exit_sender.clone(); + drop(map); + let _ = exit_sender.send(val); + return Ok(Value::Unit); + } + } + Err(ChatError::new("Not inside a process", 1)) +} + +pub fn sleep(dur: Duration) -> Result { + thread::sleep(dur); + Ok(Value::Unit) +} + +pub fn after(dur: Duration, closure: Value, state: Arc>) -> Result { + match closure { + Value::Closure(params, body, env) if params.is_empty() => { + thread::spawn(move || { + thread::sleep(dur); + let mut new_env = env; + let _ = eval_expr(&body, &mut new_env, state); + }); + Ok(Value::Unit) + } + _ => Err(ChatError::new("after expects a nullary function", 1)), + } +} + +pub fn type_name(val: &Value) -> String { + match val { + Value::Num(_) => "Num".to_string(), + Value::Char(_) => "Char".to_string(), + Value::String(_) => "String".to_string(), + Value::Bool(_) => "Bool".to_string(), + Value::Unit => "Unit".to_string(), + Value::Uid(_) => "Uid".to_string(), + Value::ByteString(_) => "ByteString".to_string(), + Value::List(_) => "List".to_string(), + Value::Tuple(_) => "Tuple".to_string(), + Value::Closure(_, _, _) => "Closure".to_string(), + Value::BuiltinFunc(_, _, _) => "BuiltinFunc".to_string(), + Value::Curry { .. } => "Curry".to_string(), + Value::Custom(_, _) => "Custom".to_string(), + Value::Record(_) => "Record".to_string(), + Value::Pid(_) => "Pid".to_string(), + Value::DateTime(_) => "DateTime".to_string(), + Value::Duration(_) => "Duration".to_string(), + Value::Json(_) => "Json".to_string(), + Value::Maybe(_) => "Maybe".to_string(), + Value::Either(_) => "Either".to_string(), + Value::ChatMsg { .. } => "ChatMsg".to_string(), + Value::FileInfo { .. } => "FileInfo".to_string(), + Value::FileTransfer { .. } => "FileTransfer".to_string(), + Value::FetchOptions(_) => "FetchOptions".to_string(), + Value::FetchResult(_) => "FetchResult".to_string(), + Value::Map(_) => "Map".to_string(), + Value::Set(_) => "Set".to_string(), + Value::ClassInstance { .. } => "ClassInstance".to_string(), + Value::Break(_) => "Break".to_string(), + } +} + +fn find_method(class_def: &Value, method: &str, env: &Environment) -> Result { + if let Value::Record(info) = class_def { + if let Some(Value::Record(methods)) = info.get("methods") { + if let Some(m) = methods.get(method) { + return Ok(m.clone()); + } + } + if let Some(Value::String(parent_name)) = info.get("extends") { + if !parent_name.is_empty() { + let parent_def = env.get(parent_name) + .ok_or_else(|| ChatError::new(&format!("Parent class '{}' not found", parent_name), 1))?; + return find_method(&parent_def, method, env); + } + } + } + Err(ChatError::new(&format!("Method '{}' not found in class hierarchy", method), 1)) +} + +/// Cast a value to a given type name. +fn cast_value(val: Value, target_type: &str, span: Span) -> Result { + match target_type { + "Num" => { + match val { + Value::Num(_) => Ok(val), + Value::String(s) => { + s.parse::() + .map(Value::Num) + .map_err(|_| ChatError::with_span("Cannot cast string to Num", 1, span)) + } + Value::Char(c) => Ok(Value::Num(c as u32 as f64)), + Value::Bool(b) => Ok(Value::Num(if b { 1.0 } else { 0.0 })), + _ => Err(ChatError::with_span(&format!("Cannot cast {} to Num", type_name(&val)), 1, span)), + } + } + "String" => { + match val { + Value::String(_) => Ok(val), + Value::Num(x) => Ok(Value::String(x.to_string())), + Value::Char(c) => Ok(Value::String(c.to_string())), + Value::Bool(b) => Ok(Value::String(b.to_string())), + Value::Unit => Ok(Value::String("()".to_string())), + Value::Uid(u) => Ok(Value::String(u)), + _ => Err(ChatError::with_span(&format!("Cannot cast {} to String", type_name(&val)), 1, span)), + } + } + "Char" => { + match val { + Value::Char(_) => Ok(val), + Value::Num(x) => { + let c = x as u32; + if let Some(ch) = std::char::from_u32(c) { + Ok(Value::Char(ch)) + } else { + Err(ChatError::with_span("Invalid Unicode code point", 1, span)) + } + } + Value::String(s) => { + if s.len() == 1 { + Ok(Value::Char(s.chars().next().unwrap())) + } else { + Err(ChatError::with_span("String must be exactly one character", 1, span)) + } + } + _ => Err(ChatError::with_span(&format!("Cannot cast {} to Char", type_name(&val)), 1, span)), + } + } + "Bool" => { + match val { + Value::Bool(_) => Ok(val), + Value::Num(x) => Ok(Value::Bool(x != 0.0)), + Value::String(s) => Ok(Value::Bool(!s.is_empty())), + _ => Err(ChatError::with_span(&format!("Cannot cast {} to Bool", type_name(&val)), 1, span)), + } + } + "ByteString" => { + match val { + Value::ByteString(_) => Ok(val), + Value::String(s) => Ok(Value::ByteString(s.into_bytes())), + Value::List(list) => { + let mut bytes = Vec::new(); + for v in list { + if let Value::Num(x) = v { + let b = x as u8; + if x < 0.0 || x > 255.0 { + return Err(ChatError::with_span("ByteString element out of range (0-255)", 1, span)); + } + bytes.push(b); + } else { + return Err(ChatError::with_span("List must contain numbers", 1, span)); + } + } + Ok(Value::ByteString(bytes)) + } + _ => Err(ChatError::with_span(&format!("Cannot cast {} to ByteString", type_name(&val)), 1, span)), + } + } + "List" => { + match val { + Value::List(_) => Ok(val), + Value::String(s) => Ok(Value::List(s.chars().map(Value::Char).collect())), + Value::ByteString(b) => Ok(Value::List(b.iter().map(|&x| Value::Num(x as f64)).collect())), + Value::Map(map) => { + let list: Vec = map.into_iter().map(|(k, v)| Value::Tuple(vec![Value::String(k), v])).collect(); + Ok(Value::List(list)) + } + Value::Set(set) => { + let list: Vec = set.into_iter().map(Value::String).collect(); + Ok(Value::List(list)) + } + _ => Err(ChatError::with_span(&format!("Cannot cast {} to List", type_name(&val)), 1, span)), + } + } + "Map" => { + match val { + Value::Map(_) => Ok(val), + Value::List(list) => { + let mut map = BTreeMap::new(); + for item in list { + match item { + Value::Tuple(v) if v.len() == 2 => { + map.insert(v[0].display(), v[1].clone()); + } + _ => return Err(ChatError::with_span("List elements must be 2-element tuples", 1, span)), + } + } + Ok(Value::Map(map)) + } + _ => Err(ChatError::with_span(&format!("Cannot cast {} to Map", type_name(&val)), 1, span)), + } + } + "Set" => { + match val { + Value::Set(_) => Ok(val), + Value::List(list) => { + let set: BTreeSet<_> = list.into_iter().map(|v| v.display()).collect(); + Ok(Value::Set(set)) + } + _ => Err(ChatError::with_span(&format!("Cannot cast {} to Set", type_name(&val)), 1, span)), + } + } + _ => { + // For custom types, we could allow if the value is already that type, or maybe not. + // For simplicity, we only allow if the value is already of that type. + if type_name(&val) == target_type { + Ok(val) + } else { + Err(ChatError::with_span(&format!("Cannot cast {} to {}", type_name(&val), target_type), 1, span)) + } + } + } +} diff --git a/src/lexer.rs b/src/lexer.rs new file mode 100644 index 0000000..ca6bd1d --- /dev/null +++ b/src/lexer.rs @@ -0,0 +1,604 @@ +use crate::ast::Literal; +use std::time::Duration; + +#[derive(Debug, Clone, PartialEq)] +pub enum LexError { + UnexpectedChar(char, usize), + UnterminatedString(usize), + InvalidEscape(char, usize), + InvalidHexByteString(String, usize), + InvalidNumber(String, usize), + InvalidDuration(String, usize), + UnexpectedEof, +} + +impl std::fmt::Display for LexError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + LexError::UnexpectedChar(c, pos) => write!(f, "unexpected character '{}' at position {}", c, pos), + LexError::UnterminatedString(pos) => write!(f, "unterminated string literal at position {}", pos), + LexError::InvalidEscape(c, pos) => write!(f, "invalid escape sequence '\\{}' at position {}", c, pos), + LexError::InvalidHexByteString(s, pos) => write!(f, "invalid hex byte string '{}' at position {}", s, pos), + LexError::InvalidNumber(s, pos) => write!(f, "invalid number '{}' at position {}", s, pos), + LexError::InvalidDuration(s, pos) => write!(f, "invalid duration '{}' at position {}", s, pos), + LexError::UnexpectedEof => write!(f, "unexpected end of input"), + } + } +} + +impl std::error::Error for LexError {} + +#[derive(Debug, Clone, PartialEq)] +pub enum TokenKind { + INDENT, + DEDENT, + NEWLINE, + LParen, + RParen, + LBrace, + RBrace, + LBracket, + RBracket, + Comma, + Semicolon, + Pipe, + Arrow, + FatArrow, + Assign, + Colon, + DoubleColon, + Cons, + Concat, + Plus, + Minus, + Star, + Slash, + Percent, + Not, + Eq, + Neq, + Lt, + Le, + Gt, + Ge, + And, + Or, + Dollar, + Dot, + DotDot, + If, + Then, + Else, + Let, + In, + Case, + Of, + Data, + Struct, + Try, + Catch, + Error, + For, + While, + Lambda, + Class, + Extends, + New, + Loop, + Break, + Super, // new token for `super` + Ident(String), + Literal(Literal), + FString(String), + Eof, +} + +#[derive(Debug, Clone)] +pub struct Token { + pub kind: TokenKind, + pub start: usize, + pub end: usize, +} + +impl Token { + pub fn span(&self) -> (usize, usize) { + (self.start, self.end) + } +} + +pub struct Lexer { + chars: Vec, + pos: usize, + indent_stack: Vec, + at_line_start: bool, + line_start_spaces: usize, + pending_tokens: Vec, +} + +impl Lexer { + pub fn new(input: &str) -> Self { + Lexer { + chars: input.chars().collect(), + pos: 0, + indent_stack: vec![0], + at_line_start: true, + line_start_spaces: 0, + pending_tokens: Vec::new(), + } + } + + fn current(&self) -> Option { + self.chars.get(self.pos).copied() + } + + fn advance(&mut self) { + self.pos += 1; + } + + pub fn tokenize(&mut self) -> Result, LexError> { + let mut tokens = Vec::new(); + loop { + match self.next_token()? { + Some(Token { kind: TokenKind::Eof, start, end }) => { + while self.indent_stack.len() > 1 { + self.indent_stack.pop(); + tokens.push(Token { kind: TokenKind::DEDENT, start, end }); + } + tokens.push(Token { kind: TokenKind::Eof, start, end }); + break; + } + Some(tok) => tokens.push(tok), + None => break, + } + } + Ok(tokens) + } + + fn next_token(&mut self) -> Result, LexError> { + if let Some(tok) = self.pending_tokens.pop() { + return Ok(Some(tok)); + } + + self.skip_whitespace()?; + + let c = match self.current() { + Some(ch) => ch, + None => return Ok(None), + }; + + let start = self.pos; + + if c == '\n' { + self.advance(); + let end = self.pos; + self.at_line_start = true; + return Ok(Some(Token { kind: TokenKind::NEWLINE, start, end })); + } + + if self.at_line_start { + self.line_start_spaces = 0; + while let Some(' ') = self.current() { + self.line_start_spaces += 1; + self.advance(); + } + self.at_line_start = false; + let current_indent = *self.indent_stack.last().unwrap(); + let end = self.pos; + if self.line_start_spaces > current_indent { + self.indent_stack.push(self.line_start_spaces); + return Ok(Some(Token { kind: TokenKind::INDENT, start, end })); + } else if self.line_start_spaces < current_indent { + let mut dedents = Vec::new(); + while self.indent_stack.last().map(|&x| x > self.line_start_spaces).unwrap_or(false) { + self.indent_stack.pop(); + dedents.push(Token { kind: TokenKind::DEDENT, start, end }); + } + self.pending_tokens.extend(dedents.into_iter().rev()); + if let Some(tok) = self.pending_tokens.pop() { + return Ok(Some(tok)); + } + } + } + + self.read_token(start) + } + + fn read_token(&mut self, start: usize) -> Result, LexError> { + let c = self.current().unwrap(); + let end = self.pos + 1; + match c { + '(' => { self.advance(); Ok(Some(Token { kind: TokenKind::LParen, start, end })) } + ')' => { self.advance(); Ok(Some(Token { kind: TokenKind::RParen, start, end })) } + '{' => { self.advance(); Ok(Some(Token { kind: TokenKind::LBrace, start, end })) } + '}' => { self.advance(); Ok(Some(Token { kind: TokenKind::RBrace, start, end })) } + '[' => { self.advance(); Ok(Some(Token { kind: TokenKind::LBracket, start, end })) } + ']' => { self.advance(); Ok(Some(Token { kind: TokenKind::RBracket, start, end })) } + ',' => { self.advance(); Ok(Some(Token { kind: TokenKind::Comma, start, end })) } + ';' => { self.advance(); Ok(Some(Token { kind: TokenKind::Semicolon, start, end })) } + '|' => { + self.advance(); + if self.current() == Some('>') { + self.advance(); + Ok(Some(Token { kind: TokenKind::Pipe, start, end })) + } else { + Ok(Some(Token { kind: TokenKind::Pipe, start, end })) + } + } + '=' => { + self.advance(); + if self.current() == Some('>') { + self.advance(); + Ok(Some(Token { kind: TokenKind::FatArrow, start, end })) + } else if self.current() == Some('=') { + self.advance(); + Ok(Some(Token { kind: TokenKind::Eq, start, end })) + } else { + Ok(Some(Token { kind: TokenKind::Assign, start, end })) + } + } + ':' => { + self.advance(); + if self.current() == Some(':') { + self.advance(); + Ok(Some(Token { kind: TokenKind::DoubleColon, start, end })) + } else { + Ok(Some(Token { kind: TokenKind::Colon, start, end })) + } + } + '+' => { + self.advance(); + if self.current() == Some('+') { + self.advance(); + Ok(Some(Token { kind: TokenKind::Concat, start, end })) + } else { + Ok(Some(Token { kind: TokenKind::Plus, start, end })) + } + } + '-' => { + self.advance(); + if self.current() == Some('>') { + self.advance(); + Ok(Some(Token { kind: TokenKind::Arrow, start, end })) + } else { + Ok(Some(Token { kind: TokenKind::Minus, start, end })) + } + } + '*' => { self.advance(); Ok(Some(Token { kind: TokenKind::Star, start, end })) } + '/' => { + self.advance(); + if self.current() == Some('=') { + self.advance(); + Ok(Some(Token { kind: TokenKind::Neq, start, end })) + } else { + Ok(Some(Token { kind: TokenKind::Slash, start, end })) + } + } + '%' => { self.advance(); Ok(Some(Token { kind: TokenKind::Percent, start, end })) } + '!' => { + self.advance(); + if self.current() == Some('=') { + self.advance(); + Ok(Some(Token { kind: TokenKind::Neq, start, end })) + } else { + Ok(Some(Token { kind: TokenKind::Not, start, end })) + } + } + '<' => { + self.advance(); + if self.current() == Some('=') { + self.advance(); + Ok(Some(Token { kind: TokenKind::Le, start, end })) + } else { + Ok(Some(Token { kind: TokenKind::Lt, start, end })) + } + } + '>' => { + self.advance(); + if self.current() == Some('=') { + self.advance(); + Ok(Some(Token { kind: TokenKind::Ge, start, end })) + } else { + Ok(Some(Token { kind: TokenKind::Gt, start, end })) + } + } + '&' => { self.advance(); Ok(Some(Token { kind: TokenKind::And, start, end })) } + '$' => { self.advance(); Ok(Some(Token { kind: TokenKind::Dollar, start, end })) } + '.' => { + self.advance(); + if self.current() == Some('.') { + self.advance(); + Ok(Some(Token { kind: TokenKind::DotDot, start, end })) + } else { + Ok(Some(Token { kind: TokenKind::Dot, start, end })) + } + } + '"' => self.consume_string(start), + '\'' => self.consume_char(start), + '@' => self.consume_uid(start), + '#' => self.consume_byte_string(start), + c if c.is_ascii_digit() => self.consume_number_or_duration(start, c), + c if c.is_alphabetic() || c == '_' => { + let mut ident = String::new(); + ident.push(c); + self.advance(); + while let Some(ch) = self.current() { + if ch.is_alphanumeric() || ch == '_' { + ident.push(ch); + self.advance(); + } else { + break; + } + } + let end = self.pos; + if ident == "f" && self.current() == Some('"') { + self.advance(); + return self.consume_fstring(start); + } + match ident.as_str() { + "true" => Ok(Some(Token { kind: TokenKind::Literal(Literal::Bool(true)), start, end })), + "false" => Ok(Some(Token { kind: TokenKind::Literal(Literal::Bool(false)), start, end })), + "if" => Ok(Some(Token { kind: TokenKind::If, start, end })), + "then" => Ok(Some(Token { kind: TokenKind::Then, start, end })), + "else" => Ok(Some(Token { kind: TokenKind::Else, start, end })), + "let" => Ok(Some(Token { kind: TokenKind::Let, start, end })), + "in" => Ok(Some(Token { kind: TokenKind::In, start, end })), + "case" => Ok(Some(Token { kind: TokenKind::Case, start, end })), + "of" => Ok(Some(Token { kind: TokenKind::Of, start, end })), + "data" => Ok(Some(Token { kind: TokenKind::Data, start, end })), + "struct" => Ok(Some(Token { kind: TokenKind::Struct, start, end })), + "try" => Ok(Some(Token { kind: TokenKind::Try, start, end })), + "catch" => Ok(Some(Token { kind: TokenKind::Catch, start, end })), + "error" => Ok(Some(Token { kind: TokenKind::Error, start, end })), + "for" => Ok(Some(Token { kind: TokenKind::For, start, end })), + "while" => Ok(Some(Token { kind: TokenKind::While, start, end })), + "lambda" => Ok(Some(Token { kind: TokenKind::Lambda, start, end })), + "and" => Ok(Some(Token { kind: TokenKind::And, start, end })), + "or" => Ok(Some(Token { kind: TokenKind::Or, start, end })), + "not" => Ok(Some(Token { kind: TokenKind::Not, start, end })), + "class" => Ok(Some(Token { kind: TokenKind::Class, start, end })), + "extends" => Ok(Some(Token { kind: TokenKind::Extends, start, end })), + "new" => Ok(Some(Token { kind: TokenKind::New, start, end })), + "loop" => Ok(Some(Token { kind: TokenKind::Loop, start, end })), + "break" => Ok(Some(Token { kind: TokenKind::Break, start, end })), + "super" => Ok(Some(Token { kind: TokenKind::Super, start, end })), + _ => Ok(Some(Token { kind: TokenKind::Ident(ident), start, end })), + } + } + _ => Err(LexError::UnexpectedChar(c, self.pos)), + } + } + + fn consume_string(&mut self, start: usize) -> Result, LexError> { + self.advance(); + let mut s = String::new(); + while let Some(ch) = self.current() { + if ch == '"' { + self.advance(); + let end = self.pos; + return Ok(Some(Token { kind: TokenKind::Literal(Literal::String(s)), start, end })); + } + if ch == '\\' { + self.advance(); + if let Some(esc) = self.current() { + match esc { + 'n' => s.push('\n'), + 't' => s.push('\t'), + 'r' => s.push('\r'), + '\\' => s.push('\\'), + '"' => s.push('"'), + '\'' => s.push('\''), + _ => return Err(LexError::InvalidEscape(esc, self.pos)), + } + self.advance(); + } else { + return Err(LexError::UnexpectedEof); + } + } else { + s.push(ch); + self.advance(); + } + } + Err(LexError::UnterminatedString(start)) + } + + fn consume_fstring(&mut self, start: usize) -> Result, LexError> { + let mut content = String::new(); + while let Some(ch) = self.current() { + if ch == '"' { + if let Some(prev) = self.chars.get(self.pos - 1) { + if *prev == '\\' { + content.push('"'); + self.advance(); + continue; + } + } + self.advance(); + let end = self.pos; + return Ok(Some(Token { kind: TokenKind::FString(content), start, end })); + } + if ch == '\\' && self.current().and_then(|_| self.chars.get(self.pos + 1)).map(|&c| c == '"').unwrap_or(false) { + content.push('\\'); + self.advance(); + if let Some(ch2) = self.current() { + content.push(ch2); + self.advance(); + } + continue; + } + content.push(ch); + self.advance(); + } + Err(LexError::UnterminatedString(start)) + } + + fn consume_char(&mut self, start: usize) -> Result, LexError> { + self.advance(); + let ch = if self.current() == Some('\\') { + self.advance(); + if let Some(esc) = self.current() { + self.advance(); + match esc { + 'n' => '\n', + 't' => '\t', + 'r' => '\r', + '\\' => '\\', + '\'' => '\'', + _ => return Err(LexError::InvalidEscape(esc, self.pos)), + } + } else { + return Err(LexError::UnexpectedEof); + } + } else { + if let Some(c) = self.current() { + self.advance(); + c + } else { + return Err(LexError::UnexpectedEof); + } + }; + if self.current() == Some('\'') { + self.advance(); + let end = self.pos; + Ok(Some(Token { kind: TokenKind::Literal(Literal::Char(ch)), start, end })) + } else { + Err(LexError::UnterminatedString(start)) + } + } + + fn consume_uid(&mut self, start: usize) -> Result, LexError> { + self.advance(); + let mut uid = String::from("@"); + while let Some(ch) = self.current() { + if ch.is_alphanumeric() || ch == '_' { + uid.push(ch); + self.advance(); + } else { + break; + } + } + let end = self.pos; + Ok(Some(Token { kind: TokenKind::Literal(Literal::Uid(uid)), start, end })) + } + + fn consume_byte_string(&mut self, start: usize) -> Result, LexError> { + self.advance(); + if self.current() == Some('B') { + self.advance(); + if self.current() != Some('"') { + return Err(LexError::UnexpectedChar(self.current().unwrap_or('\0'), self.pos)); + } + self.advance(); + let mut hex = String::new(); + while let Some(ch) = self.current() { + if ch == '"' { + self.advance(); + let end = self.pos; + let bytes = hex::decode(&hex) + .map_err(|_| LexError::InvalidHexByteString(hex.clone(), self.pos))?; + return Ok(Some(Token { kind: TokenKind::Literal(Literal::ByteString(bytes)), start, end })); + } + hex.push(ch); + self.advance(); + } + Err(LexError::UnterminatedString(self.pos)) + } else { + Err(LexError::UnexpectedChar('#', self.pos)) + } + } + + fn consume_number_or_duration(&mut self, start: usize, first: char) -> Result, LexError> { + let mut num_str = String::new(); + num_str.push(first); + self.advance(); + let mut is_float = false; + loop { + match self.current() { + Some(ch) if ch.is_ascii_digit() => { + num_str.push(ch); + self.advance(); + } + Some('.') => { + if let Some(&next) = self.chars.get(self.pos + 1) { + if next == '.' { + break; + } + } + is_float = true; + num_str.push('.'); + self.advance(); + } + _ => break, + } + } + + let suffix = match self.current() { + Some('s') => { + self.advance(); + Some('s') + } + Some('m') => { + self.advance(); + if self.current() == Some('s') { + self.advance(); + Some('m') + } else { + Some('M') + } + } + Some('h') => { + self.advance(); + Some('h') + } + _ => None, + }; + let end = self.pos; + + if let Some(suf) = suffix { + let num: f64 = num_str.parse().map_err(|_| LexError::InvalidNumber(num_str.clone(), start))?; + let (secs, nanos) = match suf { + 's' => { + let secs = num as u64; + let nanos = ((num - secs as f64) * 1_000_000_000.0) as u32; + (secs, nanos) + } + 'm' => { + let millis = (num * 1000.0) as u64; + let secs = millis / 1000; + let nanos = ((millis % 1000) * 1_000_000) as u32; + (secs, nanos) + } + 'M' => { + let secs = (num * 60.0) as u64; + let nanos = ((num * 60.0 - secs as f64) * 1_000_000_000.0) as u32; + (secs, nanos) + } + 'h' => { + let secs = (num * 3600.0) as u64; + let nanos = ((num * 3600.0 - secs as f64) * 1_000_000_000.0) as u32; + (secs, nanos) + } + _ => return Err(LexError::InvalidDuration(num_str, start)), + }; + let dur = Duration::new(secs, nanos); + Ok(Some(Token { kind: TokenKind::Literal(Literal::Duration(dur)), start, end })) + } else { + if is_float { + let f: f64 = num_str.parse().map_err(|_| LexError::InvalidNumber(num_str.clone(), start))?; + Ok(Some(Token { kind: TokenKind::Literal(Literal::Float(f)), start, end })) + } else { + let i: i64 = num_str.parse().map_err(|_| LexError::InvalidNumber(num_str.clone(), start))?; + Ok(Some(Token { kind: TokenKind::Literal(Literal::Int(i)), start, end })) + } + } + } + + fn skip_whitespace(&mut self) -> Result<(), LexError> { + while let Some(ch) = self.current() { + match ch { + ' ' | '\t' | '\r' => self.advance(), + _ => break, + } + } + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3c84f2f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,10 @@ +pub mod ast; +pub mod error; +pub mod lexer; +pub mod parser; +pub mod types; +pub mod eval; +pub mod builtins; +pub mod chat; +pub mod p2p; +pub mod server; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1debcd6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,558 @@ +use plic::chat::ChatState; +use plic::eval::eval_expr; +use plic::types::Environment; +use plic::parser::{parse_expression, parse_script}; +use rustyline::Editor; +use rustyline::error::ReadlineError; +use rustyline::completion::Completer; +use rustyline::highlight::Highlighter; +use rustyline::hint::Hinter; +use rustyline::validate::{Validator, ValidationContext, ValidationResult}; +use rustyline::Helper; +use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}}; +use std::fs; +use std::env; +use std::collections::BTreeSet; +use std::borrow::Cow; +use codespan::Files; +use codespan_reporting::diagnostic::{Diagnostic, Label}; +use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; +use codespan_reporting::term as term_reporting; + +static INTERRUPTED: AtomicBool = AtomicBool::new(false); + +struct PlicHelper { + env: Arc>, + keywords: Vec, + builtins: Vec, + types: Vec, +} + +impl Completer for PlicHelper { + type Candidate = String; + + fn complete(&self, line: &str, pos: usize, _ctx: &rustyline::Context<'_>) -> rustyline::Result<(usize, Vec)> { + let mut candidates = BTreeSet::new(); + for kw in &self.keywords { + candidates.insert(kw.clone()); + } + for b in &self.builtins { + candidates.insert(b.clone()); + } + for t in &self.types { + candidates.insert(t.clone()); + } + let env_guard = self.env.lock().unwrap(); + for (name, _) in env_guard.vars.iter() { + candidates.insert(name.clone()); + } + drop(env_guard); + + let prefix = &line[..pos]; + let last_word = prefix.split_whitespace().last().unwrap_or(""); + let matches: Vec = candidates.into_iter() + .filter(|s| s.starts_with(last_word) && s != last_word) + .collect(); + Ok((pos - last_word.len(), matches)) + } +} + +impl Highlighter for PlicHelper { + fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { + let mut result = String::new(); + let mut chars = line.chars().peekable(); + let mut in_string = false; + let mut in_fstring = false; + let mut fstring_brace_depth = 0; + let mut escape = false; + let mut multiline_comment_depth = 0; + let mut in_comment = false; + + let keywords = [ + "let", "if", "then", "else", "case", "of", "lambda", + "data", "struct", "try", "catch", "error", "for", "while", + "true", "false", "in", "and", "or", "not", "class", "extends", "new", + "loop", "break", "load", "super" + ]; + let types = [ + "Num", "Char", "String", "Bool", "Unit", "Uid", + "ByteString", "List", "Tuple", "Record", "Pid", "DateTime", + "Duration", "Json", "Maybe", "Either", "ChatMsg", "FileInfo", + "FileTransfer", "FetchOptions", "FetchResult", "Map", "Set", + "ClassInstance" + ]; + + while let Some(ch) = chars.next() { + if ch == '#' && chars.peek() == Some(&'-') { + chars.next(); + multiline_comment_depth += 1; + if in_comment { + result.push_str("\x1b[3;90m#-"); + } else { + in_comment = true; + result.push_str("\x1b[3;90m#-"); + } + continue; + } + if ch == '-' && chars.peek() == Some(&'#') { + chars.next(); + if multiline_comment_depth > 0 { + multiline_comment_depth -= 1; + result.push_str("-#\x1b[0m"); + if multiline_comment_depth == 0 { + in_comment = false; + } else { + result.push_str("\x1b[3;90m"); + } + } else { + result.push_str("-#"); + } + continue; + } + if in_comment { + result.push(ch); + continue; + } + + if ch == '#' && chars.peek() != Some(&'B') && chars.peek() != Some(&'-') { + result.push_str("\x1b[3;90m#"); + while let Some(c) = chars.next() { + result.push(c); + } + result.push_str("\x1b[0m"); + break; + } + + if ch == 'f' && chars.peek() == Some(&'"') { + in_fstring = true; + result.push_str("\x1b[32m"); + result.push(ch); + continue; + } + + if ch == '"' && !in_fstring { + if !in_string { + in_string = true; + result.push_str("\x1b[32m"); + result.push(ch); + } else { + if escape { + result.push(ch); + } else { + result.push_str("\"\x1b[0m"); + in_string = false; + } + escape = false; + } + continue; + } + + if in_string { + if ch == '\\' && !escape { + escape = true; + result.push(ch); + } else if ch == '"' && escape { + escape = false; + result.push(ch); + } else { + if escape { escape = false; } + result.push(ch); + } + continue; + } + + if in_fstring { + if ch == '"' && fstring_brace_depth == 0 { + result.push_str("\"\x1b[0m"); + in_fstring = false; + continue; + } + if ch == '{' { + if let Some(&next) = chars.peek() { + if next == '{' { + chars.next(); + result.push_str("{{"); + continue; + } + } + fstring_brace_depth += 1; + result.push_str("\x1b[0m"); + result.push(ch); + let mut expr_chars = String::new(); + let mut depth = 1; + while let Some(c) = chars.next() { + if c == '{' { + depth += 1; + expr_chars.push(c); + } else if c == '}' { + depth -= 1; + if depth == 0 { + break; + } else { + expr_chars.push(c); + } + } else { + expr_chars.push(c); + } + } + result.push_str(&expr_chars); + result.push_str("\x1b[32m"); + result.push('}'); + continue; + } + if ch == '}' { + if let Some(&next) = chars.peek() { + if next == '}' { + chars.next(); + result.push_str("}}"); + continue; + } + } + fstring_brace_depth -= 1; + result.push_str("\x1b[32m"); + result.push(ch); + continue; + } + result.push_str("\x1b[32m"); + result.push(ch); + continue; + } + + if ch.is_alphabetic() || ch == '_' { + let mut word = String::new(); + word.push(ch); + while let Some(&next) = chars.peek() { + if next.is_alphanumeric() || next == '_' { + word.push(next); + chars.next(); + } else { + break; + } + } + if keywords.contains(&word.as_str()) { + result.push_str(&format!("\x1b[34m{}\x1b[0m", word)); + } else if types.contains(&word.as_str()) { + result.push_str(&format!("\x1b[35m{}\x1b[0m", word)); + } else { + result.push_str(&format!("\x1b[37m{}\x1b[0m", word)); + } + } else if ch.is_digit(10) { + result.push_str(&format!("\x1b[33m{}\x1b[0m", ch)); + while let Some(&next) = chars.peek() { + if next.is_digit(10) || next == '.' { + result.push_str(&format!("\x1b[33m{}\x1b[0m", next)); + chars.next(); + } else { + break; + } + } + } else { + result.push(ch); + } + } + if in_string || in_fstring { + result.push_str("\x1b[0m"); + } + Cow::Owned(result) + } + + fn highlight_char(&self, _line: &str, _pos: usize) -> bool { + true + } +} + +impl Hinter for PlicHelper { + type Hint = String; + fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option { None } +} + +impl Validator for PlicHelper { + fn validate(&self, _ctx: &mut ValidationContext<'_>) -> rustyline::Result { + Ok(ValidationResult::Valid(None)) + } +} + +impl Helper for PlicHelper {} + +fn main() { + std::panic::set_hook(Box::new(|panic_info| { + eprintln!("\x1b[31mFatal error\x1b[0m: {}", panic_info); + std::process::exit(1); + })); + + let args: Vec = env::args().collect(); + let state = Arc::new(Mutex::new(ChatState::new())); + let mut env = Environment::new(); + let env_arc = Arc::new(Mutex::new(env.clone())); + + plic::builtins::populate(&mut env, Arc::clone(&state), Arc::clone(&env_arc)); + + { + let mut guard = env_arc.lock().unwrap(); + *guard = env; + } + + let mut p2p_port = 19000; + for i in 0..args.len() { + if args[i] == "--p2p-port" && i + 1 < args.len() { + if let Ok(port) = args[i+1].parse() { + p2p_port = port; + } + } + } + { + let mut state_guard = state.lock().unwrap(); + state_guard.p2p_port = p2p_port; + } + + ctrlc::set_handler(|| { + INTERRUPTED.store(true, Ordering::SeqCst); + plic::eval::set_interrupted(); + }).expect("Error setting Ctrl-C handler"); + + if args.len() > 1 && !args[1].starts_with("--") { + let filename = &args[1]; + match fs::read_to_string(filename) { + Ok(content) => { + match parse_script(&content) { + Ok(expr) => { + let mut env_guard = env_arc.lock().unwrap(); + if let Err(err) = eval_expr(&expr, &mut env_guard, Arc::clone(&state)) { + let mut files = Files::new(); + let file_id = files.add(filename, content.clone()); + let diagnostic = Diagnostic::error() + .with_message(err.message) + .with_labels(vec![ + Label::primary(file_id, err.span.unwrap_or(plic::ast::Span::dummy()).start..err.span.unwrap_or(plic::ast::Span::dummy()).end) + ]); + let writer = StandardStream::stderr(ColorChoice::Always); + let config = term_reporting::Config::default(); + let _ = term_reporting::emit(&mut writer.lock(), &config, &files, &diagnostic); + } + } + Err(e) => eprintln!("\x1b[31merror\x1b[0m: Parse error: {}", e), + } + } + Err(e) => eprintln!("\x1b[31merror\x1b[0m: Failed to read file {}: {}", filename, e), + } + return; + } + + let helper = PlicHelper { + env: Arc::clone(&env_arc), + keywords: vec![ + "let".into(), "if".into(), "then".into(), "else".into(), + "case".into(), "of".into(), "lambda".into(), + "data".into(), "struct".into(), "try".into(), "catch".into(), + "error".into(), "for".into(), "while".into(), "in".into(), + "and".into(), "or".into(), "not".into(), + "class".into(), "extends".into(), "new".into(), + "loop".into(), "break".into(), "load".into(), "super".into(), + ], + builtins: vec![ + "sqrt".into(), "sin".into(), "cos".into(), "tan".into(), + "asin".into(), "acos".into(), "atan".into(), + "show".into(), "parseInt".into(), "parseFloat".into(), + "chr".into(), "ord".into(), + "null".into(), "length".into(), "map".into(), "filter".into(), + "foldl".into(), "foldr".into(), "take".into(), "drop".into(), + "reverse".into(), "all".into(), "any".into(), "find".into(), + "sort".into(), "sortBy".into(), "sum".into(), + "concat".into(), "flatten".into(), "zip".into(), "zipWith".into(), + "unzip".into(), "indexOf".into(), "lastIndexOf".into(), + "split".into(), "join".into(), "startsWith".into(), "endsWith".into(), + "trim".into(), "replace".into(), "substring".into(), + "parseJson".into(), "encodeJson".into(), "lookup".into(), + "formatTime".into(), "parseTime".into(), "addDuration".into(), + "diffDuration".into(), + "packBytes".into(), "unpackBytes".into(), + "putStrLn".into(), "getLine".into(), "getArgs".into(), + "readFile".into(), "readBinaryFile".into(), "writeFile".into(), + "appendFile".into(), "writeBinaryFile".into(), "fileExists".into(), + "fileSize".into(), + "fetch".into(), "fetchOpts".into(), + "login".into(), "newChat".into(), "addMember".into(), "removeMember".into(), + "open".into(), "send".into(), "sendFile".into(), + "sendChat".into(), "sendFileToChat".into(), + "inbox".into(), "history".into(), "downloads".into(), "saveFile".into(), + "serverStart".into(), "serverStop".into(), + "connect".into(), + "now".into(), + "spawn".into(), "procSelf".into(), "procSend".into(), + "procRecv".into(), "procWait".into(), "procExit".into(), + "sleep".into(), "after".into(), + "Nothing".into(), "Just".into(), "maybe".into(), + "mapGet".into(), "mapSet".into(), "mapRemove".into(), + "mapKeys".into(), "mapValues".into(), "mapEntries".into(), + "mapContains".into(), "mapSize".into(), "mapFilter".into(), "mapMerge".into(), + "setAdd".into(), "setRemove".into(), + "setContains".into(), "setUnion".into(), "setIntersection".into(), + "setDifference".into(), "setSize".into(), "setFilter".into(), "setMap".into(), + "listToSet".into(), "mapToList".into(), + "sha256".into(), "sha256String".into(), + "kyberKeyPair".into(), "kyberEncapsulate".into(), "kyberDecapsulate".into(), + "listDir".into(), "createDir".into(), "removeDir".into(), + "fileMove".into(), "filePermissions".into(), "setFilePermissions".into(), + "typeof".into(), "getPublicIP".into(), "setExternalIP".into(), + "exit".into(), "load".into(), + "logout".into(), "deleteUser".into(), "deleteChat".into(), + "listChats".into(), "members".into(), + "addContact".into(), "removeContact".into(), + "p2pPort".into(), + ], + types: vec![ + "Num".into(), "Char".into(), "String".into(), + "Bool".into(), "Unit".into(), "Uid".into(), + "ByteString".into(), "List".into(), "Tuple".into(), + "Record".into(), "Pid".into(), "DateTime".into(), + "Duration".into(), "Json".into(), "Maybe".into(), + "Either".into(), "ChatMsg".into(), "FileInfo".into(), + "FileTransfer".into(), "FetchOptions".into(), "FetchResult".into(), + "Map".into(), "Set".into(), "ClassInstance".into(), + ], + }; + + let mut rl = Editor::new().unwrap(); + rl.set_helper(Some(helper)); + let _ = rl.load_history(".plic_history"); + + let mut buffer = String::new(); + let mut in_multiline = false; + let mut first_indent = 0; + + loop { + let prompt = if buffer.is_empty() { + ">>> " + } else { + "... " + }; + let line = rl.readline(prompt); + match line { + Ok(line) => { + rl.add_history_entry(line.as_str()); + let trimmed = line.trim(); + let indent = line.chars().take_while(|c| *c == ' ').count(); + + if !in_multiline { + if trimmed.is_empty() { + continue; + } + + if trimmed == "{" || trimmed == "}" { + eprintln!("\x1b[31merror\x1b[0m: Unexpected token '{}'", trimmed); + continue; + } + + // If line ends with a single colon (not ::), enter multiline mode with colon removed + if trimmed.ends_with(':') && !line.contains("::") { + let mut modified = line.trim_end().to_string(); + while modified.ends_with(':') || modified.ends_with(' ') { + modified.pop(); + } + buffer = modified; + in_multiline = true; + first_indent = indent; + continue; + } + + match parse_expression(&line) { + Ok(expr) => { + let mut env_guard = env_arc.lock().unwrap(); + match eval_expr(&expr, &mut env_guard, Arc::clone(&state)) { + Ok(_) => {}, + Err(err) => eprintln!("\x1b[31merror\x1b[0m: {}", err), + } + continue; + } + Err(_) => { + buffer = line; + in_multiline = true; + first_indent = indent; + continue; + } + } + } else { + if trimmed.is_empty() { + if !buffer.is_empty() { + let full_block = buffer.clone(); + match parse_script(&full_block) { + Ok(expr) => { + let mut env_guard = env_arc.lock().unwrap(); + match eval_expr(&expr, &mut env_guard, Arc::clone(&state)) { + Ok(_) => {}, + Err(err) => eprintln!("\x1b[31merror\x1b[0m: {}", err), + } + } + Err(e) => eprintln!("\x1b[31merror\x1b[0m: Parse error: {}", e), + } + buffer.clear(); + in_multiline = false; + first_indent = 0; + } + continue; + } + + if indent > first_indent { + buffer.push('\n'); + buffer.push_str(&line); + continue; + } else { + let full_block = buffer.clone(); + let block_ok = match parse_script(&full_block) { + Ok(expr) => { + let mut env_guard = env_arc.lock().unwrap(); + match eval_expr(&expr, &mut env_guard, Arc::clone(&state)) { + Ok(_) => true, + Err(err) => { + eprintln!("\x1b[31merror\x1b[0m: {}", err); + false + } + } + } + Err(e) => { + eprintln!("\x1b[31merror\x1b[0m: Parse error: {}", e); + false + } + }; + buffer.clear(); + in_multiline = false; + first_indent = 0; + + if !block_ok { + continue; + } + + if !trimmed.is_empty() { + match parse_expression(&line) { + Ok(expr) => { + let mut env_guard = env_arc.lock().unwrap(); + match eval_expr(&expr, &mut env_guard, Arc::clone(&state)) { + Ok(_) => {}, + Err(err) => eprintln!("\x1b[31merror\x1b[0m: {}", err), + } + } + Err(_) => { + buffer = line; + in_multiline = true; + first_indent = indent; + } + } + } + continue; + } + } + } + Err(ReadlineError::Interrupted) => { + if in_multiline { + println!("Aborted multi-line block."); + buffer.clear(); + in_multiline = false; + first_indent = 0; + } + continue; + } + Err(_) => break, + } + } + let _ = rl.save_history(".plic_history"); +} diff --git a/src/p2p.rs b/src/p2p.rs new file mode 100644 index 0000000..6788614 --- /dev/null +++ b/src/p2p.rs @@ -0,0 +1,157 @@ +//! P2P messaging with control messages. + +use crate::chat::{ChatState, P2pControl}; +use crate::types::Value; +use serde::{Deserialize, Serialize}; +use std::io::{BufRead, BufReader, Write}; +use std::net::TcpStream; +use std::sync::{Arc, Mutex}; +use std::thread; +use native_tls::{TlsConnector, TlsAcceptor, Identity}; +use std::fs; +use std::path::Path; +use once_cell::sync::Lazy; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct P2pMessage { + pub msg_type: String, + pub from: String, + pub to: String, + pub text: Option, + pub filename: Option, + pub data_base64: Option, + pub chat: Option, + pub timestamp: Option, + pub control: Option, +} + +static TLS_CONNECTOR: Lazy = Lazy::new(|| { + let identity = load_or_generate_identity().expect("Failed to load or generate TLS identity"); + TlsConnector::builder() + .identity(identity) + .danger_accept_invalid_certs(true) + .build() + .expect("Failed to build TLS connector") +}); + +static TLS_ACCEPTOR: Lazy = Lazy::new(|| { + let identity = load_or_generate_identity().expect("Failed to load or generate TLS identity"); + TlsAcceptor::new(identity).expect("Failed to build TLS acceptor") +}); + +fn load_or_generate_identity() -> Result> { + let cert_path = ".plic_cert.pem"; + let key_path = ".plic_key.pem"; + if !Path::new(cert_path).exists() || !Path::new(key_path).exists() { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()])?; + let cert_pem = cert.serialize_pem()?; + let key_pem = cert.serialize_private_key_pem(); + fs::write(cert_path, &cert_pem)?; + fs::write(key_path, &key_pem)?; + } + let cert = fs::read(cert_path)?; + let key = fs::read(key_path)?; + Identity::from_pkcs8(&cert, &key).map_err(|e| e.into()) +} + +pub fn get_tls_connector() -> TlsConnector { + TLS_CONNECTOR.clone() +} + +pub fn get_tls_acceptor() -> TlsAcceptor { + TLS_ACCEPTOR.clone() +} + +pub fn start_p2p_listener(port: u16, state: Arc>) { + let listener = match std::net::TcpListener::bind(format!("0.0.0.0:{}", port)) { + Ok(l) => l, + Err(e) => { + eprintln!("Failed to bind P2P port {}: {}", port, e); + return; + } + }; + let acceptor = get_tls_acceptor(); + thread::spawn(move || { + for stream in listener.incoming() { + if let Ok(stream) = stream { + let acceptor = acceptor.clone(); + let state = Arc::clone(&state); + thread::spawn(move || { + if let Ok(tls_stream) = acceptor.accept(stream) { + handle_p2p_connection(tls_stream, state); + } + }); + } + } + }); +} + +fn handle_p2p_connection(stream: native_tls::TlsStream, state: Arc>) { + let reader = BufReader::new(stream); + for line in reader.lines() { + if let Ok(line) = line { + if let Ok(msg) = serde_json::from_str::(&line) { + let mut state = state.lock().unwrap(); + match msg.msg_type.as_str() { + "msg" => { + let message = crate::chat::Message { + from: msg.from, + text: msg.text.unwrap_or_default(), + chat: msg.chat.clone().unwrap_or_default(), + timestamp: msg.timestamp.unwrap_or(0), + }; + if let Some(chat) = msg.chat { + state.messages.entry(chat).or_default().push(message.clone()); + } + if !msg.to.is_empty() { + state.deliver_to_user(msg.to, message); + } + } + "file" => { + use base64::Engine; + let data = base64::engine::general_purpose::STANDARD + .decode(msg.data_base64.as_deref().unwrap_or("")) + .unwrap_or_default(); + state.downloads.push(Value::FileTransfer { + from: msg.from, + filename: msg.filename.unwrap_or_default(), + data, + }); + } + "control" => { + if let Some(control) = msg.control { + if let Err(e) = state.handle_control(&control) { + eprintln!("Control error: {}", e); + } + } + } + _ => {} + } + } + } + } +} + +pub fn send_message_to(addr: &str, msg: P2pMessage) -> Result<(), Box> { + let connector = get_tls_connector(); + let stream = TcpStream::connect(addr)?; + let mut tls_stream = connector.connect("localhost", stream)?; + let json = serde_json::to_string(&msg)?; + writeln!(tls_stream, "{}", json)?; + Ok(()) +} + +pub fn send_control(addr: &str, control: P2pControl, from: &str) -> Result<(), Box> { + let msg = P2pMessage { + msg_type: "control".to_string(), + from: from.to_string(), + to: String::new(), + text: None, + filename: None, + data_base64: None, + chat: None, + timestamp: None, + control: Some(control), + }; + send_message_to(addr, msg) +} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..ecfc38b --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,1257 @@ +use crate::ast::*; +use crate::lexer::{LexError, Lexer, Token, TokenKind}; + +#[derive(Debug, Clone, PartialEq)] +pub struct ParseError { + pub message: String, + pub span: Span, +} + +impl std::fmt::Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.message) + } +} + +impl std::error::Error for ParseError {} + +impl From for ParseError { + fn from(e: LexError) -> Self { + ParseError { message: e.to_string(), span: Span::dummy() } + } +} + +pub type ParseResult = Result; + +pub fn strip_comments(input: &str) -> String { + let mut output = String::new(); + let mut chars = input.chars().peekable(); + while let Some(ch) = chars.next() { + if ch == '#' { + if chars.peek() == Some(&'-') { + chars.next(); + let mut depth = 1; + while depth > 0 { + match chars.next() { + Some('#') => { + if chars.peek() == Some(&'-') { + chars.next(); + depth += 1; + } + } + Some('-') => { + if chars.peek() == Some(&'#') { + chars.next(); + depth -= 1; + } + } + Some(_) => {} + None => break, + } + } + continue; + } else { + while let Some(c) = chars.next() { + if c == '\n' { + output.push(c); + break; + } + } + continue; + } + } + output.push(ch); + } + output +} + +pub fn parse_expression(input: &str) -> ParseResult { + let stripped = strip_comments(input); + let mut lexer = Lexer::new(&stripped); + let tokens = lexer.tokenize()?; + let mut pos = 0; + skip_newlines(&tokens, &mut pos); + let expr = parse_expr(&tokens, &mut pos)?; + while pos < tokens.len() && (matches!(tokens[pos].kind, TokenKind::NEWLINE) || matches!(tokens[pos].kind, TokenKind::DEDENT)) { + pos += 1; + } + if pos < tokens.len() && tokens[pos].kind != TokenKind::Eof { + Err(ParseError { message: format!("Unexpected token: {:?}", tokens[pos].kind), span: Span::new(tokens[pos].start, tokens[pos].end) }) + } else { + Ok(expr) + } +} + +pub fn parse_script(input: &str) -> ParseResult { + let stripped = strip_comments(input); + let mut lexer = Lexer::new(&stripped); + let tokens = lexer.tokenize()?; + let mut pos = 0; + let mut exprs = Vec::new(); + skip_newlines(&tokens, &mut pos); + while pos < tokens.len() && tokens[pos].kind != TokenKind::Eof { + let e = parse_expr(&tokens, &mut pos)?; + exprs.push(e); + while pos < tokens.len() && (matches!(tokens[pos].kind, TokenKind::NEWLINE) || matches!(tokens[pos].kind, TokenKind::Semicolon)) { + pos += 1; + } + } + if exprs.is_empty() { + Ok(Expr::Lit(Literal::Unit, Span::dummy())) + } else if exprs.len() == 1 { + Ok(exprs.remove(0)) + } else { + let start = exprs.first().map(|e| e_span(e).start).unwrap_or(0); + let end = exprs.last().map(|e| e_span(e).end).unwrap_or(0); + Ok(Expr::Block(exprs, Span::new(start, end))) + } +} + +fn e_span(e: &Expr) -> Span { + match e { + Expr::Lit(_, s) => *s, + Expr::Var(_, s) => *s, + Expr::Lambda(_, _, s) => *s, + Expr::App(_, _, s) => *s, + Expr::If(_, _, _, s) => *s, + Expr::Let { span, .. } => *span, + Expr::Assign(_, _, s) => *s, + Expr::Case(_, _, s) => *s, + Expr::Try(_, s) => *s, + Expr::Catch(_, _, _, s) => *s, + Expr::Throw(_, s) => *s, + Expr::DataDef(_, _, _, s) => *s, + Expr::StructDef(_, _, s) => *s, + Expr::StructNew(_, _, s) => *s, + Expr::Constructor(_, _, s) => *s, + Expr::Record(_, s) => *s, + Expr::FieldAccess(_, _, s) => *s, + Expr::RecordUpdate(_, _, s) => *s, + Expr::List(_, s) => *s, + Expr::Range(_, _, s) => *s, + Expr::BinOp(_, _, _, s) => *s, + Expr::Concat(_, _, s) => *s, + Expr::Pipe(_, _, s) => *s, + Expr::Dollar(_, _, s) => *s, + Expr::LogicalAnd(_, _, s) => *s, + Expr::LogicalOr(_, _, s) => *s, + Expr::Not(_, s) => *s, + Expr::Tuple(_, s) => *s, + Expr::Index(_, _, s) => *s, + Expr::For(_, _, _, s) => *s, + Expr::While(_, _, s) => *s, + Expr::Loop(_, s) => *s, + Expr::Break(_, s) => *s, + Expr::Block(_, s) => *s, + Expr::ClassDef { span, .. } => *span, + Expr::New(_, _, s) => *s, + Expr::MethodCall(_, _, _, s) => *s, + Expr::MapLiteral(_, s) => *s, + Expr::SetLiteral(_, s) => *s, + Expr::FString(_, s) => *s, + Expr::ListComp { span, .. } => *span, + Expr::Cast(_, _, s) => *s, + Expr::SuperMethod { span, .. } => *span, + } +} + +fn parse_expr(tokens: &[Token], pos: &mut usize) -> ParseResult { + parse_dollar(tokens, pos) +} + +fn parse_dollar(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_assign(tokens, pos)?; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Dollar { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_assign(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::Dollar(Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_assign(tokens: &[Token], pos: &mut usize) -> ParseResult { + let left = parse_pipe(tokens, pos)?; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Assign { + if let Expr::Var(name, _span) = left { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_assign(tokens, pos)?; + let end = e_span(&right).end; + Ok(Expr::Assign(name, Box::new(right), Span::new(start, end))) + } else { + Err(ParseError { message: "only variables can be assigned to".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }) + } + } else { + Ok(left) + } +} + +fn parse_pipe(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_logical_or(tokens, pos)?; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Pipe { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_logical_or(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::Pipe(Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_logical_or(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_logical_and(tokens, pos)?; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Or { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_logical_and(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::LogicalOr(Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_logical_and(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_comparison(tokens, pos)?; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::And { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_comparison(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::LogicalAnd(Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_comparison(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_concat(tokens, pos)?; + while *pos < tokens.len() { + let op = match tokens[*pos].kind { + TokenKind::Eq => BinOp::Eq, + TokenKind::Neq => BinOp::Neq, + TokenKind::Lt => BinOp::Lt, + TokenKind::Le => BinOp::Le, + TokenKind::Gt => BinOp::Gt, + TokenKind::Ge => BinOp::Ge, + TokenKind::In => { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_concat(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::BinOp(BinOp::In, Box::new(left), Box::new(right), Span::new(start, end)); + continue; + } + _ => break, + }; + let start = tokens[*pos].start; + *pos += 1; + let right = parse_concat(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::BinOp(op, Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_concat(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_cons(tokens, pos)?; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Concat { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_cons(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::Concat(Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_cons(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_addsub(tokens, pos)?; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Cons { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_cons(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::BinOp(BinOp::Cons, Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_addsub(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_muldiv(tokens, pos)?; + while *pos < tokens.len() { + let op = match tokens[*pos].kind { + TokenKind::Plus => BinOp::Add, + TokenKind::Minus => BinOp::Sub, + _ => break, + }; + let start = tokens[*pos].start; + *pos += 1; + let right = parse_muldiv(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::BinOp(op, Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_muldiv(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_unary(tokens, pos)?; + while *pos < tokens.len() { + let op = match tokens[*pos].kind { + TokenKind::Star => BinOp::Mul, + TokenKind::Slash => BinOp::Div, + TokenKind::Percent => BinOp::Mod, + _ => break, + }; + let start = tokens[*pos].start; + *pos += 1; + let right = parse_unary(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::BinOp(op, Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_unary(tokens: &[Token], pos: &mut usize) -> ParseResult { + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Not { + let start = tokens[*pos].start; + *pos += 1; + let expr = parse_atom(tokens, pos)?; + let end = e_span(&expr).end; + Ok(Expr::Not(Box::new(expr), Span::new(start, end))) + } else if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Minus { + let start = tokens[*pos].start; + *pos += 1; + let expr = parse_atom(tokens, pos)?; + let end = e_span(&expr).end; + Ok(Expr::BinOp(BinOp::Sub, Box::new(Expr::Lit(Literal::Int(0), Span::new(start, start))), Box::new(expr), Span::new(start, end))) + } else { + parse_atom(tokens, pos) + } +} + +fn skip_newlines(tokens: &[Token], pos: &mut usize) { + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::NEWLINE { + *pos += 1; + } +} + +fn parse_atom(tokens: &[Token], pos: &mut usize) -> ParseResult { + if *pos >= tokens.len() { + return Err(ParseError { message: "incomplete input".to_string(), span: Span::dummy() }); + } + let start = tokens[*pos].start; + match &tokens[*pos].kind { + TokenKind::Literal(lit) => { + *pos += 1; + let end = tokens[*pos - 1].end; + let expr = Expr::Lit(lit.clone(), Span::new(start, end)); + parse_postfix(expr, tokens, pos) + } + TokenKind::FString(content) => { + *pos += 1; + let end = tokens[*pos - 1].end; + let parts = parse_fstring(content)?; + Ok(Expr::FString(parts, Span::new(start, end))) + } + TokenKind::Ident(name) => { + *pos += 1; + let end = tokens[*pos - 1].end; + let mut expr = Expr::Var(name.clone(), Span::new(start, end)); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::LParen { + let temp_pos = *pos + 1; + if is_struct_constructor(tokens, temp_pos) { + *pos += 1; + let mut fields = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + let field_name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected field name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + let _field_start = tokens[*pos].start; + *pos += 1; + expect_kind(tokens, pos, TokenKind::Assign)?; + let value = parse_expr(tokens, pos)?; + let _value_end = e_span(&value).end; + fields.push((field_name, value)); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + expr = Expr::StructNew(name.clone(), fields, Span::new(start, _end)); + } else { + *pos += 1; + let mut args = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + let arg = parse_expr(tokens, pos)?; + args.push(arg); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + for arg in args { + let end = e_span(&arg).end; + expr = Expr::App(Box::new(expr), Box::new(arg), Span::new(start, end)); + } + } + } else { + expr = parse_application(expr, tokens, pos)?; + } + parse_postfix(expr, tokens, pos) + } + TokenKind::Super => { + *pos += 1; + let _end = tokens[*pos - 1].end; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Dot { + *pos += 1; + if let TokenKind::Ident(method) = &tokens[*pos].kind { + let method = method.clone(); + *pos += 1; + expect_kind(tokens, pos, TokenKind::LParen)?; + let mut args = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + args.push(parse_expr(tokens, pos)?); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let end2 = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Expr::SuperMethod { method, args, span: Span::new(start, end2) }) + } else { + Err(ParseError { message: "expected method name after super.".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }) + } + } else { + Err(ParseError { message: "expected .method after super".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }) + } + } + TokenKind::If => { + *pos += 1; + let cond = parse_expr(tokens, pos)?; + skip_newlines(tokens, pos); + expect_kind(tokens, pos, TokenKind::Then)?; + let then_expr = parse_expr_or_block(tokens, pos)?; + skip_newlines(tokens, pos); + expect_kind(tokens, pos, TokenKind::Else)?; + let else_expr = parse_expr_or_block(tokens, pos)?; + let end = e_span(&else_expr).end; + Ok(Expr::If(Box::new(cond), Box::new(then_expr), Box::new(else_expr), Span::new(start, end))) + } + TokenKind::Let => { + *pos += 1; + let name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected identifier".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + let _name_start = tokens[*pos].start; + *pos += 1; + + let mut type_ann = None; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::DoubleColon { + *pos += 1; + let type_str = parse_type(tokens, pos)?; + type_ann = Some(type_str); + } + + let mut params = Vec::new(); + while *pos < tokens.len() && matches!(tokens[*pos].kind, TokenKind::Ident(_)) { + if let TokenKind::Ident(p) = &tokens[*pos].kind { + params.push(p.clone()); + *pos += 1; + } else { + break; + } + } + + expect_kind(tokens, pos, TokenKind::Assign)?; + let body = parse_expr_or_block(tokens, pos)?; + let end = e_span(&body).end; + if params.is_empty() { + Ok(Expr::Let { + name, + type_ann, + def: Box::new(body), + body: None, + span: Span::new(start, end), + }) + } else { + let lambda = params.into_iter().rfold(body, |acc, p| { + Expr::Lambda(vec![p], Box::new(acc), Span::new(start, end)) + }); + Ok(Expr::Let { + name, + type_ann, + def: Box::new(lambda), + body: None, + span: Span::new(start, end), + }) + } + } + TokenKind::Case => { + *pos += 1; + let scrut = parse_expr(tokens, pos)?; + skip_newlines(tokens, pos); + expect_kind(tokens, pos, TokenKind::Of)?; + skip_newlines(tokens, pos); + let mut arms = Vec::new(); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::INDENT { + *pos += 1; + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::DEDENT { + let pat = parse_pattern(tokens, pos)?; + expect_kind(tokens, pos, TokenKind::Arrow)?; + let arm_body = parse_expr(tokens, pos)?; + arms.push((pat, Box::new(arm_body))); + if *pos < tokens.len() && (tokens[*pos].kind == TokenKind::Semicolon || tokens[*pos].kind == TokenKind::NEWLINE) { + *pos += 1; + } + } + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::DEDENT { + *pos += 1; + } + let end = if !arms.is_empty() { e_span(&arms.last().unwrap().1).end } else { tokens[*pos - 1].end }; + Ok(Expr::Case(Box::new(scrut), arms, Span::new(start, end))) + } else { + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::Eof && tokens[*pos].kind != TokenKind::NEWLINE { + let pat = parse_pattern(tokens, pos)?; + expect_kind(tokens, pos, TokenKind::Arrow)?; + let arm_body = parse_expr(tokens, pos)?; + arms.push((pat, Box::new(arm_body))); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Semicolon { + *pos += 1; + } else { + break; + } + } + let end = if !arms.is_empty() { e_span(&arms.last().unwrap().1).end } else { tokens[*pos - 1].end }; + Ok(Expr::Case(Box::new(scrut), arms, Span::new(start, end))) + } + } + TokenKind::Try => { + *pos += 1; + let try_expr = parse_expr(tokens, pos)?; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Catch { + *pos += 1; + let pat = parse_pattern(tokens, pos)?; + skip_newlines(tokens, pos); + expect_kind(tokens, pos, TokenKind::Arrow)?; + let handler = parse_expr(tokens, pos)?; + let end = e_span(&handler).end; + Ok(Expr::Catch(Box::new(try_expr), pat, Box::new(handler), Span::new(start, end))) + } else { + let end = e_span(&try_expr).end; + Ok(Expr::Try(Box::new(try_expr), Span::new(start, end))) + } + } + TokenKind::Error => { + *pos += 1; + let msg = parse_expr(tokens, pos)?; + let end = e_span(&msg).end; + Ok(Expr::Throw(Box::new(msg), Span::new(start, end))) + } + TokenKind::LBracket => { + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::RBracket { + *pos += 1; + let end = tokens[*pos - 1].end; + return Ok(Expr::List(vec![], Span::new(start, end))); + } + let first = parse_expr(tokens, pos)?; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::For { + let expr = first; + let generators = Vec::new(); + let filters = Vec::new(); + let (gens, filters) = parse_list_comp_generators(tokens, pos, generators, filters)?; + let end = expect_kind(tokens, pos, TokenKind::RBracket)?; + Ok(Expr::ListComp { + expr: Box::new(expr), + generators: gens, + filters, + span: Span::new(start, end), + }) + } else if *pos < tokens.len() && tokens[*pos].kind == TokenKind::DotDot { + *pos += 1; + let end_expr = parse_expr(tokens, pos)?; + let end = expect_kind(tokens, pos, TokenKind::RBracket)?; + Ok(Expr::Range(Box::new(first), Box::new(end_expr), Span::new(start, end))) + } else { + let mut elems = vec![first]; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + elems.push(parse_expr(tokens, pos)?); + } + let end = expect_kind(tokens, pos, TokenKind::RBracket)?; + Ok(Expr::List(elems, Span::new(start, end))) + } + } + TokenKind::LParen => { + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::RParen { + *pos += 1; + let end = tokens[*pos - 1].end; + return Ok(Expr::Lit(Literal::Unit, Span::new(start, end))); + } + let expr = parse_expr(tokens, pos)?; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + let mut elems = vec![expr]; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + elems.push(parse_expr(tokens, pos)?); + } + let end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Expr::Tuple(elems, Span::new(start, end))) + } else { + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(expr) + } + } + TokenKind::Lambda => { + *pos += 1; + let mut params = Vec::new(); + while *pos < tokens.len() && matches!(tokens[*pos].kind, TokenKind::Ident(_)) { + if let TokenKind::Ident(p) = &tokens[*pos].kind { + params.push(p.clone()); + *pos += 1; + } else { + break; + } + } + expect_kind(tokens, pos, TokenKind::Arrow)?; + let body = parse_expr_or_block(tokens, pos)?; + let end = e_span(&body).end; + Ok(Expr::Lambda(params, Box::new(body), Span::new(start, end))) + } + TokenKind::Struct => { + *pos += 1; + let name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected struct name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::Assign)?; + expect_kind(tokens, pos, TokenKind::LParen)?; + let mut fields = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + let field_name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected field name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::Assign)?; + let field_type = match &tokens[*pos].kind { + TokenKind::Ident(t) => t.clone(), + _ => return Err(ParseError { message: "expected type name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + fields.push((field_name, field_type)); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } else { + break; + } + } + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Expr::StructDef(name, fields, Span::new(start, _end))) + } + TokenKind::Data => { + *pos += 1; + let name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected data type name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + let mut params = Vec::new(); + while *pos < tokens.len() && matches!(tokens[*pos].kind, TokenKind::Ident(_)) { + if let TokenKind::Ident(p) = &tokens[*pos].kind { + params.push(p.clone()); + *pos += 1; + } else { break; } + } + expect_kind(tokens, pos, TokenKind::Assign)?; + let mut constructors = Vec::new(); + loop { + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::RParen { + *pos += 1; + break; + } + let ctor_name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected constructor name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + let ctor_start = tokens[*pos].start; + *pos += 1; + let mut fields = Vec::new(); + while *pos < tokens.len() && matches!(tokens[*pos].kind, TokenKind::Ident(_)) { + if let TokenKind::Ident(t) = &tokens[*pos].kind { + fields.push(t.clone()); + *pos += 1; + } else { break; } + } + let ctor_end = tokens[*pos - 1].end; + constructors.push(ConstructorDef { name: ctor_name, fields, span: Span::new(ctor_start, ctor_end) }); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Pipe { + *pos += 1; + } else { + break; + } + } + let _end = tokens[*pos - 1].end; + Ok(Expr::DataDef(name, params, constructors, Span::new(start, _end))) + } + TokenKind::For => { + *pos += 1; + let var = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected loop variable".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::In)?; + let iterable = parse_expr(tokens, pos)?; + skip_newlines(tokens, pos); + expect_kind(tokens, pos, TokenKind::Colon)?; + let body = parse_expr_or_block(tokens, pos)?; + let end = e_span(&body).end; + Ok(Expr::For(var, Box::new(iterable), Box::new(body), Span::new(start, end))) + } + TokenKind::While => { + *pos += 1; + let cond = parse_expr(tokens, pos)?; + skip_newlines(tokens, pos); + expect_kind(tokens, pos, TokenKind::Colon)?; + let body = parse_expr_or_block(tokens, pos)?; + let end = e_span(&body).end; + Ok(Expr::While(Box::new(cond), Box::new(body), Span::new(start, end))) + } + TokenKind::Loop => { + *pos += 1; + expect_kind(tokens, pos, TokenKind::Colon)?; + let body = parse_expr_or_block(tokens, pos)?; + let end = e_span(&body).end; + Ok(Expr::Loop(Box::new(body), Span::new(start, end))) + } + TokenKind::Break => { + *pos += 1; + let opt = if *pos < tokens.len() && !matches!(tokens[*pos].kind, TokenKind::RBrace | TokenKind::NEWLINE | TokenKind::Semicolon | TokenKind::Eof) { + Some(Box::new(parse_expr(tokens, pos)?)) + } else { + None + }; + let end = if let Some(e) = &opt { e_span(e).end } else { tokens[*pos - 1].end }; + Ok(Expr::Break(opt, Span::new(start, end))) + } + TokenKind::Class => { + *pos += 1; + let name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected class name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + let mut extends = None; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Extends { + *pos += 1; + if let TokenKind::Ident(parent) = &tokens[*pos].kind { + extends = Some(parent.clone()); + *pos += 1; + } else { + return Err(ParseError { message: "expected parent class name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }); + } + } + expect_kind(tokens, pos, TokenKind::Assign)?; + expect_kind(tokens, pos, TokenKind::LParen)?; + let mut fields = Vec::new(); + let mut methods = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + match &tokens[*pos].kind { + TokenKind::Ident(name) => { + let method_name = name.clone(); + let start = tokens[*pos].start; + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Assign { + *pos += 1; + let field_type = if let TokenKind::Ident(t) = &tokens[*pos].kind { + t.clone() + } else { + return Err(ParseError { message: "expected type name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }); + }; + *pos += 1; + fields.push((method_name, Some(field_type))); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } else if *pos < tokens.len() && tokens[*pos].kind == TokenKind::LParen { + *pos += 1; + let mut params = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + if let TokenKind::Ident(p) = &tokens[*pos].kind { + params.push(p.clone()); + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } else { + return Err(ParseError { message: "expected parameter name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }); + } + } + expect_kind(tokens, pos, TokenKind::RParen)?; + expect_kind(tokens, pos, TokenKind::Assign)?; + let body = parse_expr(tokens, pos)?; + let end = e_span(&body).end; + methods.push(MethodDef { + name: method_name, + params, + body: Box::new(body), + span: Span::new(start, end), + }); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Semicolon { + *pos += 1; + } + } else { + return Err(ParseError { message: "expected '=' or '(' after field/method name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }); + } + } + _ => return Err(ParseError { message: format!("unexpected token {:?}", tokens[*pos].kind), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + } + } + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Expr::ClassDef { + name, + extends, + fields, + methods, + span: Span::new(start, _end), + }) + } + TokenKind::New => { + *pos += 1; + let class_name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected class name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::LParen)?; + let mut args = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + args.push(parse_expr(tokens, pos)?); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Expr::New(class_name, args, Span::new(start, _end))) + } + TokenKind::Percent => { + *pos += 1; + match &tokens[*pos].kind { + TokenKind::LParen => { + *pos += 1; + let mut entries = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + let key = parse_expr(tokens, pos)?; + expect_kind(tokens, pos, TokenKind::FatArrow)?; + let value = parse_expr(tokens, pos)?; + entries.push((key, value)); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Expr::MapLiteral(entries, Span::new(start, _end))) + } + TokenKind::LBracket => { + *pos += 1; + let mut elems = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RBracket { + elems.push(parse_expr(tokens, pos)?); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let end = expect_kind(tokens, pos, TokenKind::RBracket)?; + Ok(Expr::SetLiteral(elems, Span::new(start, end))) + } + _ => Err(ParseError { message: "expected '(' or '[' after '%'".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + } + } + _ => Err(ParseError { message: format!("unexpected token {:?}", tokens[*pos].kind), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + } +} + +fn is_struct_constructor(tokens: &[Token], mut pos: usize) -> bool { + let mut depth = 1; + while pos < tokens.len() && depth > 0 { + match tokens[pos].kind { + TokenKind::LParen => depth += 1, + TokenKind::RParen => depth -= 1, + TokenKind::Assign => { + if depth == 1 { + return true; + } + } + _ => {} + } + pos += 1; + } + false +} + +fn parse_postfix(mut expr: Expr, tokens: &[Token], pos: &mut usize) -> ParseResult { + loop { + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::LBracket { + let start = tokens[*pos].start; + *pos += 1; + let index = parse_expr(tokens, pos)?; + let end = expect_kind(tokens, pos, TokenKind::RBracket)?; + expr = Expr::Index(Box::new(expr), Box::new(index), Span::new(start, end)); + } else if *pos < tokens.len() && tokens[*pos].kind == TokenKind::DoubleColon { + let start = tokens[*pos].start; + *pos += 1; + let type_name = match &tokens[*pos].kind { + TokenKind::Ident(t) => t.clone(), + _ => return Err(ParseError { message: "expected type name after ::".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + let end = tokens[*pos - 1].end; + expr = Expr::Cast(Box::new(expr), type_name, Span::new(start, end)); + continue; + } else if *pos < tokens.len() && tokens[*pos].kind == TokenKind::LBrace { + let start = tokens[*pos].start; + *pos += 1; + let mut updates = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RBrace { + let field = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected field name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::Assign)?; + let val = parse_expr(tokens, pos)?; + updates.push((field, val)); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let end = expect_kind(tokens, pos, TokenKind::RBrace)?; + expr = Expr::RecordUpdate(Box::new(expr), updates, Span::new(start, end)); + } else if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Dot { + let start = tokens[*pos].start; + *pos += 1; + if let TokenKind::Ident(field) = &tokens[*pos].kind { + let field = field.clone(); + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::LParen { + *pos += 1; + let mut args = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + args.push(parse_expr(tokens, pos)?); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let end = expect_kind(tokens, pos, TokenKind::RParen)?; + expr = Expr::MethodCall(Box::new(expr), field, args, Span::new(start, end)); + } else { + let end = tokens[*pos - 1].end; + expr = Expr::FieldAccess(Box::new(expr), field, Span::new(start, end)); + } + } else { + return Err(ParseError { message: "expected field or method name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }); + } + } else { + break; + } + } + Ok(expr) +} + +fn parse_application(mut func: Expr, tokens: &[Token], pos: &mut usize) -> ParseResult { + loop { + if *pos < tokens.len() && is_argument_start(&tokens[*pos].kind) { + let arg = parse_expr(tokens, pos)?; + let end = e_span(&arg).end; + let start = e_span(&func).start; + func = Expr::App(Box::new(func), Box::new(arg), Span::new(start, end)); + } else { + break; + } + } + Ok(func) +} + +fn is_argument_start(kind: &TokenKind) -> bool { + matches!(kind, + TokenKind::Literal(_) | TokenKind::Ident(_) | TokenKind::Super | + TokenKind::LParen | TokenKind::LBracket | + TokenKind::Lambda | TokenKind::If | TokenKind::Let | TokenKind::Case | + TokenKind::Try | TokenKind::Error | TokenKind::For | TokenKind::While | TokenKind::Not | + TokenKind::Minus | TokenKind::Percent | TokenKind::Class | TokenKind::New | TokenKind::Struct | TokenKind::Data | + TokenKind::FString(_) | TokenKind::Loop | TokenKind::Break + ) +} + +fn parse_expr_or_block(tokens: &[Token], pos: &mut usize) -> ParseResult { + skip_newlines(tokens, pos); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::INDENT { + let start = tokens[*pos].start; + *pos += 1; + let mut exprs = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::DEDENT { + let e = parse_expr(tokens, pos)?; + exprs.push(e); + if *pos < tokens.len() && (tokens[*pos].kind == TokenKind::NEWLINE || tokens[*pos].kind == TokenKind::Semicolon) { + *pos += 1; + } + } + let end = if *pos < tokens.len() && tokens[*pos].kind == TokenKind::DEDENT { + let dedent_end = tokens[*pos].end; + *pos += 1; + dedent_end + } else { + tokens[*pos - 1].end + }; + if exprs.len() == 1 { + Ok(exprs.remove(0)) + } else { + Ok(Expr::Block(exprs, Span::new(start, end))) + } + } else { + parse_expr(tokens, pos) + } +} + +fn parse_pattern(tokens: &[Token], pos: &mut usize) -> Result { + if *pos >= tokens.len() { + return Err(ParseError { message: "incomplete input".to_string(), span: Span::dummy() }); + } + let start = tokens[*pos].start; + match &tokens[*pos].kind { + TokenKind::Ident(name) => { + *pos += 1; + let end = tokens[*pos - 1].end; + if name == "_" { + Ok(Pattern::Wildcard(Span::new(start, end))) + } else { + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::LParen { + *pos += 1; + let mut sub_pats = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + let pat = parse_pattern(tokens, pos)?; + sub_pats.push(pat); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Pattern::Constructor(name.clone(), sub_pats, Span::new(start, end))) + } else { + Ok(Pattern::Var(name.clone(), Span::new(start, end))) + } + } + } + TokenKind::Literal(lit) => { + *pos += 1; + let end = tokens[*pos - 1].end; + Ok(Pattern::Literal(lit.clone(), Span::new(start, end))) + } + TokenKind::LBracket => { + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::RBracket { + *pos += 1; + let end = tokens[*pos - 1].end; + return Ok(Pattern::List(vec![], Span::new(start, end))); + } + let mut pats = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RBracket { + pats.push(parse_pattern(tokens, pos)?); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let end = expect_kind(tokens, pos, TokenKind::RBracket)?; + Ok(Pattern::List(pats, Span::new(start, end))) + } + TokenKind::LParen => { + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::RParen { + *pos += 1; + let end = tokens[*pos - 1].end; + return Ok(Pattern::Literal(Literal::Unit, Span::new(start, end))); + } + let pat = parse_pattern(tokens, pos)?; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + let mut pats = vec![pat]; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + pats.push(parse_pattern(tokens, pos)?); + } + let end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Pattern::Tuple(pats, Span::new(start, end))) + } else { + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(pat) + } + } + TokenKind::LBrace => { + *pos += 1; + let mut fields = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RBrace { + let field_name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected field name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::Assign)?; + let pat = parse_pattern(tokens, pos)?; + fields.push((field_name, pat)); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let end = expect_kind(tokens, pos, TokenKind::RBrace)?; + Ok(Pattern::Record(fields, Span::new(start, end))) + } + _ => Err(ParseError { message: format!("unexpected token {:?}", tokens[*pos].kind), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + } +} + +fn parse_type(tokens: &[Token], pos: &mut usize) -> Result { + let mut type_str = String::new(); + while *pos < tokens.len() { + match &tokens[*pos].kind { + TokenKind::Ident(name) => { + if !type_str.is_empty() { type_str.push(' '); } + type_str.push_str(name); + *pos += 1; + } + TokenKind::Arrow => { + type_str.push_str(" -> "); + *pos += 1; + } + _ => break, + } + } + if type_str.is_empty() { + Err(ParseError { message: "expected type".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }) + } else { + Ok(type_str) + } +} + +fn expect_kind(tokens: &[Token], pos: &mut usize, expected: TokenKind) -> Result { + if *pos < tokens.len() && tokens[*pos].kind == expected { + let end = tokens[*pos].end; + *pos += 1; + Ok(end) + } else { + Err(ParseError { message: format!("expected {:?}", expected), span: Span::new(tokens[*pos].start, tokens[*pos].end) }) + } +} + +fn parse_fstring(raw: &str) -> Result, ParseError> { + let mut parts = Vec::new(); + let chars: Vec = raw.chars().collect(); + let mut i = 0; + while i < chars.len() { + let ch = chars[i]; + if ch == '{' { + if i + 1 < chars.len() && chars[i + 1] == '{' { + parts.push(FStringPart::Literal("{".to_string())); + i += 2; + continue; + } + i += 1; + let start = i; + let mut depth = 1; + let mut in_string = false; + while i < chars.len() && depth > 0 { + let c = chars[i]; + if c == '"' && (i == 0 || chars[i-1] != '\\') { + in_string = !in_string; + } + if !in_string { + if c == '{' { + depth += 1; + } else if c == '}' { + depth -= 1; + if depth == 0 { + break; + } + } + } + i += 1; + } + if depth != 0 { + return Err(ParseError { message: "unclosed expression in f-string".to_string(), span: Span::dummy() }); + } + let expr_str: String = chars[start..i].iter().collect(); + let expr = parse_expression(&expr_str)?; + parts.push(FStringPart::Expr(Box::new(expr))); + i += 1; + } else if ch == '}' && i + 1 < chars.len() && chars[i + 1] == '}' { + parts.push(FStringPart::Literal("}".to_string())); + i += 2; + } else { + let start = i; + while i < chars.len() { + let c = chars[i]; + if c == '{' || c == '}' { + break; + } + i += 1; + } + let lit: String = chars[start..i].iter().collect(); + let processed = process_escapes(&lit)?; + parts.push(FStringPart::Literal(processed)); + } + } + Ok(parts) +} + +fn process_escapes(s: &str) -> Result { + let mut result = String::new(); + let mut chars = s.chars().peekable(); + while let Some(ch) = chars.next() { + if ch == '\\' { + if let Some(esc) = chars.next() { + match esc { + 'n' => result.push('\n'), + 't' => result.push('\t'), + 'r' => result.push('\r'), + '\\' => result.push('\\'), + '"' => result.push('"'), + '\'' => result.push('\''), + _ => return Err(ParseError { message: format!("invalid escape sequence: \\{}", esc), span: Span::dummy() }), + } + } else { + return Err(ParseError { message: "unfinished escape sequence".to_string(), span: Span::dummy() }); + } + } else { + result.push(ch); + } + } + Ok(result) +} + +fn parse_list_comp_generators(tokens: &[Token], pos: &mut usize, mut gens: Vec<(String, Box)>, mut filters: Vec>) -> ParseResult<(Vec<(String, Box)>, Vec>)> { + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RBracket { + match tokens[*pos].kind { + TokenKind::For => { + *pos += 1; + let var = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected loop variable".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::In)?; + let iter = parse_expr(tokens, pos)?; + gens.push((var, Box::new(iter))); + } + TokenKind::If => { + *pos += 1; + let cond = parse_expr(tokens, pos)?; + filters.push(Box::new(cond)); + } + _ => break, + } + } + Ok((gens, filters)) +} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..5ac2ae3 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,115 @@ +//! Contact server. + +use std::collections::BTreeMap; +use std::io::{BufRead, BufReader, Write}; +use std::sync::{Arc, Mutex}; +use std::thread; +use native_tls::TlsStream; +use std::net::TcpStream; +use std::sync::mpsc::Sender; + +pub type ContactsDb = Arc>>; + +pub fn start_contacts_server(addr: &str, password: Option) -> Result<(ContactsDb, thread::JoinHandle<()>, Sender<()>), Box> { + let db = Arc::new(Mutex::new(BTreeMap::new())); + let listener = std::net::TcpListener::bind(addr)?; + listener.set_nonblocking(true)?; + let acceptor = crate::p2p::get_tls_acceptor(); + let db_clone = db.clone(); + let password_arc = Arc::new(password); + + let (stop_tx, stop_rx) = std::sync::mpsc::channel(); + + let handle = thread::spawn(move || { + loop { + if let Ok(()) = stop_rx.try_recv() { + break; + } + + match listener.accept() { + Ok((stream, _)) => { + let db = db_clone.clone(); + let acceptor = acceptor.clone(); + let password = password_arc.clone(); + thread::spawn(move || { + if let Ok(tls_stream) = acceptor.accept(stream) { + handle_connection(tls_stream, db, password); + } + }); + } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + thread::sleep(std::time::Duration::from_millis(10)); + continue; + } + Err(e) => { + eprintln!("Contact server accept error: {}", e); + break; + } + } + } + }); + + Ok((db, handle, stop_tx)) +} + +fn handle_connection(stream: TlsStream, db: ContactsDb, password: Arc>) { + let mut reader = BufReader::new(stream); + let mut line = String::new(); + + let mut authenticated = password.is_none(); + if let Some(pass) = &*password { + if let Ok(n) = reader.read_line(&mut line) { + if n == 0 { return; } + let trimmed = line.trim(); + if trimmed.starts_with("PASSWORD ") { + let provided = trimmed[9..].trim(); + if provided == pass { + authenticated = true; + let _ = writeln!(reader.get_mut(), "OK"); + let _ = reader.get_mut().flush(); + } else { + let _ = writeln!(reader.get_mut(), "ERROR Invalid password"); + let _ = reader.get_mut().flush(); + return; + } + } else { + let _ = writeln!(reader.get_mut(), "ERROR Password required"); + let _ = reader.get_mut().flush(); + return; + } + } else { + return; + } + } + + line.clear(); + while let Ok(n) = reader.read_line(&mut line) { + if n == 0 { break; } + let trimmed = line.trim(); + let parts: Vec<&str> = trimmed.splitn(2, ' ').collect(); + match parts[0] { + "REGISTER" => { + if authenticated && parts.len() == 2 { + let mut split = parts[1].splitn(2, ' '); + if let (Some(uid), Some(addr)) = (split.next(), split.next()) { + db.lock().unwrap().insert(uid.to_string(), addr.to_string()); + let _ = writeln!(reader.get_mut(), "OK"); + let _ = reader.get_mut().flush(); + } + } + } + "GET" => { + if authenticated { + let contacts = db.lock().unwrap().iter() + .map(|(k, v)| format!("{} {}", k, v)) + .collect::>() + .join("\n"); + let _ = writeln!(reader.get_mut(), "{}", contacts); + let _ = reader.get_mut().flush(); + } + } + _ => {} + } + line.clear(); + } +} diff --git a/src/tests/integration.rs b/src/tests/integration.rs new file mode 100644 index 0000000..51dd4e4 --- /dev/null +++ b/src/tests/integration.rs @@ -0,0 +1,1225 @@ +//! Parser with indentation‑sensitive blocks, list comprehensions, and spans. + +use crate::ast::*; +use crate::lexer::{LexError, Lexer, Token, TokenKind}; + +#[derive(Debug, Clone, PartialEq)] +pub struct ParseError { + pub message: String, + pub span: Span, +} + +impl std::fmt::Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.message) + } +} + +impl std::error::Error for ParseError {} + +impl From for ParseError { + fn from(e: LexError) -> Self { + ParseError { message: e.to_string(), span: Span::dummy() } + } +} + +pub type ParseResult = Result; + +pub fn strip_comments(input: &str) -> String { + let mut output = String::new(); + let mut chars = input.chars().peekable(); + while let Some(ch) = chars.next() { + if ch == '#' { + if chars.peek() == Some(&'-') { + chars.next(); + let mut depth = 1; + while depth > 0 { + match chars.next() { + Some('#') => { + if chars.peek() == Some(&'-') { + chars.next(); + depth += 1; + } + } + Some('-') => { + if chars.peek() == Some(&'#') { + chars.next(); + depth -= 1; + } + } + Some(_) => {} + None => break, + } + } + continue; + } else { + while let Some(c) = chars.next() { + if c == '\n' { + output.push(c); + break; + } + } + continue; + } + } + output.push(ch); + } + output +} + +pub fn parse_expression(input: &str) -> ParseResult { + let stripped = strip_comments(input); + let mut lexer = Lexer::new(&stripped); + let tokens = lexer.tokenize()?; + let mut pos = 0; + skip_newlines(&tokens, &mut pos); + let expr = parse_expr(&tokens, &mut pos)?; + while pos < tokens.len() && (matches!(tokens[pos].kind, TokenKind::NEWLINE) || matches!(tokens[pos].kind, TokenKind::DEDENT)) { + pos += 1; + } + if pos < tokens.len() && tokens[pos].kind != TokenKind::Eof { + Err(ParseError { message: format!("Unexpected token: {:?}", tokens[pos].kind), span: Span::new(tokens[pos].start, tokens[pos].end) }) + } else { + Ok(expr) + } +} + +pub fn parse_script(input: &str) -> ParseResult { + let stripped = strip_comments(input); + let mut lexer = Lexer::new(&stripped); + let tokens = lexer.tokenize()?; + let mut pos = 0; + let mut exprs = Vec::new(); + skip_newlines(&tokens, &mut pos); + while pos < tokens.len() && tokens[pos].kind != TokenKind::Eof { + let e = parse_expr(&tokens, &mut pos)?; + exprs.push(e); + while pos < tokens.len() && (matches!(tokens[pos].kind, TokenKind::NEWLINE) || matches!(tokens[pos].kind, TokenKind::Semicolon)) { + pos += 1; + } + } + if exprs.is_empty() { + Ok(Expr::Lit(Literal::Unit, Span::dummy())) + } else if exprs.len() == 1 { + Ok(exprs.remove(0)) + } else { + let start = exprs.first().map(|e| e_span(e).start).unwrap_or(0); + let end = exprs.last().map(|e| e_span(e).end).unwrap_or(0); + Ok(Expr::Block(exprs, Span::new(start, end))) + } +} + +fn e_span(e: &Expr) -> Span { + match e { + Expr::Lit(_, s) => *s, + Expr::Var(_, s) => *s, + Expr::Lambda(_, _, s) => *s, + Expr::App(_, _, s) => *s, + Expr::If(_, _, _, s) => *s, + Expr::Let { span, .. } => *span, + Expr::Assign(_, _, s) => *s, + Expr::Case(_, _, s) => *s, + Expr::Try(_, s) => *s, + Expr::Catch(_, _, _, s) => *s, + Expr::Throw(_, s) => *s, + Expr::DataDef(_, _, _, s) => *s, + Expr::StructDef(_, _, s) => *s, + Expr::StructNew(_, _, s) => *s, + Expr::Constructor(_, _, s) => *s, + Expr::Record(_, s) => *s, + Expr::FieldAccess(_, _, s) => *s, + Expr::RecordUpdate(_, _, s) => *s, + Expr::List(_, s) => *s, + Expr::Range(_, _, s) => *s, + Expr::BinOp(_, _, _, s) => *s, + Expr::Concat(_, _, s) => *s, + Expr::Pipe(_, _, s) => *s, + Expr::Dollar(_, _, s) => *s, + Expr::LogicalAnd(_, _, s) => *s, + Expr::LogicalOr(_, _, s) => *s, + Expr::Not(_, s) => *s, + Expr::Tuple(_, s) => *s, + Expr::Index(_, _, s) => *s, + Expr::For(_, _, _, s) => *s, + Expr::While(_, _, s) => *s, + Expr::Loop(_, s) => *s, + Expr::Break(_, s) => *s, + Expr::Block(_, s) => *s, + Expr::ClassDef { span, .. } => *span, + Expr::New(_, _, s) => *s, + Expr::MethodCall(_, _, _, s) => *s, + Expr::MapLiteral(_, s) => *s, + Expr::SetLiteral(_, s) => *s, + Expr::FString(_, s) => *s, + Expr::ListComp { span, .. } => *span, + } +} + +fn parse_expr(tokens: &[Token], pos: &mut usize) -> ParseResult { + parse_dollar(tokens, pos) +} + +fn parse_dollar(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_assign(tokens, pos)?; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Dollar { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_assign(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::Dollar(Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_assign(tokens: &[Token], pos: &mut usize) -> ParseResult { + let left = parse_pipe(tokens, pos)?; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Assign { + if let Expr::Var(name, _span) = left { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_assign(tokens, pos)?; + let end = e_span(&right).end; + Ok(Expr::Assign(name, Box::new(right), Span::new(start, end))) + } else { + Err(ParseError { message: "only variables can be assigned to".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }) + } + } else { + Ok(left) + } +} + +fn parse_pipe(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_logical_or(tokens, pos)?; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Pipe { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_logical_or(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::Pipe(Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_logical_or(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_logical_and(tokens, pos)?; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Or { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_logical_and(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::LogicalOr(Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_logical_and(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_comparison(tokens, pos)?; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::And { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_comparison(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::LogicalAnd(Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_comparison(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_concat(tokens, pos)?; + while *pos < tokens.len() { + let op = match tokens[*pos].kind { + TokenKind::Eq => BinOp::Eq, + TokenKind::Neq => BinOp::Neq, + TokenKind::Lt => BinOp::Lt, + TokenKind::Le => BinOp::Le, + TokenKind::Gt => BinOp::Gt, + TokenKind::Ge => BinOp::Ge, + TokenKind::In => { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_concat(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::BinOp(BinOp::In, Box::new(left), Box::new(right), Span::new(start, end)); + continue; + } + _ => break, + }; + let start = tokens[*pos].start; + *pos += 1; + let right = parse_concat(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::BinOp(op, Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_concat(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_cons(tokens, pos)?; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Concat { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_cons(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::Concat(Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_cons(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_addsub(tokens, pos)?; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Cons { + let start = tokens[*pos].start; + *pos += 1; + let right = parse_cons(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::BinOp(BinOp::Cons, Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_addsub(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_muldiv(tokens, pos)?; + while *pos < tokens.len() { + let op = match tokens[*pos].kind { + TokenKind::Plus => BinOp::Add, + TokenKind::Minus => BinOp::Sub, + _ => break, + }; + let start = tokens[*pos].start; + *pos += 1; + let right = parse_muldiv(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::BinOp(op, Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_muldiv(tokens: &[Token], pos: &mut usize) -> ParseResult { + let mut left = parse_unary(tokens, pos)?; + while *pos < tokens.len() { + let op = match tokens[*pos].kind { + TokenKind::Star => BinOp::Mul, + TokenKind::Slash => BinOp::Div, + TokenKind::Percent => BinOp::Mod, + _ => break, + }; + let start = tokens[*pos].start; + *pos += 1; + let right = parse_unary(tokens, pos)?; + let end = e_span(&right).end; + left = Expr::BinOp(op, Box::new(left), Box::new(right), Span::new(start, end)); + } + Ok(left) +} + +fn parse_unary(tokens: &[Token], pos: &mut usize) -> ParseResult { + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Not { + let start = tokens[*pos].start; + *pos += 1; + let expr = parse_atom(tokens, pos)?; + let end = e_span(&expr).end; + Ok(Expr::Not(Box::new(expr), Span::new(start, end))) + } else if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Minus { + let start = tokens[*pos].start; + *pos += 1; + let expr = parse_atom(tokens, pos)?; + let end = e_span(&expr).end; + Ok(Expr::BinOp(BinOp::Sub, Box::new(Expr::Lit(Literal::Int(0), Span::new(start, start))), Box::new(expr), Span::new(start, end))) + } else { + parse_atom(tokens, pos) + } +} + +fn skip_newlines(tokens: &[Token], pos: &mut usize) { + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::NEWLINE { + *pos += 1; + } +} + +fn parse_atom(tokens: &[Token], pos: &mut usize) -> ParseResult { + if *pos >= tokens.len() { + return Err(ParseError { message: "incomplete input".to_string(), span: Span::dummy() }); + } + let start = tokens[*pos].start; + match &tokens[*pos].kind { + TokenKind::Literal(lit) => { + *pos += 1; + let end = tokens[*pos - 1].end; + let expr = Expr::Lit(lit.clone(), Span::new(start, end)); + parse_postfix(expr, tokens, pos) + } + TokenKind::FString(content) => { + *pos += 1; + let end = tokens[*pos - 1].end; + let parts = parse_fstring(content)?; + Ok(Expr::FString(parts, Span::new(start, end))) + } + TokenKind::Ident(name) => { + *pos += 1; + let end = tokens[*pos - 1].end; + let mut expr = Expr::Var(name.clone(), Span::new(start, end)); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::LParen { + let temp_pos = *pos + 1; + if is_struct_constructor(tokens, temp_pos) { + *pos += 1; + let mut fields = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + let field_name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected field name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + let _field_start = tokens[*pos].start; + *pos += 1; + expect_kind(tokens, pos, TokenKind::Assign)?; + let value = parse_expr(tokens, pos)?; + let _value_end = e_span(&value).end; + fields.push((field_name, value)); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + expr = Expr::StructNew(name.clone(), fields, Span::new(start, _end)); + } else { + *pos += 1; + let mut args = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + let arg = parse_expr(tokens, pos)?; + args.push(arg); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + for arg in args { + let end = e_span(&arg).end; + expr = Expr::App(Box::new(expr), Box::new(arg), Span::new(start, end)); + } + } + } else { + expr = parse_application(expr, tokens, pos)?; + } + parse_postfix(expr, tokens, pos) + } + TokenKind::If => { + *pos += 1; + let cond = parse_expr(tokens, pos)?; + skip_newlines(tokens, pos); + expect_kind(tokens, pos, TokenKind::Then)?; + let then_expr = parse_expr_or_block(tokens, pos)?; + skip_newlines(tokens, pos); + expect_kind(tokens, pos, TokenKind::Else)?; + let else_expr = parse_expr_or_block(tokens, pos)?; + let end = e_span(&else_expr).end; + Ok(Expr::If(Box::new(cond), Box::new(then_expr), Box::new(else_expr), Span::new(start, end))) + } + TokenKind::Let => { + *pos += 1; + let name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected identifier".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + let _name_start = tokens[*pos].start; + *pos += 1; + + let mut type_ann = None; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::DoubleColon { + *pos += 1; + let type_str = parse_type(tokens, pos)?; + type_ann = Some(type_str); + } + + let mut params = Vec::new(); + // No parentheses allowed; just a sequence of identifiers without commas + while *pos < tokens.len() && matches!(tokens[*pos].kind, TokenKind::Ident(_)) { + if let TokenKind::Ident(p) = &tokens[*pos].kind { + params.push(p.clone()); + *pos += 1; + } else { + break; + } + } + + expect_kind(tokens, pos, TokenKind::Assign)?; + let body = parse_expr_or_block(tokens, pos)?; + let end = e_span(&body).end; + if params.is_empty() { + Ok(Expr::Let { + name, + type_ann, + def: Box::new(body), + body: None, + span: Span::new(start, end), + }) + } else { + let lambda = params.into_iter().rfold(body, |acc, p| { + Expr::Lambda(vec![p], Box::new(acc), Span::new(start, end)) + }); + Ok(Expr::Let { + name, + type_ann, + def: Box::new(lambda), + body: None, + span: Span::new(start, end), + }) + } + } + TokenKind::Case => { + *pos += 1; + let scrut = parse_expr(tokens, pos)?; + skip_newlines(tokens, pos); + expect_kind(tokens, pos, TokenKind::Of)?; + skip_newlines(tokens, pos); + let mut arms = Vec::new(); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::INDENT { + *pos += 1; + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::DEDENT { + let pat = parse_pattern(tokens, pos)?; + expect_kind(tokens, pos, TokenKind::Arrow)?; + let arm_body = parse_expr(tokens, pos)?; + arms.push((pat, Box::new(arm_body))); + if *pos < tokens.len() && (tokens[*pos].kind == TokenKind::Semicolon || tokens[*pos].kind == TokenKind::NEWLINE) { + *pos += 1; + } + } + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::DEDENT { + *pos += 1; + } + let end = if !arms.is_empty() { e_span(&arms.last().unwrap().1).end } else { tokens[*pos - 1].end }; + Ok(Expr::Case(Box::new(scrut), arms, Span::new(start, end))) + } else { + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::Eof && tokens[*pos].kind != TokenKind::NEWLINE { + let pat = parse_pattern(tokens, pos)?; + expect_kind(tokens, pos, TokenKind::Arrow)?; + let arm_body = parse_expr(tokens, pos)?; + arms.push((pat, Box::new(arm_body))); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Semicolon { + *pos += 1; + } else { + break; + } + } + let end = if !arms.is_empty() { e_span(&arms.last().unwrap().1).end } else { tokens[*pos - 1].end }; + Ok(Expr::Case(Box::new(scrut), arms, Span::new(start, end))) + } + } + TokenKind::Try => { + *pos += 1; + let try_expr = parse_expr(tokens, pos)?; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Catch { + *pos += 1; + let pat = parse_pattern(tokens, pos)?; + skip_newlines(tokens, pos); + expect_kind(tokens, pos, TokenKind::Arrow)?; + let handler = parse_expr(tokens, pos)?; + let end = e_span(&handler).end; + Ok(Expr::Catch(Box::new(try_expr), pat, Box::new(handler), Span::new(start, end))) + } else { + let end = e_span(&try_expr).end; + Ok(Expr::Try(Box::new(try_expr), Span::new(start, end))) + } + } + TokenKind::Error => { + *pos += 1; + let msg = parse_expr(tokens, pos)?; + let end = e_span(&msg).end; + Ok(Expr::Throw(Box::new(msg), Span::new(start, end))) + } + TokenKind::LBracket => { + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::RBracket { + *pos += 1; + let end = tokens[*pos - 1].end; + return Ok(Expr::List(vec![], Span::new(start, end))); + } + let first = parse_expr(tokens, pos)?; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::For { + let expr = first; + let generators = Vec::new(); + let filters = Vec::new(); + let (gens, filters) = parse_list_comp_generators(tokens, pos, generators, filters)?; + let end = expect_kind(tokens, pos, TokenKind::RBracket)?; + Ok(Expr::ListComp { + expr: Box::new(expr), + generators: gens, + filters, + span: Span::new(start, end), + }) + } else if *pos < tokens.len() && tokens[*pos].kind == TokenKind::DotDot { + *pos += 1; + let end_expr = parse_expr(tokens, pos)?; + let end = expect_kind(tokens, pos, TokenKind::RBracket)?; + Ok(Expr::Range(Box::new(first), Box::new(end_expr), Span::new(start, end))) + } else { + let mut elems = vec![first]; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + elems.push(parse_expr(tokens, pos)?); + } + let end = expect_kind(tokens, pos, TokenKind::RBracket)?; + Ok(Expr::List(elems, Span::new(start, end))) + } + } + TokenKind::LParen => { + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::RParen { + *pos += 1; + let end = tokens[*pos - 1].end; + return Ok(Expr::Lit(Literal::Unit, Span::new(start, end))); + } + let expr = parse_expr(tokens, pos)?; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + let mut elems = vec![expr]; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + elems.push(parse_expr(tokens, pos)?); + } + let end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Expr::Tuple(elems, Span::new(start, end))) + } else { + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(expr) + } + } + TokenKind::Backslash | TokenKind::Lambda => { + *pos += 1; + let mut params = Vec::new(); + // No parentheses; just a sequence of identifiers without commas + while *pos < tokens.len() && matches!(tokens[*pos].kind, TokenKind::Ident(_)) { + if let TokenKind::Ident(p) = &tokens[*pos].kind { + params.push(p.clone()); + *pos += 1; + } else { + break; + } + } + expect_kind(tokens, pos, TokenKind::Arrow)?; + let body = parse_expr_or_block(tokens, pos)?; + let end = e_span(&body).end; + Ok(Expr::Lambda(params, Box::new(body), Span::new(start, end))) + } + TokenKind::Struct => { + *pos += 1; + let name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected struct name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::Assign)?; + expect_kind(tokens, pos, TokenKind::LParen)?; + let mut fields = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + let field_name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected field name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::Assign)?; + let field_type = match &tokens[*pos].kind { + TokenKind::Ident(t) => t.clone(), + _ => return Err(ParseError { message: "expected type name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + fields.push((field_name, field_type)); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } else { + break; + } + } + let end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Expr::StructDef(name, fields, Span::new(start, end))) + } + TokenKind::Data => { + *pos += 1; + let name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected data type name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + let mut params = Vec::new(); + while *pos < tokens.len() && matches!(tokens[*pos].kind, TokenKind::Ident(_)) { + if let TokenKind::Ident(p) = &tokens[*pos].kind { + params.push(p.clone()); + *pos += 1; + } else { break; } + } + expect_kind(tokens, pos, TokenKind::Assign)?; + let mut constructors = Vec::new(); + loop { + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::RParen { + *pos += 1; + break; + } + let ctor_name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected constructor name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + let ctor_start = tokens[*pos].start; + *pos += 1; + let mut fields = Vec::new(); + while *pos < tokens.len() && matches!(tokens[*pos].kind, TokenKind::Ident(_)) { + if let TokenKind::Ident(t) = &tokens[*pos].kind { + fields.push(t.clone()); + *pos += 1; + } else { break; } + } + let ctor_end = tokens[*pos - 1].end; + constructors.push(ConstructorDef { name: ctor_name, fields, span: Span::new(ctor_start, ctor_end) }); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Pipe { + *pos += 1; + } else { + break; + } + } + let end = tokens[*pos - 1].end; + Ok(Expr::DataDef(name, params, constructors, Span::new(start, end))) + } + TokenKind::For => { + *pos += 1; + let var = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected loop variable".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::In)?; + let iterable = parse_expr(tokens, pos)?; + skip_newlines(tokens, pos); + expect_kind(tokens, pos, TokenKind::Colon)?; + let body = parse_expr_or_block(tokens, pos)?; + let end = e_span(&body).end; + Ok(Expr::For(var, Box::new(iterable), Box::new(body), Span::new(start, end))) + } + TokenKind::While => { + *pos += 1; + let cond = parse_expr(tokens, pos)?; + skip_newlines(tokens, pos); + expect_kind(tokens, pos, TokenKind::Colon)?; + let body = parse_expr_or_block(tokens, pos)?; + let end = e_span(&body).end; + Ok(Expr::While(Box::new(cond), Box::new(body), Span::new(start, end))) + } + TokenKind::Loop => { + *pos += 1; + expect_kind(tokens, pos, TokenKind::LBrace)?; + let body = parse_expr(tokens, pos)?; + let end = expect_kind(tokens, pos, TokenKind::RBrace)?; + Ok(Expr::Loop(Box::new(body), Span::new(start, end))) + } + TokenKind::Break => { + *pos += 1; + let opt = if *pos < tokens.len() && !matches!(tokens[*pos].kind, TokenKind::RBrace | TokenKind::NEWLINE | TokenKind::Semicolon | TokenKind::Eof) { + Some(Box::new(parse_expr(tokens, pos)?)) + } else { + None + }; + let end = if let Some(e) = &opt { e_span(e).end } else { tokens[*pos - 1].end }; + Ok(Expr::Break(opt, Span::new(start, end))) + } + TokenKind::Class => { + *pos += 1; + let name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected class name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + let mut extends = None; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Extends { + *pos += 1; + if let TokenKind::Ident(parent) = &tokens[*pos].kind { + extends = Some(parent.clone()); + *pos += 1; + } else { + return Err(ParseError { message: "expected parent class name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }); + } + } + expect_kind(tokens, pos, TokenKind::Assign)?; + expect_kind(tokens, pos, TokenKind::LParen)?; + let mut fields = Vec::new(); + let mut methods = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + match &tokens[*pos].kind { + TokenKind::Ident(name) => { + let method_name = name.clone(); + let start = tokens[*pos].start; + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Assign { + *pos += 1; + let field_type = if let TokenKind::Ident(t) = &tokens[*pos].kind { + t.clone() + } else { + return Err(ParseError { message: "expected type name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }); + }; + *pos += 1; + fields.push((method_name, Some(field_type))); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } else if *pos < tokens.len() && tokens[*pos].kind == TokenKind::LParen { + *pos += 1; + let mut params = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + if let TokenKind::Ident(p) = &tokens[*pos].kind { + params.push(p.clone()); + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } else { + return Err(ParseError { message: "expected parameter name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }); + } + } + expect_kind(tokens, pos, TokenKind::RParen)?; + expect_kind(tokens, pos, TokenKind::Assign)?; + let body = parse_expr(tokens, pos)?; + let end = e_span(&body).end; + methods.push(MethodDef { + name: method_name, + params, + body: Box::new(body), + span: Span::new(start, end), + }); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Semicolon { + *pos += 1; + } + } else { + return Err(ParseError { message: "expected '=' or '(' after field/method name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }); + } + } + _ => return Err(ParseError { message: format!("unexpected token {:?}", tokens[*pos].kind), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + } + } + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Expr::ClassDef { + name, + extends, + fields, + methods, + span: Span::new(start, _end), + }) + } + TokenKind::New => { + *pos += 1; + let class_name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected class name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::LParen)?; + let mut args = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + args.push(parse_expr(tokens, pos)?); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Expr::New(class_name, args, Span::new(start, _end))) + } + TokenKind::Percent => { + *pos += 1; + match &tokens[*pos].kind { + TokenKind::LParen => { + *pos += 1; + let mut entries = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + let key = parse_expr(tokens, pos)?; + expect_kind(tokens, pos, TokenKind::FatArrow)?; + let value = parse_expr(tokens, pos)?; + entries.push((key, value)); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let _end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Expr::MapLiteral(entries, Span::new(start, _end))) + } + TokenKind::LBracket => { + *pos += 1; + let mut elems = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RBracket { + elems.push(parse_expr(tokens, pos)?); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let end = expect_kind(tokens, pos, TokenKind::RBracket)?; + Ok(Expr::SetLiteral(elems, Span::new(start, end))) + } + _ => Err(ParseError { message: "expected '(' or '[' after '%'".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + } + } + _ => Err(ParseError { message: format!("unexpected token {:?}", tokens[*pos].kind), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + } +} + +fn is_struct_constructor(tokens: &[Token], mut pos: usize) -> bool { + let mut depth = 1; + while pos < tokens.len() && depth > 0 { + match tokens[pos].kind { + TokenKind::LParen => depth += 1, + TokenKind::RParen => depth -= 1, + TokenKind::Assign => { + if depth == 1 { + return true; + } + } + _ => {} + } + pos += 1; + } + false +} + +fn parse_postfix(mut expr: Expr, tokens: &[Token], pos: &mut usize) -> ParseResult { + loop { + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::LBracket { + let start = tokens[*pos].start; + *pos += 1; + let index = parse_expr(tokens, pos)?; + let end = expect_kind(tokens, pos, TokenKind::RBracket)?; + expr = Expr::Index(Box::new(expr), Box::new(index), Span::new(start, end)); + } else if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Dot { + let start = tokens[*pos].start; + *pos += 1; + if let TokenKind::Ident(field) = &tokens[*pos].kind { + let field = field.clone(); + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::LParen { + *pos += 1; + let mut args = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + args.push(parse_expr(tokens, pos)?); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let end = expect_kind(tokens, pos, TokenKind::RParen)?; + expr = Expr::MethodCall(Box::new(expr), field, args, Span::new(start, end)); + } else { + // Check for record update: expr { field = value } + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::LBrace { + let start2 = tokens[*pos].start; + *pos += 1; + let mut updates = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RBrace { + let f = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected field name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::Assign)?; + let val = parse_expr(tokens, pos)?; + updates.push((f, val)); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let end2 = expect_kind(tokens, pos, TokenKind::RBrace)?; + expr = Expr::RecordUpdate(Box::new(expr), updates, Span::new(start2, end2)); + } else { + let end = tokens[*pos - 1].end; + expr = Expr::FieldAccess(Box::new(expr), field, Span::new(start, end)); + } + } + } else { + return Err(ParseError { message: "expected field or method name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }); + } + } else { + break; + } + } + Ok(expr) +} + +fn parse_application(mut func: Expr, tokens: &[Token], pos: &mut usize) -> ParseResult { + loop { + if *pos < tokens.len() && is_argument_start(&tokens[*pos].kind) { + let arg = parse_expr(tokens, pos)?; + let end = e_span(&arg).end; + let start = e_span(&func).start; + func = Expr::App(Box::new(func), Box::new(arg), Span::new(start, end)); + } else { + break; + } + } + Ok(func) +} + +fn is_argument_start(kind: &TokenKind) -> bool { + matches!(kind, + TokenKind::Literal(_) | TokenKind::Ident(_) | TokenKind::LParen | TokenKind::LBracket | + TokenKind::Backslash | TokenKind::Lambda | TokenKind::If | TokenKind::Let | TokenKind::Case | + TokenKind::Try | TokenKind::Error | TokenKind::For | TokenKind::While | TokenKind::Not | + TokenKind::Minus | TokenKind::Percent | TokenKind::Class | TokenKind::New | TokenKind::Struct | TokenKind::Data | + TokenKind::FString(_) | TokenKind::Loop | TokenKind::Break + ) +} + +fn parse_expr_or_block(tokens: &[Token], pos: &mut usize) -> ParseResult { + skip_newlines(tokens, pos); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::INDENT { + let start = tokens[*pos].start; + *pos += 1; + let mut exprs = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::DEDENT { + let e = parse_expr(tokens, pos)?; + exprs.push(e); + if *pos < tokens.len() && (tokens[*pos].kind == TokenKind::NEWLINE || tokens[*pos].kind == TokenKind::Semicolon) { + *pos += 1; + } + } + let end = if *pos < tokens.len() && tokens[*pos].kind == TokenKind::DEDENT { + let dedent_end = tokens[*pos].end; + *pos += 1; + dedent_end + } else { + tokens[*pos - 1].end + }; + if exprs.len() == 1 { + Ok(exprs.remove(0)) + } else { + Ok(Expr::Block(exprs, Span::new(start, end))) + } + } else { + parse_expr(tokens, pos) + } +} + +fn parse_pattern(tokens: &[Token], pos: &mut usize) -> Result { + if *pos >= tokens.len() { + return Err(ParseError { message: "incomplete input".to_string(), span: Span::dummy() }); + } + let start = tokens[*pos].start; + match &tokens[*pos].kind { + TokenKind::Ident(name) => { + *pos += 1; + let end = tokens[*pos - 1].end; + if name == "_" { + Ok(Pattern::Wildcard(Span::new(start, end))) + } else { + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::LParen { + *pos += 1; + let mut sub_pats = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RParen { + let pat = parse_pattern(tokens, pos)?; + sub_pats.push(pat); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Pattern::Constructor(name.clone(), sub_pats, Span::new(start, end))) + } else { + Ok(Pattern::Var(name.clone(), Span::new(start, end))) + } + } + } + TokenKind::Literal(lit) => { + *pos += 1; + let end = tokens[*pos - 1].end; + Ok(Pattern::Literal(lit.clone(), Span::new(start, end))) + } + TokenKind::LBracket => { + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::RBracket { + *pos += 1; + let end = tokens[*pos - 1].end; + return Ok(Pattern::List(vec![], Span::new(start, end))); + } + let mut pats = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RBracket { + pats.push(parse_pattern(tokens, pos)?); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let end = expect_kind(tokens, pos, TokenKind::RBracket)?; + Ok(Pattern::List(pats, Span::new(start, end))) + } + TokenKind::LParen => { + *pos += 1; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::RParen { + *pos += 1; + let end = tokens[*pos - 1].end; + return Ok(Pattern::Literal(Literal::Unit, Span::new(start, end))); + } + let pat = parse_pattern(tokens, pos)?; + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + let mut pats = vec![pat]; + while *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + pats.push(parse_pattern(tokens, pos)?); + } + let end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(Pattern::Tuple(pats, Span::new(start, end))) + } else { + let end = expect_kind(tokens, pos, TokenKind::RParen)?; + Ok(pat) + } + } + TokenKind::LBrace => { + *pos += 1; + let mut fields = Vec::new(); + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RBrace { + let field_name = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected field name".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::Assign)?; + let pat = parse_pattern(tokens, pos)?; + fields.push((field_name, pat)); + if *pos < tokens.len() && tokens[*pos].kind == TokenKind::Comma { + *pos += 1; + } + } + let end = expect_kind(tokens, pos, TokenKind::RBrace)?; + Ok(Pattern::Record(fields, Span::new(start, end))) + } + _ => Err(ParseError { message: format!("unexpected token {:?}", tokens[*pos].kind), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + } +} + +fn parse_type(tokens: &[Token], pos: &mut usize) -> Result { + let mut type_str = String::new(); + while *pos < tokens.len() { + match &tokens[*pos].kind { + TokenKind::Ident(name) => { + if !type_str.is_empty() { type_str.push(' '); } + type_str.push_str(name); + *pos += 1; + } + TokenKind::Arrow => { + type_str.push_str(" -> "); + *pos += 1; + } + _ => break, + } + } + if type_str.is_empty() { + Err(ParseError { message: "expected type".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }) + } else { + Ok(type_str) + } +} + +fn expect_kind(tokens: &[Token], pos: &mut usize, expected: TokenKind) -> Result { + if *pos < tokens.len() && tokens[*pos].kind == expected { + let end = tokens[*pos].end; + *pos += 1; + Ok(end) + } else { + Err(ParseError { message: format!("expected {:?}", expected), span: Span::new(tokens[*pos].start, tokens[*pos].end) }) + } +} + +fn parse_fstring(raw: &str) -> Result, ParseError> { + let mut parts = Vec::new(); + let chars: Vec = raw.chars().collect(); + let mut i = 0; + while i < chars.len() { + let ch = chars[i]; + if ch == '{' { + if i + 1 < chars.len() && chars[i + 1] == '{' { + parts.push(FStringPart::Literal("{".to_string())); + i += 2; + continue; + } + i += 1; + let start = i; + let mut depth = 1; + let mut in_string = false; + while i < chars.len() && depth > 0 { + let c = chars[i]; + if c == '"' && (i == 0 || chars[i-1] != '\\') { + in_string = !in_string; + } + if !in_string { + if c == '{' { + depth += 1; + } else if c == '}' { + depth -= 1; + if depth == 0 { + break; + } + } + } + i += 1; + } + if depth != 0 { + return Err(ParseError { message: "unclosed expression in f-string".to_string(), span: Span::dummy() }); + } + let expr_str: String = chars[start..i].iter().collect(); + let expr = parse_expression(&expr_str)?; + parts.push(FStringPart::Expr(Box::new(expr))); + i += 1; + } else if ch == '}' && i + 1 < chars.len() && chars[i + 1] == '}' { + parts.push(FStringPart::Literal("}".to_string())); + i += 2; + } else { + let start = i; + while i < chars.len() { + let c = chars[i]; + if c == '{' || c == '}' { + break; + } + i += 1; + } + let lit: String = chars[start..i].iter().collect(); + let processed = process_escapes(&lit)?; + parts.push(FStringPart::Literal(processed)); + } + } + Ok(parts) +} + +fn process_escapes(s: &str) -> Result { + let mut result = String::new(); + let mut chars = s.chars().peekable(); + while let Some(ch) = chars.next() { + if ch == '\\' { + if let Some(esc) = chars.next() { + match esc { + 'n' => result.push('\n'), + 't' => result.push('\t'), + 'r' => result.push('\r'), + '\\' => result.push('\\'), + '"' => result.push('"'), + '\'' => result.push('\''), + _ => return Err(ParseError { message: format!("invalid escape sequence: \\{}", esc), span: Span::dummy() }), + } + } else { + return Err(ParseError { message: "unfinished escape sequence".to_string(), span: Span::dummy() }); + } + } else { + result.push(ch); + } + } + Ok(result) +} + +fn parse_list_comp_generators(tokens: &[Token], pos: &mut usize, mut gens: Vec<(String, Box)>, mut filters: Vec>) -> ParseResult<(Vec<(String, Box)>, Vec>)> { + while *pos < tokens.len() && tokens[*pos].kind != TokenKind::RBracket { + match tokens[*pos].kind { + TokenKind::For => { + *pos += 1; + let var = match &tokens[*pos].kind { + TokenKind::Ident(n) => n.clone(), + _ => return Err(ParseError { message: "expected loop variable".to_string(), span: Span::new(tokens[*pos].start, tokens[*pos].end) }), + }; + *pos += 1; + expect_kind(tokens, pos, TokenKind::In)?; + let iter = parse_expr(tokens, pos)?; + gens.push((var, Box::new(iter))); + } + TokenKind::If => { + *pos += 1; + let cond = parse_expr(tokens, pos)?; + filters.push(Box::new(cond)); + } + _ => break, + } + } + Ok((gens, filters)) +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..b96628a --- /dev/null +++ b/src/types.rs @@ -0,0 +1,236 @@ +use crate::ast::Expr; +use crate::error::ChatError; +use serde::{Serialize, Deserialize}; +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::fmt; +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub struct FetchOptions { + pub url: String, + pub method: String, + pub headers: Vec<(String, String)>, + pub body: Option, +} + +#[derive(Debug, Clone)] +pub struct FetchResult { + pub status: i64, + pub body: String, + pub headers: Vec<(String, String)>, +} + +#[derive(Clone)] +pub enum Value { + Num(f64), // unified number type + Char(char), + String(String), + Bool(bool), + Unit, + Uid(String), + ByteString(Vec), + List(Vec), + Tuple(Vec), + Closure(Vec, Expr, Environment), + BuiltinFunc(String, usize, Arc) -> Result + Send + Sync>), + Curry { + name: String, + arity: usize, + f: Arc) -> Result + Send + Sync>, + args: Vec, + }, + Custom(String, Vec), + Record(BTreeMap), + Pid(usize), + DateTime(chrono::DateTime), + Duration(std::time::Duration), + Json(JsonValue), + Maybe(Option>), + Either(Box), + ChatMsg { + from: String, + text: String, + chat: String, + attachment: Option>, + }, + FileInfo { + name: String, + size: i64, + mime: String, + }, + FileTransfer { + from: String, + filename: String, + data: Vec, + }, + FetchOptions(FetchOptions), + FetchResult(FetchResult), + Map(BTreeMap), + Set(BTreeSet), + ClassInstance { + class: String, + fields: BTreeMap, + }, + Break(Option>), +} + +#[derive(Clone, Serialize, Deserialize)] +pub enum JsonValue { + Null, + Bool(bool), + Number(f64), + String(String), + Array(Vec), + Object(BTreeMap), +} + +#[derive(Clone)] +pub struct Either { + pub left: Option>, + pub right: Option>, +} + +impl fmt::Debug for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.display()) + } +} + +impl Value { + pub fn display(&self) -> String { + match self { + Value::Num(x) => { + let s = format!("{:.10}", x); + if s == "-0.0000000000" { "0.0".to_string() } + else { + let trimmed = s.trim_end_matches('0').trim_end_matches('.'); + if trimmed.is_empty() { "0".to_string() } else { trimmed.to_string() } + } + } + Value::Char(c) => c.to_string(), + Value::String(s) => s.clone(), + Value::Bool(b) => b.to_string(), + Value::Unit => "()".to_string(), + Value::Uid(u) => u.clone(), + Value::ByteString(b) => format!("#B\"{}\"", hex::encode(b)), + Value::List(v) => { + let items: Vec = v.iter().map(|x| x.display()).collect(); + format!("[{}]", items.join(", ")) + } + Value::Tuple(v) => { + let items: Vec = v.iter().map(|x| x.display()).collect(); + format!("({})", items.join(", ")) + } + Value::Closure(_, _, _) => "".to_string(), + Value::BuiltinFunc(name, _, _) => format!("", name), + Value::Curry { name, .. } => format!("", name), + Value::Custom(name, args) => { + let args_str: Vec = args.iter().map(|x| x.display()).collect(); + if args.is_empty() { + name.clone() + } else { + format!("{} {}", name, args_str.join(" ")) + } + } + Value::Record(map) => { + let fields: Vec = map.iter() + .map(|(k, v)| format!("{} = {}", k, v.display())) + .collect(); + format!("{{ {} }}", fields.join(", ")) + } + Value::Pid(pid) => format!("", pid), + Value::DateTime(dt) => dt.to_rfc3339(), + Value::Duration(d) => format!("{}s", d.as_secs_f64()), + Value::Json(j) => serde_json::to_string_pretty(j).unwrap_or_default(), + Value::Maybe(m) => match m { + Some(v) => format!("Just {}", v.display()), + None => "Nothing".to_string(), + }, + Value::Either(e) => { + if let Some(left) = &e.left { + format!("Left {}", left.display()) + } else if let Some(right) = &e.right { + format!("Right {}", right.display()) + } else { + "Either".to_string() + } + } + Value::ChatMsg { from, text, chat, attachment } => { + let attach = if let Some(a) = attachment { + format!(" with {}", a.display()) + } else { "".to_string() }; + format!("[Message from {} in {}: \"{}\"{}]", from, chat, text, attach) + } + Value::FileInfo { name, size, mime } => { + format!("[FileInfo {} ({} bytes, {})]", name, size, mime) + } + Value::FileTransfer { from, filename, .. } => { + format!("[FileTransfer from {}: {}]", from, filename) + } + Value::FetchOptions(opts) => { + format!("FetchOptions {{ url: {}, method: {}, headers: {:?}, body: {:?} }}", + opts.url, opts.method, opts.headers, opts.body) + } + Value::FetchResult(res) => { + format!("FetchResult {{ status: {}, body: {}, headers: {:?} }}", + res.status, res.body, res.headers) + } + Value::Map(map) => { + let entries: Vec = map.iter() + .map(|(k, v)| format!("{}: {}", k, v.display())) + .collect(); + format!("%{{{}}}", entries.join(", ")) + } + Value::Set(set) => { + let elems: Vec = set.iter().cloned().collect(); + format!("%[{}]", elems.join(", ")) + } + Value::ClassInstance { class, fields } => { + let flds: Vec = fields.iter() + .map(|(k, v)| format!("{} = {}", k, v.display())) + .collect(); + format!("{} {{ {} }}", class, flds.join(", ")) + } + Value::Break(opt) => { + if let Some(v) = opt { + format!("break {}", v.display()) + } else { + "break".to_string() + } + } + } + } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.display()) + } +} + +#[derive(Debug, Clone)] +pub struct Environment { + pub vars: BTreeMap, + pub type_map: BTreeMap, +} + +impl Environment { + pub fn new() -> Self { + Environment { vars: BTreeMap::new(), type_map: BTreeMap::new() } + } + + pub fn get(&self, name: &str) -> Option { + self.vars.get(name).cloned() + } + + pub fn set(&mut self, name: String, val: Value) { + self.vars.insert(name, val); + } + + pub fn extend(&self, other: BTreeMap) -> Self { + let mut new = self.clone(); + new.vars.extend(other); + new + } +}