From fcdfb4488bc5d9815d6f7bfe97ae71621241933a Mon Sep 17 00:00:00 2001 From: Soispha Date: Sat, 23 Dec 2023 21:58:19 +0100 Subject: [PATCH] chore(trixy, keymaps): Extract the libraries to their own repository --- flake.nix | 1 + keymaps/.gitignore | 6 - keymaps/Cargo.toml | 20 - keymaps/src/error/mod.rs | 46 --- keymaps/src/key_repr/key/crossterm.rs | 76 ---- keymaps/src/key_repr/key/mod.rs | 70 ---- keymaps/src/key_repr/key/parsing/mod.rs | 213 ---------- keymaps/src/key_repr/key_representation.pest | 60 --- keymaps/src/key_repr/key_value/crossterm.rs | 70 ---- keymaps/src/key_repr/key_value/mod.rs | 123 ------ keymaps/src/key_repr/keys/mod.rs | 128 ------ keymaps/src/key_repr/mod.rs | 189 --------- keymaps/src/lib.rs | 3 - keymaps/src/trie.rs | 248 ------------ trixy/.gitignore | 6 - trixy/Cargo.toml | 13 - trixy/src/config/mod.rs | 103 ----- trixy/src/generate/command_enum/mod.rs | 103 ----- .../lua_functions_to_globals/mod.rs | 123 ------ trixy/src/generate/lua_wrapper/mod.rs | 22 -- .../lua_wrapper/rust_wrapper_functions/mod.rs | 231 ----------- trixy/src/generate/mod.rs | 56 --- trixy/src/lib.rs | 97 ----- trixy/trixy-lang_parser/.gitignore | 6 - trixy/trixy-lang_parser/Cargo.toml | 11 - trixy/trixy-lang_parser/README.md | 6 - trixy/trixy-lang_parser/docs/grammar.ebnf | 32 -- trixy/trixy-lang_parser/docs/grammar.pdf | Bin 67698 -> 0 bytes trixy/trixy-lang_parser/example/comments.tri | 12 - trixy/trixy-lang_parser/example/empty.tri | 0 trixy/trixy-lang_parser/example/failing.tri | 9 - .../example/failing_comments.tri | 13 - .../example/failing_types.tri | 10 - trixy/trixy-lang_parser/example/full.tri | 136 ------- trixy/trixy-lang_parser/example/multiple.tri | 13 - trixy/trixy-lang_parser/example/simple.tri | 9 - trixy/trixy-lang_parser/example/types.tri | 18 - trixy/trixy-lang_parser/generate_docs | 9 - .../src/command_spec/checked.rs | 154 -------- .../trixy-lang_parser/src/command_spec/mod.rs | 2 - .../src/command_spec/unchecked.rs | 127 ------ trixy/trixy-lang_parser/src/error.rs | 194 --------- trixy/trixy-lang_parser/src/lexing/error.rs | 77 ---- trixy/trixy-lang_parser/src/lexing/mod.rs | 307 --------------- trixy/trixy-lang_parser/src/lexing/test.rs | 194 --------- .../trixy-lang_parser/src/lexing/tokenizer.rs | 226 ----------- trixy/trixy-lang_parser/src/lib.rs | 18 - trixy/trixy-lang_parser/src/main.rs | 110 ------ .../src/parsing/checked/error.rs | 82 ---- .../src/parsing/checked/mod.rs | 261 ------------ .../src/parsing/checked/test.rs | 215 ---------- trixy/trixy-lang_parser/src/parsing/mod.rs | 2 - .../src/parsing/unchecked/error.rs | 113 ------ .../src/parsing/unchecked/mod.rs | 372 ------------------ .../src/parsing/unchecked/test.rs | 101 ----- update.sh | 4 +- 56 files changed, 2 insertions(+), 4848 deletions(-) delete mode 100644 keymaps/.gitignore delete mode 100644 keymaps/Cargo.toml delete mode 100644 keymaps/src/error/mod.rs delete mode 100644 keymaps/src/key_repr/key/crossterm.rs delete mode 100644 keymaps/src/key_repr/key/mod.rs delete mode 100644 keymaps/src/key_repr/key/parsing/mod.rs delete mode 100644 keymaps/src/key_repr/key_representation.pest delete mode 100644 keymaps/src/key_repr/key_value/crossterm.rs delete mode 100644 keymaps/src/key_repr/key_value/mod.rs delete mode 100644 keymaps/src/key_repr/keys/mod.rs delete mode 100644 keymaps/src/key_repr/mod.rs delete mode 100644 keymaps/src/lib.rs delete mode 100644 keymaps/src/trie.rs delete mode 100644 trixy/.gitignore delete mode 100644 trixy/Cargo.toml delete mode 100644 trixy/src/config/mod.rs delete mode 100644 trixy/src/generate/command_enum/mod.rs delete mode 100644 trixy/src/generate/lua_wrapper/lua_functions_to_globals/mod.rs delete mode 100644 trixy/src/generate/lua_wrapper/mod.rs delete mode 100644 trixy/src/generate/lua_wrapper/rust_wrapper_functions/mod.rs delete mode 100644 trixy/src/generate/mod.rs delete mode 100644 trixy/src/lib.rs delete mode 100644 trixy/trixy-lang_parser/.gitignore delete mode 100644 trixy/trixy-lang_parser/Cargo.toml delete mode 100644 trixy/trixy-lang_parser/README.md delete mode 100644 trixy/trixy-lang_parser/docs/grammar.ebnf delete mode 100644 trixy/trixy-lang_parser/docs/grammar.pdf delete mode 100644 trixy/trixy-lang_parser/example/comments.tri delete mode 100644 trixy/trixy-lang_parser/example/empty.tri delete mode 100644 trixy/trixy-lang_parser/example/failing.tri delete mode 100644 trixy/trixy-lang_parser/example/failing_comments.tri delete mode 100644 trixy/trixy-lang_parser/example/failing_types.tri delete mode 100644 trixy/trixy-lang_parser/example/full.tri delete mode 100644 trixy/trixy-lang_parser/example/multiple.tri delete mode 100644 trixy/trixy-lang_parser/example/simple.tri delete mode 100644 trixy/trixy-lang_parser/example/types.tri delete mode 100755 trixy/trixy-lang_parser/generate_docs delete mode 100644 trixy/trixy-lang_parser/src/command_spec/checked.rs delete mode 100644 trixy/trixy-lang_parser/src/command_spec/mod.rs delete mode 100644 trixy/trixy-lang_parser/src/command_spec/unchecked.rs delete mode 100644 trixy/trixy-lang_parser/src/error.rs delete mode 100644 trixy/trixy-lang_parser/src/lexing/error.rs delete mode 100644 trixy/trixy-lang_parser/src/lexing/mod.rs delete mode 100644 trixy/trixy-lang_parser/src/lexing/test.rs delete mode 100644 trixy/trixy-lang_parser/src/lexing/tokenizer.rs delete mode 100644 trixy/trixy-lang_parser/src/lib.rs delete mode 100644 trixy/trixy-lang_parser/src/main.rs delete mode 100644 trixy/trixy-lang_parser/src/parsing/checked/error.rs delete mode 100644 trixy/trixy-lang_parser/src/parsing/checked/mod.rs delete mode 100644 trixy/trixy-lang_parser/src/parsing/checked/test.rs delete mode 100644 trixy/trixy-lang_parser/src/parsing/mod.rs delete mode 100644 trixy/trixy-lang_parser/src/parsing/unchecked/error.rs delete mode 100644 trixy/trixy-lang_parser/src/parsing/unchecked/mod.rs delete mode 100644 trixy/trixy-lang_parser/src/parsing/unchecked/test.rs diff --git a/flake.nix b/flake.nix index 8151795..0b88674 100644 --- a/flake.nix +++ b/flake.nix @@ -9,6 +9,7 @@ url = "github:edolstra/flake-compat"; flake = false; }; + crane = { url = "github:ipetkov/crane"; inputs = { diff --git a/keymaps/.gitignore b/keymaps/.gitignore deleted file mode 100644 index 72fc7e3..0000000 --- a/keymaps/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# build -/target -/result - -# lua_macros is a library -Cargo.lock diff --git a/keymaps/Cargo.toml b/keymaps/Cargo.toml deleted file mode 100644 index 373cece..0000000 --- a/keymaps/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "keymaps" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[features] -default = [] -crossterm = ["dep:crossterm"] - -[dependencies] -crossterm = { version = "0.25", optional = true } -log = "0.4.20" -thiserror = "1.0.50" -pest = "2.7.5" -pest_derive = {version = "2.7.5", features = ["grammar-extras"]} - -[dev-dependencies] -pretty_assertions = "1.4.0" diff --git a/keymaps/src/error/mod.rs b/keymaps/src/error/mod.rs deleted file mode 100644 index a2baacb..0000000 --- a/keymaps/src/error/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::{fmt::Display, num::ParseIntError}; - -use thiserror::Error; - -use crate::key_repr::{key, keys, Keys}; - -#[derive(Error, Debug)] -pub enum TrieInsertError { - #[error("The key ('{0}') contains nodes, which already have a value set!")] - KeyPathBlocked(Keys), - - #[error("The key ('{key}') already has a value associatet with it, which is: '{value}'")] - KeyAlreadySet { key: Keys, value: V }, - - #[error("The node accessed by this key ('{0}') already has children! You can not set a value for it")] - NodeHasChildren(Keys), -} - -#[derive(Error, Debug)] -pub enum KeyParseError { - #[error("This key expression could not be parsed, see the error message for more: {0}")] - RawParseError(#[from] pest::error::Error), -} - -#[derive(Error, Debug)] -pub enum KeysParseError { - #[error( - "This chain of key expressions could not be parsed, see the error message for more: {0}" - )] - RawParseError(#[from] pest::error::Error), -} - -#[derive(Error, Debug)] -pub enum KeyValueParseError { - #[error("The String ('{0}') is not a correct special key name!")] - NoMatch(String), - - #[error("The number associate with the F key ('{0}') can't be parsed as u8!")] - CantParseFNumber(#[from] ParseIntError), -} - -#[derive(Error, Debug)] -pub enum KeyFromCrossterm { - #[error("Can not parse non Key event to a Key code: ('{0:?}')")] - OnlyKey(crossterm::event::Event), -} diff --git a/keymaps/src/key_repr/key/crossterm.rs b/keymaps/src/key_repr/key/crossterm.rs deleted file mode 100644 index d814d47..0000000 --- a/keymaps/src/key_repr/key/crossterm.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers}; - -use crate::{error, key_repr::KeyValue}; - -use super::Key; - -impl Into for Key { - fn into(self) -> Event { - let mut modifiers; - { - modifiers = KeyModifiers::all(); - if !self.alt { - modifiers.remove(KeyModifiers::ALT); - } - if !self.ctrl { - modifiers.remove(KeyModifiers::CONTROL); - } - if !self.meta { - modifiers.remove(KeyModifiers::META); - modifiers.remove(KeyModifiers::SUPER); - } - if !self.shift { - modifiers.remove(KeyModifiers::SHIFT); - } - modifiers.remove(KeyModifiers::HYPER); - modifiers.remove(KeyModifiers::META); - if self.alt || self.ctrl || self.meta || self.shift { - modifiers.remove(KeyModifiers::NONE); - } - } - - let output = Event::Key(KeyEvent { - code: { - if let Some(KeyValue::Char(char)) = self.value { - KeyCode::Char(char) - } else { - self.value.unwrap_or(KeyValue::Null).into() - } - }, - modifiers, - kind: KeyEventKind::Press, - state: KeyEventState::NONE, - }); - output - } -} - -impl TryFrom<&Event> for Key { - type Error = error::KeyFromCrossterm; - - fn try_from(value: &Event) -> std::result::Result { - let mut output_key: Key = Key::new(); - match value { - Event::Key(key_event) => { - { - let key_mods = key_event.modifiers; - - output_key.alt = KeyModifiers::intersects(&key_mods, KeyModifiers::ALT); - output_key.ctrl = KeyModifiers::intersects(&key_mods, KeyModifiers::CONTROL); - output_key.meta = KeyModifiers::intersects(&key_mods, KeyModifiers::META) - || KeyModifiers::intersects(&key_mods, KeyModifiers::SUPER); - output_key.shift = KeyModifiers::intersects(&key_mods, KeyModifiers::SHIFT); - // let hyper = KeyModifiers::intersects(&key_mods, KeyModifiers::HYPER); - // let none = KeyModifiers::intersects(&key_mods, KeyModifiers::NONE); - } - - { - output_key.value = Some(key_event.code.into()); - } - - Ok(output_key) - } - event => Err(error::KeyFromCrossterm::OnlyKey(event.to_owned())), - } - } -} diff --git a/keymaps/src/key_repr/key/mod.rs b/keymaps/src/key_repr/key/mod.rs deleted file mode 100644 index e1fae0a..0000000 --- a/keymaps/src/key_repr/key/mod.rs +++ /dev/null @@ -1,70 +0,0 @@ -#[cfg(feature = "crossterm")] -mod crossterm; - -mod parsing; - -use std::fmt::Display; - -use pest_derive::Parser; - -use super::KeyValue; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default, Parser)] -#[grammar = "./key_repr/key_representation.pest"] -pub struct Key { - // Modifiers - pub(super) alt: bool, - pub(super) ctrl: bool, - pub(super) meta: bool, - pub(super) shift: bool, - - pub(super) value: Option, -} - -impl Key { - pub fn new() -> Self { - Self::default() - } - - pub fn value(&self) -> Option<&KeyValue> { - self.value.as_ref() - } - - pub fn to_string_repr(self) -> String { - let mut output = String::new(); - if self.alt || self.ctrl || self.meta || self.shift { - output.push('<') - } - if self.alt { - output.push('A'); - } - if self.ctrl { - output.push('C'); - } - if self.meta { - output.push('M'); - } - if self.shift { - output.push('S'); - } - if self.alt || self.ctrl || self.meta || self.shift { - output.push('-') - } - output.push_str( - &self - .value - .expect("There can be no `None`s here, if the Key comes from the public api") - .to_string(), - ); - if self.alt || self.ctrl || self.meta || self.shift { - output.push('>') - } - output - } -} - -impl Display for Key { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.to_string_repr()) - } -} diff --git a/keymaps/src/key_repr/key/parsing/mod.rs b/keymaps/src/key_repr/key/parsing/mod.rs deleted file mode 100644 index 5d6b14a..0000000 --- a/keymaps/src/key_repr/key/parsing/mod.rs +++ /dev/null @@ -1,213 +0,0 @@ -use std::{fmt::Debug, hash::Hash, marker, str::FromStr}; - -use pest::{iterators::Pair, Parser}; - -use crate::{error, key_repr::{KeyValue, key::Rule}}; - -use super::Key; - -impl Key { - pub fn from_pair(pair: Pair) -> Self - where - R: marker::Copy + Debug + Hash + Ord, - { - let pairs = pair.into_inner(); - let mut output = Self::default(); - - let modifiers: Vec<_> = pairs.clone().find_tagged("modifiers").collect(); - let values: Vec<_> = pairs.clone().find_tagged("value").collect(); - let special_keys: Vec<_> = pairs.find_tagged("special_key").collect(); - - for modifier in modifiers { - match modifier.as_span().as_str() { - "A" => output.alt = true, - "C" => output.ctrl = true, - "M" => output.meta = true, - "S" => output.shift = true, - _ => unreachable!("This modifier should not have been parsed"), - } - } - - if values.is_empty() { - // Check that we have parsed *something* - debug_assert!(!special_keys.is_empty()); - // Check, that we have only parsed one special key - debug_assert_eq!(special_keys.len(), 1); - - let special_key_value: KeyValue = special_keys - .first() - .expect("This should exist (and be the only value)") - .as_str() - .parse() - .expect("This was parsed, and should thus be a valid special_key"); - output.value = Some(special_key_value); - } else { - // Check, that we have only parsed one key - debug_assert_eq!(values.len(), 1); - - let key_str = values.first().expect("This should exist").as_str(); - // Check that we really only have a one char &str - debug_assert_eq!(key_str.chars().count(), 1); - - let key_value: KeyValue = KeyValue::Char( - key_str - .chars() - .nth(0) - .expect("This &str should have a length of exactly one char"), - ); - - output.value = Some(key_value); - } - output - } - // pub(super) fn parse_old(chars: &mut Chars) -> anyhow::Result { - // assert_eq!(chars.pop().expect("This is a developer error"), '<'); - // let mut parse_buffer: Vec = Vec::new(); - // - // let mut reached_non_modifier = false; - // let mut output_key_filled = false; - // let mut output_key = Key::new(); - // while let Some(char) = chars.pop() { - // if char == '>' { - // break; - // } else { - // if char.is_ascii_uppercase() - // || char.is_numeric() && !reached_non_modifier && !output_key_filled - // { - // parse_buffer.push(char); - // } else if char == '-' && !reached_non_modifier && !output_key_filled { - // // We moved to the modified char - // reached_non_modifier = true; - // - // // Our parse_buffer should only contain modifiers: - // let mut alt = false; - // let mut ctrl = false; - // let mut meta = false; - // let mut shift = false; - // - // for char in &parse_buffer { - // match char { - // 'A' => alt = true, - // 'C' => ctrl = true, - // 'M' => meta = true, - // 'S' => shift = true, - // char => bail!( - // "The char ('{}') is not a valid descriptor of a modifier", - // char - // ), - // } - // } - // output_key = Key { - // alt, - // ctrl, - // meta, - // shift, - // value: None, - // }; - // } else if reached_non_modifier && !output_key_filled { - // if char == '<' { - // chars.prepend('<'); - // let key = Key::parse(chars)?; - // output_key = output_key.merge_with(key); - // } else { - // output_key.value = Some(KeyValue::Char(char)); - // } - // output_key_filled = true; - // } else { - // bail!( - // "Your can not put a this char here! - // parse_buffer: '{}'; - // char: '{}'; - // chars: '{:#?}'; - // output_key: '{:#?}' ", - // &parse_buffer.iter().collect::(), - // &char, - // &chars, - // &output_key - // ) - // } - // } - // } - // if output_key_filled { - // Ok(output_key) - // } else { - // let mut parse_buffer = Chars(parse_buffer.into()); - // let get_output = |value: KeyValue| -> Key { - // let key = Key { - // alt: false, - // ctrl: false, - // meta: false, - // shift: false, - // value: Some(value), - // }; - // - // return key.merge_with(output_key); - // }; - // if let Some(char) = parse_buffer.peek() { - // if char == &'F' { - // let _f = parse_buffer.pop(); - // let number: u8 = parse_buffer.to_string().parse().with_context(|| { - // format!("Failed to parse buffer ('{}') as u8", &parse_buffer) - // })?; - // Ok(get_output(KeyValue::F(number))) - // } else { - // match &parse_buffer.to_string()[..] { - // "BACKSPACE" => Ok(get_output(KeyValue::Backspace)), - // "ENTER" => Ok(get_output(KeyValue::Enter)), - // "LEFT" => Ok(get_output(KeyValue::Left)), - // "RIGHT" => Ok(get_output(KeyValue::Right)), - // "UP" => Ok(get_output(KeyValue::Up)), - // "DOWN" => Ok(get_output(KeyValue::Down)), - // "HOME" => Ok(get_output(KeyValue::Home)), - // "END" => Ok(get_output(KeyValue::End)), - // "PAGEUP" => Ok(get_output(KeyValue::PageUp)), - // "PAGEDOWN" => Ok(get_output(KeyValue::PageDown)), - // "TAB" => Ok(get_output(KeyValue::Tab)), - // "BACKTAB" => Ok(get_output(KeyValue::BackTab)), - // "DELETE" => Ok(get_output(KeyValue::Delete)), - // "INSERT" => Ok(get_output(KeyValue::Insert)), - // "ESC" => Ok(get_output(KeyValue::Esc)), - // "CAPSLOCK" => Ok(get_output(KeyValue::CapsLock)), - // "SCROLLlOCK" => Ok(get_output(KeyValue::ScrollLock)), - // "NUMLOCK" => Ok(get_output(KeyValue::NumLock)), - // "PRINTSCREEN" => Ok(get_output(KeyValue::PrintScreen)), - // "PAUSE" => Ok(get_output(KeyValue::Pause)), - // "MENU" => Ok(get_output(KeyValue::Menu)), - // "KEYPADBEGIN" => Ok(get_output(KeyValue::KeypadBegin)), - // - // "DASH" => Ok(get_output(KeyValue::Char('-'))), - // "ANGULAR_BRACKET_OPEN" | "ABO" => Ok(get_output(KeyValue::Char('<'))), - // "ANGULAR_BRACKET_CLOSE" | "ABC" => Ok(get_output(KeyValue::Char('>'))), - // other_str => bail!( - // "The String ('{}') is not a correct special key name!", - // other_str - // ), - // } - // } - // } else { - // bail!( - // "You need to put something into the angulare brackets (<>) - // parse_buffer: '{}'; - // chars: '{:#?}';", - // &parse_buffer, - // &chars, - // ) - // } - // } - // } -} - -impl FromStr for Key { - type Err = error::KeyParseError; - - fn from_str(s: &str) -> Result { - let mut key_pairs: Vec<_> = Self::parse(Rule::key, s)?.find_tagged("key").collect(); - debug_assert_eq!(key_pairs.len(), 1); - let key: Key = Self::from_pair::( - key_pairs - .pop() - .expect("This should contain only this first (and last) element"), - ); - Ok(key) - } -} diff --git a/keymaps/src/key_repr/key_representation.pest b/keymaps/src/key_repr/key_representation.pest deleted file mode 100644 index 6527bde..0000000 --- a/keymaps/src/key_repr/key_representation.pest +++ /dev/null @@ -1,60 +0,0 @@ -angular_bracket_open = _{ "<" } -abo = _{ angular_bracket_open } -angular_bracket_close = _{ ">" } -abc = _{ angular_bracket_close } - -special_key = { - "BACKSPACE" - | "ENTER" - | "LEFT" - | "RIGHT" - | "UP" - | "DOWN" - | "HOME" - | "END" - | "PAGEUP" - | "PAGEDOWN" - | "TAB" - | "BACKTAB" - | "DELETE" - | "INSERT" - | "ESC" - | "CAPSLOCK" - | "SCROLLlOCK" - | "NUMLOCK" - | "PRINTSCREEN" - | "PAUSE" - | "MENU" - | "KEYPADBEGIN" - | ("F" ~ ASCII_DIGIT+) - - // Aliases to make using them easier, as they are also used in the notation - | "DASH" - | "ANGULAR_BRACKET_OPEN" - | "ABO" - | "ANGULAR_BRACKET_CLOSE" - | "ABC" -} - -modifier = { - "A" | // Alt - "C" | // Ctrl - "M" | // Meta - "S" // Shift -} - -value = { - !abo ~ ANY -} - -raw_key = { - ((abo ~ (#modifiers = modifier)+ ~ "-" ~ (#special_key = (abo ~ special_key ~ abc) | #value = value) ~ abc) | #special_key = (abo ~ special_key ~ abc) | #value = value) -} - -// api -key = { - SOI ~ #key = raw_key ~ EOI -} -keys = { - SOI ~ (#keys = raw_key)+ ~ EOI -} diff --git a/keymaps/src/key_repr/key_value/crossterm.rs b/keymaps/src/key_repr/key_value/crossterm.rs deleted file mode 100644 index 178e860..0000000 --- a/keymaps/src/key_repr/key_value/crossterm.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crossterm::event::KeyCode; - -use super::KeyValue; - -impl From for KeyValue { - fn from(value: KeyCode) -> Self { - match value { - KeyCode::Backspace => Self::Backspace, - KeyCode::Enter => Self::Enter, - KeyCode::Left => Self::Left, - KeyCode::Right => Self::Right, - KeyCode::Up => Self::Up, - KeyCode::Down => Self::Down, - KeyCode::Home => Self::Home, - KeyCode::End => Self::End, - KeyCode::PageUp => Self::PageUp, - KeyCode::PageDown => Self::PageDown, - KeyCode::Tab => Self::Tab, - KeyCode::BackTab => Self::BackTab, - KeyCode::Delete => Self::Delete, - KeyCode::Insert => Self::Insert, - KeyCode::F(n) => Self::F(n), - KeyCode::Char(c) => Self::Char(c), - KeyCode::Null => Self::Null, - KeyCode::Esc => Self::Esc, - KeyCode::CapsLock => Self::CapsLock, - KeyCode::ScrollLock => Self::ScrollLock, - KeyCode::NumLock => Self::NumLock, - KeyCode::PrintScreen => Self::PrintScreen, - KeyCode::Pause => Self::Pause, - KeyCode::Menu => Self::Menu, - KeyCode::KeypadBegin => Self::KeypadBegin, - // FIXME(@soispha): This reduces our information, casting a KeyCode to a KeyValue - // and back again would not equal the original KeyCode <2023-10-15> - KeyCode::Media(_) => Self::Null, - KeyCode::Modifier(_) => Self::Null, - } - } -} -impl Into for KeyValue { - fn into(self) -> KeyCode { - match self { - Self::Backspace => KeyCode::Backspace, - Self::Enter => KeyCode::Enter, - Self::Left => KeyCode::Left, - Self::Right => KeyCode::Right, - Self::Up => KeyCode::Up, - Self::Down => KeyCode::Down, - Self::Home => KeyCode::Home, - Self::End => KeyCode::End, - Self::PageUp => KeyCode::PageUp, - Self::PageDown => KeyCode::PageDown, - Self::Tab => KeyCode::Tab, - Self::BackTab => KeyCode::BackTab, - Self::Delete => KeyCode::Delete, - Self::Insert => KeyCode::Insert, - Self::F(n) => KeyCode::F(n), - Self::Char(c) => KeyCode::Char(c), - Self::Null => KeyCode::Null, - Self::Esc => KeyCode::Esc, - Self::CapsLock => KeyCode::CapsLock, - Self::ScrollLock => KeyCode::ScrollLock, - Self::NumLock => KeyCode::NumLock, - Self::PrintScreen => KeyCode::PrintScreen, - Self::Pause => KeyCode::Pause, - Self::Menu => KeyCode::Menu, - Self::KeypadBegin => KeyCode::KeypadBegin, - } - } -} diff --git a/keymaps/src/key_repr/key_value/mod.rs b/keymaps/src/key_repr/key_value/mod.rs deleted file mode 100644 index 61240d4..0000000 --- a/keymaps/src/key_repr/key_value/mod.rs +++ /dev/null @@ -1,123 +0,0 @@ -#[cfg(feature = "crossterm")] -mod crossterm; - -use std::{ - fmt::{Display, Write}, - str::FromStr, -}; - -use crate::error; - -// taken directly from crossterm -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum KeyValue { - Backspace, - Enter, - Left, - Right, - Up, - Down, - Home, - End, - PageUp, - PageDown, - Tab, - BackTab, - Delete, - Insert, - F(u8), - Char(char), - Null, // TODO(@soispha): Should we keep this key (as it's effectively not a key)?<2023-10-15> - Esc, - CapsLock, - ScrollLock, - NumLock, - PrintScreen, - Pause, - Menu, - KeypadBegin, - // TODO(@soispha): We could add support for these: <2023-10-15> - // Media(MediaKeyCode), -} - -impl FromStr for KeyValue { - type Err = error::KeyValueParseError; - - fn from_str(s: &str) -> Result { - match s { - "BACKSPACE" => Ok(Self::Backspace), - "ENTER" => Ok(Self::Enter), - "LEFT" => Ok(Self::Left), - "RIGHT" => Ok(Self::Right), - "UP" => Ok(Self::Up), - "DOWN" => Ok(Self::Down), - "HOME" => Ok(Self::Home), - "END" => Ok(Self::End), - "PAGEUP" => Ok(Self::PageUp), - "PAGEDOWN" => Ok(Self::PageDown), - "TAB" => Ok(Self::Tab), - "BACKTAB" => Ok(Self::BackTab), - "DELETE" => Ok(Self::Delete), - "INSERT" => Ok(Self::Insert), - "ESC" => Ok(Self::Esc), - "CAPSLOCK" => Ok(Self::CapsLock), - "SCROLLlOCK" => Ok(Self::ScrollLock), - "NUMLOCK" => Ok(Self::NumLock), - "PRINTSCREEN" => Ok(Self::PrintScreen), - "PAUSE" => Ok(Self::Pause), - "MENU" => Ok(Self::Menu), - "KEYPADBEGIN" => Ok(Self::KeypadBegin), - f_str - // FIXME(@soispha): This should check the full string, as something - // like 'FA12' would also match this case <2023-11-07> - if f_str.starts_with('F') - && f_str.ends_with(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']) => - { - Ok(Self::F(f_str.trim_matches('F').parse()?)) - } - - "DASH" => Ok(Self::Char('-')), - "ANGULAR_BRACKET_OPEN" | "ABO" => Ok(Self::Char('<')), - "ANGULAR_BRACKET_CLOSE" | "ABC" => Ok(Self::Char('>')), - other_str => Err(Self::Err::NoMatch(other_str.to_owned())), - } - } -} - -impl Display for KeyValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut w = |str| f.write_str(str); - match self { - KeyValue::Backspace => w(""), - KeyValue::Enter => w(""), - KeyValue::Left => w(""), - KeyValue::Right => w(""), - KeyValue::Up => w(""), - KeyValue::Down => w(""), - KeyValue::Home => w(""), - KeyValue::End => w(""), - KeyValue::PageUp => w(""), - KeyValue::PageDown => w(""), - KeyValue::Tab => w(""), - KeyValue::BackTab => w(""), - KeyValue::Delete => w(""), - KeyValue::Insert => w(""), - KeyValue::F(n) => f.write_fmt(format_args!("", n)), - KeyValue::Char(c) => match c { - '<' => w(""), - '>' => w(""), - '-' => w(""), - c => f.write_char(*c), - }, - KeyValue::Null => w(""), - KeyValue::Esc => w(""), - KeyValue::CapsLock => w(""), - KeyValue::ScrollLock => w(""), - KeyValue::NumLock => w(""), - KeyValue::PrintScreen => w(""), - KeyValue::Pause => w(""), - KeyValue::Menu => w(""), - KeyValue::KeypadBegin => w(""), - } - } -} diff --git a/keymaps/src/key_repr/keys/mod.rs b/keymaps/src/key_repr/keys/mod.rs deleted file mode 100644 index fc645b7..0000000 --- a/keymaps/src/key_repr/keys/mod.rs +++ /dev/null @@ -1,128 +0,0 @@ -use pest::Parser; -use pest_derive::Parser; - -use crate::error; - -use super::Key; - -use std::{fmt::Display, str::FromStr}; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, Parser)] -#[grammar = "./key_repr/key_representation.pest"] -pub struct Keys(pub(super) Vec); - -impl Keys { - pub fn new(initial_value: Key) -> Self { - Keys(vec![initial_value]) - } - pub fn join(&self, key: Key) -> Keys { - let mut output = self.0.clone(); - output.push(key); - Keys(output) - } -} - -pub struct KeysIter { - keys: Keys, - index: usize, -} - -pub struct KeysIterB<'a> { - keys: &'a Keys, - index: usize, -} - -impl IntoIterator for Keys { - type Item = Key; - type IntoIter = KeysIter; - - fn into_iter(self) -> Self::IntoIter { - KeysIter { - keys: self, - index: 0, - } - } -} - -impl Iterator for KeysIter { - type Item = Key; - - fn next(&mut self) -> Option { - let output = if self.keys.0.len() <= self.index { - None - } else { - Some(self.keys.0[self.index]) - }; - self.index += 1; - output - } -} - -impl<'a> IntoIterator for &'a Keys { - type Item = &'a Key; - type IntoIter = KeysIterB<'a>; - - fn into_iter(self) -> Self::IntoIter { - KeysIterB { - keys: self, - index: 0, - } - } -} - -impl<'a> Iterator for KeysIterB<'a> { - type Item = &'a Key; - - fn next(&mut self) -> Option { - let output = self.keys.0.get(self.index); - self.index += 1; - output - } -} - -impl Display for Keys { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str( - &self - .into_iter() - .map(|key| key.to_string_repr()) - .collect::(), - ) - } -} - -impl FromStr for Keys { - type Err = error::KeysParseError; - - fn from_str(s: &str) -> Result { - let mut output: Vec = vec![]; - let parsed = Self::parse(Rule::keys, s)?; - - let keys: Vec<_> = parsed.find_tagged("keys").collect(); - - for key in keys { - let output_key: Key = Key::from_pair(key); - output.push(output_key) - } - - // let mut chars = Chars(s.chars().collect()); - // while let Some(char) = chars.pop() { - // match char { - // '<' => { - // chars.prepend('<'); - // let key = Key::parse(&mut chars) - // .with_context(|| format!("Failed to parse keymapping ('{}')", &chars))?; - // output.push(key) - // } - // other_char => output.push(Key { - // alt: false, - // ctrl: false, - // meta: false, - // shift: false, - // value: Some(KeyValue::Char(other_char)), - // }), - // } - // } - Ok(Keys(output)) - } -} diff --git a/keymaps/src/key_repr/mod.rs b/keymaps/src/key_repr/mod.rs deleted file mode 100644 index 925038e..0000000 --- a/keymaps/src/key_repr/mod.rs +++ /dev/null @@ -1,189 +0,0 @@ -pub mod key; -pub mod key_value; -pub mod keys; - -pub use key::Key; -pub use key_value::*; -pub use keys::Keys; - -#[cfg(test)] -mod test { - use crate::key_repr::{Key, KeyValue}; - use pretty_assertions::assert_eq; - - use super::Keys; - - // "ba" => "Ctrl+a" && "b" && "a" - // "" => "A" || "Shift+a" - // "A" => "A" - // " " => "Alt+a" || "Super+a" - // "a" => "a" && "Ctrl+b" && "Ctrl+a" - // "" => "Ctrl+Shift+Alt+b" - // "a " => "a" && " " - // "å🙂" => "å" && "🙂" - // "" => escape key - // "" => backspace key (and so forth) - // "" => "-" - // "" || "" => "<" - // "" || "" => ">" - #[test] - fn test_simple() { - let keys: Keys = "".parse().unwrap(); - assert_eq!( - keys, - Keys(vec![Key { - alt: false, - ctrl: true, - meta: false, - shift: false, - value: Some(KeyValue::Char('a')) - }]) - ) - } - - #[test] - fn test_shift_a() { - let keys: Keys = "".parse().unwrap(); - assert_eq!( - keys, - Keys(vec![Key { - alt: false, - ctrl: false, - meta: false, - shift: true, - value: Some(KeyValue::Char('a')) - }]) - ) - } - - #[test] - fn test_string_repr() { - let key = Key { - alt: true, - ctrl: false, - meta: true, - shift: true, - value: Some(KeyValue::Up), - }; - assert_eq!(">".to_owned(), key.to_string_repr()); - } - #[test] - fn test_string_repr_special() { - let key = Key { - alt: true, - ctrl: false, - meta: true, - shift: true, - value: Some(KeyValue::Char('<')), - }; - assert_eq!( - ">".to_owned(), - key.to_string_repr() - ); - } - - #[test] - fn test_extra_special_keys() { - // The part works! Although we should probably not encourage it - let keys: Keys = ">>".parse().unwrap(); - assert_eq!( - keys, - Keys(vec![ - Key { - alt: false, - ctrl: true, - meta: false, - shift: false, - value: Some(KeyValue::Char('-')) - }, - Key { - alt: false, - ctrl: true, - meta: false, - shift: false, - value: Some(KeyValue::Char('-')) - }, - Key { - alt: true, - ctrl: false, - meta: false, - shift: false, - value: Some(KeyValue::Char('<')) - }, - Key { - alt: false, - ctrl: false, - meta: false, - shift: false, - value: Some(KeyValue::Char('>')) - }, - ]) - ) - } - - #[test] - fn test_false_pattern() { - let keys: Result = "".parse(); - assert!(keys.is_err()) - } - - #[test] - fn test_f_keys() { - let key: Key = ">".parse().unwrap(); - let expected = Key { - alt: false, - ctrl: true, - meta: false, - shift: false, - value: Some(KeyValue::F(255)), - }; - assert_eq!(key, expected); - } - - #[test] - fn test_complex() { - let keys: Keys = ">a 🙂" - .parse() - .unwrap_or_else(|e| panic!("{}", e)); - assert_eq!( - keys, - Keys(vec![ - Key { - alt: false, - ctrl: true, - meta: false, - shift: false, - value: Some(KeyValue::Char('a')) - }, - Key { - alt: true, - ctrl: true, - meta: true, - shift: true, - value: Some(KeyValue::Esc) - }, - Key { - alt: false, - ctrl: false, - meta: false, - shift: false, - value: Some(KeyValue::Char('a')) - }, - Key { - alt: false, - ctrl: false, - meta: false, - shift: false, - value: Some(KeyValue::Char(' ')) - }, - Key { - alt: false, - ctrl: false, - meta: false, - shift: false, - value: Some(KeyValue::Char('🙂')) - }, - ]) - ) - } -} diff --git a/keymaps/src/lib.rs b/keymaps/src/lib.rs deleted file mode 100644 index ab1be33..0000000 --- a/keymaps/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod key_repr; -pub mod trie; -pub mod error; diff --git a/keymaps/src/trie.rs b/keymaps/src/trie.rs deleted file mode 100644 index 87b4671..0000000 --- a/keymaps/src/trie.rs +++ /dev/null @@ -1,248 +0,0 @@ -use std::collections::HashMap; - -use log::info; - -use crate::error; - -use super::key_repr::{Key, Keys}; - -#[derive(Debug, PartialEq, Eq)] -pub struct Node> { - children: HashMap>>, - value: Option, - is_terminal: bool, - is_child: bool, -} - -impl> Default for Node { - fn default() -> Self { - Self::new() - } -} - -impl> Node { - pub fn new() -> Node { - Node { - children: HashMap::new(), - is_terminal: true, - is_child: false, - value: None, - } - } - pub fn new_child() -> Node { - Node { - children: HashMap::new(), - is_terminal: true, - is_child: true, - value: None, - } - } - - pub fn is_terminal(&self) -> bool { - self.is_terminal - } - pub fn is_child(&self) -> bool { - self.is_child - } - - /// Get a reference to a child value of this node by key, will return None if the key does not exist. - /// If the key does exists, but does not have any values associated with it, it will return an - /// empty vector - /// The boolean denotes if the returned node is a true end or just a waypoint. It should be - /// called, when the bool is true - pub fn get(&self, keys: &Keys) -> Option<(Vec<&Node>, bool)> { - let mut current_node = self; - let mut old_node = None; - for char in keys { - // r - // | - // a - // / \ - // b a - // | - // c - // - // - // - // r->a->a: Some([a]) -> a.is_child() && a.is_terminal() ? call a : *key input pending* - // r->a->b: Some([b]) -> b.is_child() && b.is_terminal() ? *call b* : key input pending - // r->a->c: None -> continue - // r->a->a->c: Some([c]) -> c.is_child() && c.is_terminal() ? *call c* : key input pending - // - // r->a: Some([a, b]) -> key input pending - if let Some(node) = current_node.children.get(char) { - old_node = Some((current_node, char)); - current_node = node; - } else { - return None; - } - } - if current_node.is_child() && current_node.is_terminal() { - let (on, char) = old_node.expect("At this point, this should be Some"); - info!("Returning calling node for char: '{}'", char); - Some(( - vec![on - .children - .get(char) - .expect("This should be some, as this was checked above")], - true, - )) - } else { - Some(( - current_node.children.values().map(|a| a.as_ref()).collect(), - false, - )) - } - } - - /// Insert a key value pair into the trie. The key is supplied as a string to facilitate the - /// creation of nested nodes. - pub fn insert(&mut self, keys: &Keys, value: V) -> Result<(), error::TrieInsertError> { - let mut current_node = self; - for char in keys { - let child_node = current_node - .children - .entry(char.to_owned()) - .or_insert(Box::new(Node::new_child())); - if current_node.value.is_some() { - return Err(error::TrieInsertError::KeyPathBlocked(keys.to_owned())); - } - current_node.is_terminal = false; - current_node = child_node - } - if current_node.value.is_some() { - return Err(error::TrieInsertError::KeyAlreadySet { - key: keys.to_owned(), - value: current_node - .value - .as_ref() - .expect("This should be set") - .to_owned(), - }); - } else if !current_node.children.is_empty() { - return Err(error::TrieInsertError::NodeHasChildren(keys.to_owned())); - } else { - current_node.value = Some(value); - Ok(()) - } - } - - /// Return the values from this node. If the node does not have a value associated with it, - /// return None - pub fn value(&self) -> Option<&V> { - self.value.as_ref() - } - - /// Collect all values from this nodes children. Can be called recursively as it should be - /// tail-recursive. - pub fn collect_values_all(&self) -> Vec<&V> { - if self.is_terminal && self.is_child { - vec![self.value.as_ref().expect("We checked above")] - } else { - let out: Vec<_> = self - .children - .values() - .flat_map(|node| node.collect_values_all()) - .collect(); - out - } - } -} - -#[cfg(test)] -mod test { - use crate::key_repr::{Key, Keys}; - use pretty_assertions::assert_eq; - - use super::Node; - - fn i(str: &str) -> Keys { - str.parse().unwrap() - } - fn k(str: &str) -> Key { - str.parse::().unwrap() - } - fn collect>( - nodes: Option<(Vec<&Node>, bool)>, - ) -> Vec<&V> { - let (nodes, _should_call) = nodes.unwrap(); - nodes - .iter() - .map(|node| node.value()) - .filter_map(|x| x) - .collect() - } - - #[test] - fn test_empty_values() { - let trie: Node = Node::new(); - assert_eq!(trie.collect_values_all(), Vec::<&bool>::new()); - } - - #[test] - fn test_insert() { - let mut trie: Node = Node::new(); - - trie.insert(&i("abc"), true).unwrap(); - trie.insert(&i("abd"), true).unwrap(); - trie.insert(&i("aca"), false).unwrap(); - - let output: Vec<&bool> = vec![&true, &true]; - let get_output: Vec<_> = collect(trie.get(&i("ab"))); - assert_eq!(get_output, output); - } - - #[test] - fn test_duplicate_insert() { - let mut trie: Node = Node::new(); - - trie.insert(&i("abc"), true).unwrap(); - trie.insert(&i("aca"), true).unwrap(); - let output = trie.insert(&i("ab"), false); - - dbg!(&trie); - assert!(output.is_err()); - } - - #[test] - fn test_multiple_get() { - // | | | | | - // a <- Input 1st a | q | i - // / \ | | - // b a <- Input 2nd a | | - // / \ | | - // d e - let mut trie: Node = Node::new(); - let mut output: Node = Node::new(); - - trie.insert(&i("aa"), true).unwrap(); - trie.insert(&i("abe"), true).unwrap(); - trie.insert(&i("abd"), true).unwrap(); - - output.insert(&i("abd"), true).unwrap(); - output.insert(&i("abe"), true).unwrap(); - output.insert(&i("aa"), true).unwrap(); - output.insert(&i("acd"), true).unwrap(); - output.insert(&i("ace"), true).unwrap(); - - let a_children = &output.children.get(&k("a")).unwrap(); - let output_nodes = vec![ - a_children.children[&k("a")].as_ref(), - a_children.children[&k("b")].as_ref(), - ]; - - let (nodes, _should_call) = trie.get(&i("a")).unwrap(); - assert_eq!(nodes, output_nodes); - } - - #[test] - fn test_wrong_get() { - let mut trie: Node = Node::new(); - - trie.insert(&i("abc"), true).unwrap(); - trie.insert(&i("abd"), true).unwrap(); - trie.insert(&i("aca"), false).unwrap(); - - assert!(trie.get(&i("bb")).is_none()); - } -} diff --git a/trixy/.gitignore b/trixy/.gitignore deleted file mode 100644 index 72fc7e3..0000000 --- a/trixy/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# build -/target -/result - -# lua_macros is a library -Cargo.lock diff --git a/trixy/Cargo.toml b/trixy/Cargo.toml deleted file mode 100644 index af5db2d..0000000 --- a/trixy/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "trixy" -version = "0.1.0" -edition = "2021" - -[lib] -proc-macro = true - -[dependencies] -convert_case = "0.6.0" -proc-macro2 = "1.0.70" -quote = "1.0.33" -syn = { version = "2.0.41", features = ["extra-traits", "full", "parsing"] } diff --git a/trixy/src/config/mod.rs b/trixy/src/config/mod.rs deleted file mode 100644 index 5899d6e..0000000 --- a/trixy/src/config/mod.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! This module is responsible for parsing the config passed to the macro call: -//! For example: -//! ```no_run -//! trixy_generate! { -//! path: ./trintrix_command_interface.tri -//! languages: rust, lua, c -//! } -//! ``` - -use std::path::PathBuf; - -use proc_macro2::Ident; -use syn::{parse::Parse, punctuated::Punctuated, LitStr, Result, Token}; - -mod kw { - syn::custom_keyword!(path); - syn::custom_keyword!(languages); -} - -#[derive(Debug)] -pub enum Language { - Rust, - Lua, - C, -} - -#[derive(Debug)] -struct Languages { - #[allow(dead_code)] - languages: kw::languages, - #[allow(dead_code)] - colon: Token![:], - raw: Punctuated, -} - -#[derive(Debug)] -struct Path { - #[allow(dead_code)] - path: kw::path, - #[allow(dead_code)] - colon: Token![:], - raw: PathBuf, -} - -#[derive(Debug)] -pub struct TrixyConfig { - /// The Path to the base command interface config file - path: Path, - - /// The languages the commands should be exposed in - languages: Languages, -} -impl TrixyConfig { - pub fn get_path(&self) -> PathBuf { - self.path.raw - } -} - -impl Parse for TrixyConfig { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - Ok(Self { - path: input.parse()?, - languages: input.parse()?, - }) - } -} - -impl Parse for Path { - fn parse(input: syn::parse::ParseStream) -> Result { - let path: kw::path = input.parse()?; - let colon: Token![:] = input.parse()?; - let raw = PathBuf::from(input.parse::()?.value()); - Ok(Self { path, colon, raw }) - } -} - -impl Parse for Languages { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let languages: kw::languages = input.parse()?; - let colon: Token![:] = input.parse()?; - let raw = Punctuated::::parse_separated_nonempty(input)?; - Ok(Self { - languages, - colon, - raw, - }) - } -} - -impl Parse for Language { - fn parse(input: syn::parse::ParseStream) -> Result { - let ident: Ident = input.parse()?; - match &ident.to_string()[..] { - "rust" | "Rust" => Ok(Self::Rust), - "lua" | "Lua" => Ok(Self::Lua), - "c" | "C" => Ok(Self::C), - other => Err(input.error(format!( - "The language: `{}` is not a registered language!", - other - ))), - } - } -} diff --git a/trixy/src/generate/command_enum/mod.rs b/trixy/src/generate/command_enum/mod.rs deleted file mode 100644 index 61f0b7b..0000000 --- a/trixy/src/generate/command_enum/mod.rs +++ /dev/null @@ -1,103 +0,0 @@ -use convert_case::{Case, Casing}; -use proc_macro2::TokenStream as TokenStream2; -use quote::{format_ident, quote, ToTokens}; -use syn::{punctuated::Punctuated, Ident, Token, Type}; - -use crate::{DataCommandEnum, command_enum_parsing::Field}; - -use super::get_input_type_of_bare_fn_field; - -pub fn command_enum(input: &DataCommandEnum) -> TokenStream2 { - let (fields, namespace_enums): (TokenStream2, TokenStream2) = - turn_fields_to_enum(&input.fields); - - quote! { - #[derive(Debug)] - pub enum Command { - #fields - } - #namespace_enums - } -} - -fn turn_fields_to_enum(fields: &Punctuated) -> (TokenStream2, TokenStream2) { - let output: Vec<_> = fields - .iter() - .map(|field| turn_struct_field_to_enum(field)) - .collect(); - - let mut fields_output: TokenStream2 = Default::default(); - let mut namespace_enums_output: TokenStream2 = Default::default(); - - for (fields, namespace_enum) in output { - fields_output.extend(fields.to_token_stream()); - namespace_enums_output.extend(namespace_enum.to_token_stream()); - } - - (fields_output, namespace_enums_output) -} - -fn turn_struct_field_to_enum(field: &Field) -> (TokenStream2, TokenStream2) { - match field { - Field::Function(fun_field) => { - let field_name = format_ident!( - "{}", - fun_field - .name - .to_string() - .from_case(Case::Snake) - .to_case(Case::Pascal) - ); - - let input_type: Option = get_input_type_of_bare_fn_field(fun_field); - - match input_type { - Some(input_type) => ( - quote! { - #field_name(#input_type), - }, - quote! {}, - ), - None => ( - quote! { - #field_name, - }, - quote! {}, - ), - } - } - Field::Namespace(namespace) => { - let (namespace_output_fields, namespace_output_namespace_enums) = - turn_fields_to_enum(&namespace.fields); - let namespace_name: Ident = format_ident!( - "{}", - namespace - .path - .iter() - .map(|name| name.to_string()) - .collect::() - ); - - let new_namespace_name: Ident = format_ident!( - "{}", - namespace_name - .to_string() - .from_case(Case::Snake) - .to_case(Case::Pascal) - ); - - ( - quote! { - #new_namespace_name(#new_namespace_name), - }, - quote! { - #[derive(Debug)] - pub enum #new_namespace_name { - #namespace_output_fields - } - #namespace_output_namespace_enums - }, - ) - } - } -} diff --git a/trixy/src/generate/lua_wrapper/lua_functions_to_globals/mod.rs b/trixy/src/generate/lua_wrapper/lua_functions_to_globals/mod.rs deleted file mode 100644 index 124817a..0000000 --- a/trixy/src/generate/lua_wrapper/lua_functions_to_globals/mod.rs +++ /dev/null @@ -1,123 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::{format_ident, quote}; -use syn::{punctuated::Punctuated, Token}; - -use crate::{ - command_enum_parsing::{Field, NamespacePath, FunctionDeclaration}, - DataCommandEnum, -}; - -pub fn generate_add_lua_functions_to_globals(input: &DataCommandEnum) -> TokenStream2 { - fn turn_field_to_functions( - input: &Punctuated, - namespace_path: Option<&NamespacePath>, - ) -> TokenStream2 { - input - .iter() - .map(|field| match field { - Field::Function(function) => generate_function_adder(function, namespace_path), - Field::Namespace(namespace) => { - let mut passed_namespace = - namespace_path.unwrap_or(&Default::default()).clone(); - namespace - .path - .clone() - .into_iter() - .for_each(|val| passed_namespace.push(val)); - - turn_field_to_functions(&namespace.fields, Some(&passed_namespace)) - } - }) - .collect() - } - let function_adders: TokenStream2 = turn_field_to_functions(&input.fields, None); - - quote! { - pub fn add_lua_functions_to_globals( - lua: mlua::Lua, - tx: tokio::sync::mpsc::Sender, - ) -> mlua::Lua { - lua.set_app_data(tx); - let globals = lua.globals(); - - #function_adders - - drop(globals); - lua - } - } -} - -fn generate_function_adder( - field: &FunctionDeclaration, - namespace_path: Option<&NamespacePath>, -) -> TokenStream2 { - let field_ident = &field.name; - - let function_ident = format_ident!("wrapped_lua_function_{}", field_ident); - let function_name = field_ident.to_string(); - - let setter = if let Some(namespace_path) = namespace_path { - // ```lua - // local globals = { - // ns1: { - // ns_value, - // ns_value2, - // }, - // ns2: { - // ns_value3, - // } - // } - // ns1.ns_value - // ``` - let mut counter = 0; - let namespace_table_gen: TokenStream2 = namespace_path.iter().map(|path| { - let path = path.to_string(); - counter += 1; - let mut set_function: TokenStream2 = Default::default(); - if counter == namespace_path.len() { - set_function = quote! { - table.set(#function_name, #function_ident).expect( - "Setting a static global value should work" - ); - }; - } - quote! { - let table: mlua::Table = { - if table.contains_key(#path).expect("This check should work") { - let table2 = table.get(#path).expect("This was already checked"); - table2 - } else { - table.set(#path, lua.create_table().expect("This should also always work")).expect("Setting this value should work"); - table.get(#path).expect("This was set, just above") - } - }; - #set_function - } - }).collect(); - - quote! { - let table = &globals; - { - #namespace_table_gen - } - } - } else { - quote! { - globals.set(#function_name, #function_ident).expect( - "Setting a static global value should work" - ); - } - }; - quote! { - { - let #function_ident = lua.create_async_function(#field_ident).expect( - &format!( - "The function: `{}` should be defined", - #function_name - ) - ); - #setter - } - } -} diff --git a/trixy/src/generate/lua_wrapper/mod.rs b/trixy/src/generate/lua_wrapper/mod.rs deleted file mode 100644 index ddbb235..0000000 --- a/trixy/src/generate/lua_wrapper/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::{ - generate::lua_wrapper::{ - lua_functions_to_globals::generate_add_lua_functions_to_globals, - rust_wrapper_functions::generate_rust_wrapper_functions, - }, - DataCommandEnum, -}; - -mod lua_functions_to_globals; -mod rust_wrapper_functions; - -pub fn lua_wrapper(input: &DataCommandEnum) -> TokenStream2 { - let add_lua_functions_to_globals = generate_add_lua_functions_to_globals(input); - let rust_wrapper_functions = generate_rust_wrapper_functions(None, input); - quote! { - #add_lua_functions_to_globals - #rust_wrapper_functions - } -} diff --git a/trixy/src/generate/lua_wrapper/rust_wrapper_functions/mod.rs b/trixy/src/generate/lua_wrapper/rust_wrapper_functions/mod.rs deleted file mode 100644 index c0dc052..0000000 --- a/trixy/src/generate/lua_wrapper/rust_wrapper_functions/mod.rs +++ /dev/null @@ -1,231 +0,0 @@ -use convert_case::{Case, Casing}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use syn::{punctuated::Punctuated, token::Comma, GenericArgument, Lifetime, Token, Type}; - -use crate::{ - command_enum_parsing::{Field, FunctionDeclaration, NamespacePath}, - generate::{get_input_type_of_bare_fn_field, get_return_type_of_bare_fn_field}, - DataCommandEnum, -}; - -pub fn generate_rust_wrapper_functions( - namespace: Option<&NamespacePath>, - input: &DataCommandEnum, -) -> TokenStream2 { - generate_rust_wrapper_functions_rec(namespace, &input.fields) -} - -pub fn generate_rust_wrapper_functions_rec( - namespace: Option<&NamespacePath>, - input: &Punctuated, -) -> TokenStream2 { - let wrapped_functions: TokenStream2 = input - .iter() - .map(|field| match field { - Field::Function(fun_field) => { - wrap_lua_function(namespace.unwrap_or(&Default::default()), fun_field) - } - Field::Namespace(nasp) => { - let mut passed_namespace = namespace.unwrap_or(&Default::default()).clone(); - nasp.path - .clone() - .into_iter() - .for_each(|val| passed_namespace.push(val)); - generate_rust_wrapper_functions_rec(Some(&passed_namespace), &nasp.fields) - } - }) - .collect(); - - quote! { - #wrapped_functions - } -} - -fn wrap_lua_function(namespace: &NamespacePath, field: &FunctionDeclaration) -> TokenStream2 { - let input_type = get_input_type_of_bare_fn_field(field); - let return_type = get_return_type_of_bare_fn_field(field); - - let function_name = &field.name; - let function_body = get_function_body(&namespace, field, input_type.is_some(), &return_type); - - let lifetime_args = - get_and_add_lifetimes_form_inputs_and_outputs(input_type.clone(), return_type); - - let input_type = input_type - .unwrap_or(syn::parse(quote! {()}.into()).expect("This is static, it always works")); - - quote! { - async fn #function_name <#lifetime_args>( - lua: &mlua::Lua, - input: #input_type - ) -> Result { - #function_body - } - } -} - -fn get_and_add_lifetimes_form_inputs_and_outputs<'a>( - input_type: Option, - return_type: Option, -) -> Punctuated { - fn get_lifetime_args_from_type<'a>(return_type: syn::Type) -> Option> { - match return_type { - syn::Type::Path(path) => { - let args_to_final_path_segment = &path - .path - .segments - .last() - .expect("The path should have a last segment") - .arguments; - match args_to_final_path_segment { - syn::PathArguments::None => - /* We ignore this case */ - { - None - } - syn::PathArguments::AngleBracketed(angle) => { - let lifetime_args: Vec<_> = angle - .args - .iter() - .filter_map(|arg| { - if let GenericArgument::Lifetime(lifetime) = arg { - Some(lifetime.to_owned()) - } else { - None - } - }) - .collect(); - return Some(lifetime_args); - } - syn::PathArguments::Parenthesized(_) => todo!("Parenthesized Life time"), - } - } - syn::Type::Tuple(_) => { - // TODO(@soispha): I don't really know if tuples can have lifetimes, but let's just - // ignore them for now <2023-10-14> - dbg!("Ignoring tuple lifetime!"); - - None - } - non_path => todo!("Non path lifetime: {:#?}", non_path), - } - } - - let mut output: Punctuated = Punctuated::new(); - if let Some(input_type) = input_type { - let lifetime_args = get_lifetime_args_from_type(input_type).unwrap_or(vec![]); - lifetime_args.into_iter().for_each(|arg| output.push(arg)); - } - if let Some(return_type) = return_type { - let lifetime_args = get_lifetime_args_from_type(return_type).unwrap_or(vec![]); - lifetime_args.into_iter().for_each(|arg| output.push(arg)); - } - output -} - -fn get_function_body( - namespace: &NamespacePath, - field: &FunctionDeclaration, - has_input: bool, - output_type: &Option, -) -> TokenStream2 { - let command_name = field - .name - .to_string() - .from_case(Case::Snake) - .to_case(Case::Pascal); - - let command_ident = { - if has_input { - format!("{}(", command_name) - } else { - command_name.clone() - } - }; - - let command_namespace: String = { - namespace - .iter() - .map(|path| { - let path_enum_name: String = path - .to_string() - .from_case(Case::Snake) - .to_case(Case::Pascal); - - path_enum_name.clone() + "(" + &path_enum_name + "::" - }) - .collect::>() - .join("") - }; - - let send_output: TokenStream2 = { - let finishing_brackets = { - if has_input { - let mut output = "input.clone()".to_owned(); - output.push_str(&(0..namespace.len()).map(|_| ')').collect::()); - output - } else { - (0..namespace.len()).map(|_| ')').collect::() - } - }; - - ("Event::CommandEvent( Command::".to_owned() - + &command_namespace - + &command_ident - + &finishing_brackets - + {if has_input {")"} else {""}} /* Needed as command_name opens one */ - + ",Some(callback_tx))") - .parse() - .expect("This code should be valid") - }; - - let function_return = if let Some(_) = output_type { - quote! { - return Ok(output.into_lua(lua).expect("This conversion should always work")); - } - } else { - quote! { - return Ok(mlua::Value::Nil); - } - }; - let does_function_expect_output = if output_type.is_some() { - quote! { - // We didn't receive output but expected output. Raise an error to notify the lua code - // about it. - return Err(mlua::Error::ExternalError(std::sync::Arc::new( - err - ))); - } - } else { - quote! { - // We didn't receive output and didn't expect output. Everything went well! - return Ok(mlua::Value::Nil); - } - }; - - quote! { - let (callback_tx, callback_rx) = tokio::sync::oneshot::channel::(); - let tx: mlua::AppDataRef> = - lua.app_data_ref().expect("This should exist, it was set before"); - - (*tx) - .send(#send_output) - .await - .expect("This should work, as the receiver is not dropped"); - - cli_log::info!("Sent CommandEvent: `{}`", #command_name); - - match callback_rx.await { - Ok(output) => { - cli_log::info!( - "Lua function: `{}` returned output to lua: `{}`", #command_name, &output - ); - #function_return - }, - Err(err) => { - #does_function_expect_output - } - }; - } -} diff --git a/trixy/src/generate/mod.rs b/trixy/src/generate/mod.rs deleted file mode 100644 index 103b3f1..0000000 --- a/trixy/src/generate/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -mod command_enum; -mod lua_wrapper; - -pub use command_enum::command_enum; -pub use lua_wrapper::lua_wrapper; -use syn::{ReturnType, Type, TypeBareFn}; - -use crate::command_enum_parsing::FunctionDeclaration; - -pub fn get_bare_fn_input_type(function: &TypeBareFn) -> Option { - if function.inputs.len() == 1 { - Some( - function - .inputs - .first() - .expect("Only one element exists, we checked the length above") - .ty - .clone(), - ) - } else if function.inputs.len() == 0 { - // No inputs, so we can't return a type - None - } else { - unreachable!( - "The Function can only take one or zero arguments. - Use a tuple `(arg1, arg2)` if you want more" - ); - } -} - -pub fn get_input_type_of_bare_fn_field(field: &FunctionDeclaration) -> Option { - match &field.ty { - syn::Type::BareFn(function) => get_bare_fn_input_type(&function), - _ => unimplemented!( - "Please specify the type as a bare fn type. - That is: `fn() -> `" - ), - } -} -pub fn get_return_type_of_bare_fn_field(field: &FunctionDeclaration) -> Option { - match &field.ty { - syn::Type::BareFn(function) => get_bare_fn_return_type(&function), - _ => unimplemented!( - "Please specify the type as a bare fn type. - That is: `fn() -> `" - ), - } -} - -pub fn get_bare_fn_return_type(function: &TypeBareFn) -> Option { - let return_path: &ReturnType = &function.output; - match return_path { - ReturnType::Default => None, - ReturnType::Type(_, return_type) => Some(*return_type.to_owned()), - } -} diff --git a/trixy/src/lib.rs b/trixy/src/lib.rs deleted file mode 100644 index 21aa639..0000000 --- a/trixy/src/lib.rs +++ /dev/null @@ -1,97 +0,0 @@ -use command_enum_parsing::DataCommandEnum; -use config::TrixyConfig; -use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use syn::parse_macro_input; - -use crate::trixy_lang::parse_trixy_lang; - -mod command_enum_parsing; -mod config; -mod generate; -mod trixy_lang; - -/// This is the heart of the command api -/// It mainly does two things: -/// - Generate a command enum -/// - Wrap the enum in all supported languages (only lua for now) -/// - Generate wrapper lua function for each command -/// - Generate a `add_lua_functions_to_globals` function, which adds -/// the rust wrapper functions to the lua globals. -/// -/// The input and output values of the wrapped functions are derived from the values specified in -/// the input to the `parse_command_enum` proc macro. -/// -/// For example this rust code: -/// ```no_run -/// parse_command_enum! { -/// /// Greets the user -/// greet: fn(String) -> String, -/// } -/// ``` -/// results in this expanded code: -/// ```no_run -/// #[derive(Debug)] -/// pub enum Command { -/// Greet(String), -/// } -/// pub fn add_lua_functions_to_globals( -/// lua: mlua::Lua, -/// tx: tokio::sync::mpsc::Sender, -/// ) -> mlua::Lua { -/// lua.set_app_data(tx); -/// let globals = lua.globals(); -/// { -/// let wrapped_lua_function_greet = lua -/// .create_async_function(greet) -/// .expect( -/// format!( -/// "The function: `{}` should be defined", -/// "greet", -/// ) -/// ); -/// globals -/// .set("greet", wrapped_lua_function_greet) -/// .expect("Setting a static global value should work"); -/// } -/// drop(globals); -/// lua -/// } -/// async fn greet(lua: &mlua::Lua, input: String) -> Result { -/// let (callback_tx, callback_rx) = tokio::sync::oneshot::channel::(); -/// let tx: core::cell::Ref> = lua -/// .app_data_ref() -/// .expect("This should exist, it was set before"); -/// (*tx) -/// .send(Event::CommandEvent(Command::Greet(input.clone()), Some(callback_tx))) -/// .await -/// .expect("This should work, as the receiver is not dropped"); -/// match callback_rx.await { -/// Ok(output) => { -/// return Ok(output); -/// } -/// Err(err) => { -/// return Err(mlua::Error::ExternalError(std::sync::Arc::new(err))); -/// } -/// }; -/// } -/// ``` -#[proc_macro] -pub fn trixy_generate(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as TrixyConfig); - - let trixy_code = parse_trixy_lang(input.get_path()); - todo!() - // // Build the language wrappers - // let lua_wrapper: TokenStream2 = generate::lua_wrapper(&input); - // - // // Build the final enum - // let command_enum = generate::command_enum(&input); - - // let output = quote! { - // #command_enum - // #lua_wrapper - // }; - // output.into() -} diff --git a/trixy/trixy-lang_parser/.gitignore b/trixy/trixy-lang_parser/.gitignore deleted file mode 100644 index 20c0ba9..0000000 --- a/trixy/trixy-lang_parser/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# build -/target -/result - -# This crate is a library -Cargo.lock diff --git a/trixy/trixy-lang_parser/Cargo.toml b/trixy/trixy-lang_parser/Cargo.toml deleted file mode 100644 index 500ee94..0000000 --- a/trixy/trixy-lang_parser/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "trixy-lang_parser" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -clap = { version = "4.4.11", features = ["derive"] } -pretty_assertions = "1.4.0" -thiserror = "1.0.50" diff --git a/trixy/trixy-lang_parser/README.md b/trixy/trixy-lang_parser/README.md deleted file mode 100644 index 0a6f6bb..0000000 --- a/trixy/trixy-lang_parser/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# trixy-lang_parser -This crate contains a parser (and lexer) for the Trixy language. -The corresponding grammar is in the grammar file [here](./docs/grammar.ebnf) encoded in [Extended Backus-Naur Form](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form). - -## Docs -Run `./generate_docs` to turn the grammar file into railroad diagrams. diff --git a/trixy/trixy-lang_parser/docs/grammar.ebnf b/trixy/trixy-lang_parser/docs/grammar.ebnf deleted file mode 100644 index d495fc3..0000000 --- a/trixy/trixy-lang_parser/docs/grammar.ebnf +++ /dev/null @@ -1,32 +0,0 @@ -# (* -# Trixy is fully whitespace independent, this means that you can -# interleave whitespace in the definitions. -# The same applies to comments: -# - Line comments (`// \n`) and -# - Block comments (`/* */`). -# *) - -CommandSpec = {Function | Namespace | Enumeration | Structure } ; - -Function = {DocComment} "fn" Identifier "(" [NamedType {"," NamedType }] ")" [ "->" Type ] ";" ; -Namespace = {DocComment} "nasp" Identifier "{" {Function | Namespace | Enumeration | Structure} "}" ; -Structure = {DocComment} "struct" Identifier "{" [DocNamedType {"," DocNamedType } [","]] "}" ";"; -Enumeration = {DocComment} "enum" Identifier "{" [DocIdentifier {"," DocIdentifier} [","]] "}" ";"; - -Type = Identifier ["<" Type {"," Type} ">"]; - -Identifier = (CHARACTER | "_") { NUMBER | CHARACTER | "_" } ; -DocIdentifier = {DocComment} (CHARACTER | "_") { NUMBER | CHARACTER | "_" } ; - -NamedType = Identifier ":" Type; -DocNamedType = {DocComment} Identifier ":" Type; - - -DocComment = "///" {ANYTHING} LineEnding; - -Comment = "//" [ NOT ("/" {ANYTHING} LineEnding) | "//"] {ANYTHING} LineEnding; -LineEnding = "\\n" | "\\r" | "\\r\\n"; - -# (* -# vim: ft=ebnf -# *) diff --git a/trixy/trixy-lang_parser/docs/grammar.pdf b/trixy/trixy-lang_parser/docs/grammar.pdf deleted file mode 100644 index 716a39f4ca7afe33f06daa98ec15512bc7f3ab59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67698 zcmbrm1C(UVw(r|z+v;+4*|u%lwr$(CZQHhO+f`kz+uu3&ytmK$_8a%yy>sNqkr^3l zjfj|m`HTOIMJy{QOhre{1W7!*IJ62$$BajdXQOWp$;k;xBWYx9;%JIT$H;*9pD#!n zVKYldBYQj=VM{$nBS9kr8$%;VZf-~iM|&eZD@fOMr)fGUKsxB)J+2;t0@&lXCNT#s zA&{*=>;1x39Q$vdy3;L!#7dcMLlRq4px`i+S}+uaqY=RYH$dEN$D&uAHC3XE*3wml zyig5w#q-ib@`5U=t5Q`(aczxd1$^;Q3knzHb)DN*((f{_qP@gO05apaiwlnhfbAwO z!IQVc4bTmbuGzYIu+%NyK1d^L!~b2fKi~eU72QA7PSMTQ2#-cpM&I1Xz!8#0!Aalo zKT?Ek?5+M(^v~yiUDB=`b zGAp~KFi;One!;mGy6J)z9utCAcF|6YJVqG`1@9^s`pI$kNdc1ihYXZ5N|K0#TntGgiKLnQo%;f? z#yoE18m3Og$hfPqnZ&7qhiYTs_=*2{T5N~>p^7#k1H!BPh^&MHsK!DPzQTh4;LcWj zWFb#^tiOtG7SfAXXgH2(KwIF^anMbO0?JlPaAcPixGIgZ6T?1njWwz2sc`+ zHVE4E;wQ!`U@&43_*&zy%qswlRec!K7R+)>`IY>({CxXcFO$ZJbWjP4!bYLACSCbW z0$nP~P**VJ?)}Pcp(^F^eNBp^6M~%B2&ZND?4~bG+*byqkz4YiJMs~GB~(|CCpE9} zYulLzrx>TBB7gOnZ96UzQ>>H?W7j!>&iz${W&zwRFYF_A60^Y6d0g9Ncw@jHxb-k8 zL}B`d?3RZgUkywNOA1G65KXr4B~Jvgxdet~B<0gX!XS__b>VoT?8+IT^yKE`sVcqc zkSye;(d1g&UGc^l>#LV;bz`bA$TBR;3rp)K(A22okH4g}zu)I`fj$}nKc+psRFkK^ zPF`B2E+iDrHN@G%OUIXuKxuE>D82w>c(b+tJ}~JS|8&y-ns@%$B=i4{&%Z_`oxuOo zmFel&*#2<}%FI%-P*hMs?Z!XMHkgnNVI&7S&W~Py#WGn=Kg)Tg zv)IBt$#z)Y-uar|-WkE?ie8aK%}6L@c$ zhvwJWIy%v?ZHe z+%if^7ET+DZp}DvQiB>%Y%2WFnNZRFmO*ZP3BV9XhxM5T5h?RC=WUDCxY)br7`!`B zH_w^@8h^DXMaJHJ>#;rear1lvDY)|OLLFnOn$9=vzTT^7XNK==c8) z1w=RiNMpR5csVefe18x6wGuQ`rO|vH3rx5Jj(jDG6^=3E7G3*mki(}Jo+c}kbQB%+ z8P_bn>B_#qgh)02rlhOpoilYf+~nakN?DkoH(1#|K~2M^9psL^muB4A>3rsGujSjH z>rS4N}5kM2^cpfawj{oEa*YHH*6*`+nwt=~0Yt zM`FaZr`)F3%km%43G zpT2+rp0E>C5(}#-3cnVq;;8W1Ac}buJdKGphVi$O3J1cVH&L)~NU#vyt5z|%MBx`Cw9;I-q?99_MorcgIF|pGQ@LVz-46p#H@Cp( zqsIuH#KagSKveo!D@AjD)lrEZ-e%QIrd|ThSx|l|%#O34gYuoo zo+jqYtK7y&ujd?%ux|s>IWsoE*GoH%Bz#-)@ZN1{4K4dWI^%bvh2 z!b2fKzj5EV)6&Pel~&_p>^&7?MiHBhV7lU=lqT1O>x;YF!>dAbW_>M(rGIZE{mv}4 zec}4BKYuRz@SD*@7TAn$D(#SsxfvbHhDz{SyI>F6ekt_!e?C9i%s$>%XP2&p7O3Ag zr33#BA~q;rR9(3lRXA_ih`xt5X+&R2bG7~=yE5ooTgYdps8r6|9kRrwBmXifvHP=> zY<;A!jUlQhJ)s=2<`;1|Vi>u$^#I-MX-F0lw{AKg{LQ3TPS3&V9h!(d>&i?Q0 z+RZtSZ#-G^Mo=dId-`|MpaXz(+Q;$^G0SV6lbR<4)$^>Wd#_cN`|F; z%+-Y*I?ipvD3oiE$@8wEA~OP^JmJWCHB3Tb1)Ip|Mww0cd#r=Xsvtt|fYL2z^H45* zz#Pcf!CZ>jV>A006ueYoEKU(6pogb9d`=VKc7*LBx30`@?gUUEM(g zXl2NiLpE&w1j@ zGfRhOEe}Ffq(_^~%xsg^*drAVVp6)K0!UO|vjLFs?jdK_aPi8Gv+Ps#29zay*LG>2 zSp8){IUnl*d|-^);61vCG?yEj-pvYu=sG@gmeT6MiC?ah;}?L6%Ee0;i9sSy^1uQ5 zxCd>k4j*4EDKy1{a@!0cPSNrsks$+M4#H<}!Y2Kbu@&_c9>0zk&{%;vX9A&Z{Ge?^3hzeTa&ck}&x@9Ji$(b-?$`5A*KCtfF-X;14y z0qGZcdmV!Q@v@6aG7Xs);+mAZx3_zSbqabO&t&auS<|pi_cVU8R}oWu^3Y?Q9r7e~ zC~cvZ<-tx@T1)Fk$H_F2Sv784Ez+%Jv&aV%nIZACL4p76amm)897~ZhQHTR+q9Y<= zQ`0FRURR!HM~2PFK;Znc=@!gHbMl}vH^lUY+*_lCSSE`|?qEE%^W^1zlMRoD4*|#0W&$c#Vc*id4Ets)p zY;$kHONC-(WJ&ZiJ6gx!jsA5ucX6X{O$T)?AMtBJWDdpk=<+P%WCqnJ8!?+xpJyF= z3^MVzB$mdTKg4u{8TQ^+tL2@Wl%c=ck}hWO3o@@#(-RZZR@RBSG(PKk9Evm@qHg5; zMgA5Eg$>}G=z^so)ez*{^ixYTKI>XKmLy$TFS?p`b4_L!kp-YgMYO7u)M+FQH~6gv z#tP3?imjtgWv#3HCQ<}!?q>Y%)-Axrq+KP$NL$w0GmQziT#0yb>On8MR(=@YtcF3! z!TB8k2ixQFLQ4pxwsuT;m$wl7H}GVStlvNJc>clR`8QF-$lCE=#}KCfV1|Fd5JpB; zJXV%}gF^m7$iGtv%U`MgokHm7*=YaI7;=;<5rfSH?|buw;uR=SvlBzeC&$Yl5uXip zLec?ou8qvQf1^Otag~iH#E=>KO#9V-X|L3#+5CH<(1gM4BVor$#kjwRCp&4(Y#NhS z=C(;US0C^i3n~W}$=STfu~!|;6p$LD61%cp>i&f{cM@e^zW$p41d%uY*MB_&VVCw+ei+Lz-c(M zq5p4FZ3r+u(Qu^bm3fi|J!o7{=zVJo{cvBl^s&?ZWBN8gvI7U9o6Ss#;?8|I{Ao;X z%PRrDj{<;7C_*6xT?3^mh~XnvG@VfKUDAb9UAW{Or6w9(KEItRFMK4s$Ww!hp|Ymm zGRf~3j$h4OQDr8#tl4vR;!;4}P_oS!66iICO7=;~%)00S#pdY|X+V9-_`-nK z%s*TzC_HPjH~f!s%l(6Sm16y$whm}gaJ@Y}8HOuULzxvX;BymmVJIQw%C`_0I5kTR zpsKHon&GOOjGHBgZ|?^gJ2Ej*$D@E;>HBv_%Ym9f@u%i`_we854AamvD$ae8kBx7) zf!fWuicr*5IU&k}{4MtY3S;FcIdv^$sgOSn_YiiRpr7YOb`S68XS~ua&rXl`_RKHlz* z+KRj!nECY_W$9stsD(@QX~$)K5lijE$lT%#+shOi6-7MOVZ%XexRszbv5U)bHz+1a zMw*885Sr6lB%komP+-iOv!fYwpexj8kN^CAu}fAxw{rffwL+lK9$>=h)~QLFHQ);B z0dF;s%5cmBVP%gZyZn}{N2HV{zKf9*zq_DmUrdEET|qtr~1yDtJJ8<{jIj& zw$CJ8*i^8seArMGk`mP78BoRVG@#GTTHPuOBPyuv*b51<76L1aZ^4Urv=k5?hQfSG zY4`vJmEwK|q?g0wW+N>w>k zuoqW^pslwU`xVXwK(}u=)c1u*l{6@a8|e z(ee+$wWkX{tlF=ILZq-=^0|hR%)#qz4OYF^9ImD;S4tPmTo1LalEY@?nb=M1mz{YN z>r_YL+uKTLY>JzN#xnmmU&Bc?BEYbvl=Q-1v$M>bzk6=p-&1L^)Bb;;*+W)A1+yn5)sWQ+(w8B&>x(jNRXEpATSpG9?+lY7sM!`pD!OTgulP9xE!TLU8$AJhV_zohr+q@ zR$0|T^0jnN2AiXajipPw&tpwio6n|4w?~h|%*|7sr0$P_dP`Y;>FSTsdRC)r3&`&W z#4odCIGEiWge1A3nza_;Nr?b-L?Qw{zhC^g6=@)7-eAo;{^le|qxErgV%0+(2l3f3 za60YUX(eQ%W8>7&8g2V+X#MnzdORxq%%d@fhGhMye|9Zi^^CNuHRW0vez6psimn!T z4eTGP35DoTyzDgTw14`1Vg$R88F;R!CAl&_8$> z<2R(PO>s_8>Lt@*rGXnGHTbXfTN-E^I@2>?g!#fDjgF>qTzW2J_yRc+NF&o=!WjY@ z0NR8P3@Jw+a0?@dMx^YT+D_i$;8oS0aPrh7JrTPTnZYUuJX@Kc>_AZ;n;tjf2U9be zDp2B^uU_zx4OLU86qFUqF+<0M^-X2Lxg0GWBWZz?k&?+Y>N73onQ<|S{_sPieF`_Z zRVqwM=^XNAhrl8c8ad%@&X906#S+5}ZaBTivZ`hPj&a1^CNHozTr-}%uIev}Jrnk@ zh@`#%2ZeF3^`Ug%*^5t|nwOCc;u6a;kR*p8<7C4Tu3O!kH^G{wO|n=*sF9Y;UHqZ2a>S zU2!b2U>$Bu=T>Eag(seye*|rIy#HN+K&R6tERcL@v$Uxw6C1y{091iYi&v@7kq#L> zV~T1JgP&8%iR;;Xo;Q>EGS6i3w7y8}H&qsxgBd@qR!HScN>UXo`46URK8h>%k|*k5 zLEs2Jr-KLuD;wpI$y=e*;FlXPH2|Ktn7srdBUvJ4JL9BfS;=wHvVulq8wqy&+UfvWxfj!6j_H$;w&};-2vp{vi8iHR&BnXY(5?2WP5= zYBeiWXs|Om0)UO}d|x4a&0wtDmwBTp&^)51S-}~ll@u*CeUHSBr%RnqjP?k&wGV#3 zaRmtHBM5ICyJdz)QJj31&4e7j# zc{bXdDV3i~q~MO=dB22pjc;^mf{Lo`EYVb)xc9NYFWvP zItUybkco#O8`4t6lBV`+cX{S3G!M@n@8Z6q=NRO>8c20Clm+==6P>#W1N?4gWt`hN zfxW6O;S^|@iSH-x4pyiL!8efhaGhM~fj5{vZYE zPw3(U$$)Qr_&lOQQ$jKhycktw+`!e?etX*OY_l@_hg5inUi6Uk&|3y(!Kr2Y=%l0^Xb`-c0uQa6}%SMavLCMj@u zL6)!IY{H$OdU$Mm`|I~44R8ptKqWE}@S_xgjRk6C!Nv z(!2U?I6jY-1&V#R`@VQT(n11u5j!h6EnDw^dXk*oqd6>s=A8KN*Idf)(DGH?N_NbA za(XN=2r_$28sr+8aUN0F-NBCco^A%a^ZwGto{plB$ApZslY)w}L)BaB_c-@?N%fgW z=jA6QH%e#wXz2D1jv3dju0zPPa?6z`U3I$8#6!!Wz*i~%o18h5EA<4iA&P_c;Ps2G zfD|L9+aklw4USE(a-tTs^D*b*xD~rjdXJsx$lu+`kin=V(;Es6B*oh4nH=u{$85Ux z4jLBZ=T$AVKiJQ65g_*E`GToCCYE{m+0UEajT({F@+TxSDsC@l@Ml~2<5g5k6&q39 zWLlb>oKTv{3}f`;eO)bWXayAoAYJW*HZHndlWf_EViTO|(RWMbqGF0imNL?OD^ zUiD_10vL{{lvJ=@L>Lu|_N;Eh+xHVbLjE+mk?ME;CPw_K@144;Gs{}$gYF7ly}&fL zkZIPjJR^23rSd3L^OLV9V+d!yH?v3GZ_WOiL%7TonA4boyje zkXf=&FnmO>AG~ma7|e-lSI$u!P37ZD(O@lqwM%&MvJS2eld<$|B&q(so5?XiV(dS)hH2&LO{kAgi% zJ-$-i9yq5Xnn4?n;(8v+c^_W9<){76g82fLD(vB5VB^N}HBTf@gIe*Y-gWaER9|`P z6a|vSe{}hkT1001C+>3P_@e_;L3S3E^7jVY$L9sk=px7hCo(t&9)w0##R}mp?RBq| z;5v$a%gc9k_q@tPd=?LD5^SA7rA!j~94qNa9R?>d<0zf~X#bFQ0VNGySSa7dh7D<1 zNNCSq%;K+yrb=d42?A)z7YRxw?Zu5SDwE-`NO_IDy7i0p#bNi8@HfRs=JH5x##s>h zvPg}{gw&Pghb}glS*vzw(J79}v9@u}24J0~76X!)4P#70p);^RwaV9^%zkt3Ye{xT zlf*5e>ZBUx2&mg>J;^pr3#9Bc5u(x{cUVq?l}#a{Y%?_BtVkY9;z#UKCL$Jw9011S zT}lZJt)Ly!o&QxpuZc+MFD@#rF_o#IGosa31BKlZ*+FFBl=TU>AHeKp(MD!oW#gX= zo_}3zy2isokhD%2}!FaD)BQX4^1c_Kh%#BE@HZQj^JA^-@Y zPG~@J_}GxrQeCy7d}SAnm634g@T+Q$CZ}eJjWZX9kgPSVi|K3JPYaW4+~3d~RUP7|sa8wu?K*~ti57?ZRQX}?o#kExDbCM72Mjp>jEIww2(JBI`5^(1K@U>nlhQq;yS zb&Y8oKtj=Vgr_(4P8wiwFBMK7R1<01PdnyS&OD?!6*Q^7vLkpOHBoYH{QlaTz-myXCdRH!<vPN#&&pX4pW zW%Q4B#C}7i!^#$R@R!w8Ys3YphOjFZ2NZR+xqVYKE)7|5d3$-_mY(&2({5f6N%L(w zH2!I{4;#3Apa6X_rw;U0^7L%of!(5Qg;N*uznkGuM-vOqu?}ZmG8$BD#B*{F?#%S| z7Rcg$jX#73k}GYNwzWkbA`Sp#eT|x*Hv$9B9o8~!Q@Wtt8G?_{r9;+yy_j2$DF^)I>PyG}dgoK_ zl{wUVVHG1gjDTV_@KaY26&DMuDs)$I=;Vs4B&m3|iGkgqh9g-7TQCGKbifA~+{ya< zsBb)-4W6A!s}S8~MqG;Lgy*XN#s|=W*60R>rqRNr62(b$_E{};?270rxjeyrY~Yc+ z!>-LQUYnf(AmMq|#OdJP{${yP}EnpZO6d0Jv}8m@IDI6f5T$KcPAf^eg-D&+vDHB>~#Kzqt*Yk5-uiCd(Vr^QE+RjR2HfO>DseJV&%9LXm{bn3>1{wk5LUr5U z`vuX2W`~6GOn;#+xg&_%ozmnNObH0<%xVAaWh&_IewYv~GltGqgDJ{UY&yY-iT?WnP<-Uw) zHc@5jL~GN$qPWj-V`IU+WcxKp>gAymlB?7fJsZ)mN*Z?`g(Mc~!-Z-)Z-A{P+1%l{}dqAhOGwn__tKz0OV9ZbFrl_DSsI{gBS}Gyb9*wrJkDm%-T}628 z46BPvzphLj+G3)3?xN+s+*mg5h>@kkUAIOMtJXwe;WM?*St$-`@*pkp*#&xvSY@Lw zlg;HNY<;Tmg@jY^-CU9lVt9cX6T{Cu-!`vM@G!AV*<3W6QIwSW^X2HY?A7?kC!9dN z?DU%X_-sWdy;1?X30uDH^;qeYNhB3^uml{8%Q)?{a~;XAy@kB$tbP{5 z%{2z9gq^9QxTG^C!jSBrU5zrzXmqY~=w(4wm65YqzWsb~n5$&?h(2@JH|~yV_>|r8 ztT~3T&hg8~lEt0qfvg~cut-4B5F--1R@5ZNB)wtQ8nO+d=&(MWJe$7s__oCk`xPd1 zA32ycqRdxU>g)x@kW=^l64BiWGiSNz1~~7) zu}0{Lfs4&l4S)P&Ps8bo4U4Z7#Uyxk@lPK}9})OacRhx{q>~m%PV$ zQwYL3sQ(?;#rk}GZF3`u&Ba8Hb83D~0(5A;KlY@gXitkboUS2}E-E2o6Vg>PZ&V-e zO~1OjG|w zU)WVs+s0PGT>pjh#>KeaXZ0s*3@X0jr=nn~B-dr2+b7Bd!=ocIl&aTjgQ}{mEVrug z9I99!&+s73z8@~_e^8`Rx&aUW9fSydeQ*Cy=e&(B_r<+V+>k^?WS~&evp+WPSiZZr&=k~Ug-Mr;T zT8t^dV$lHZ@r*{dDAI7#bjcN3#R7wp4!A7Bm-)QPKO_L2cwX7)umiq}7D7gRsx09D1T^!^zfmh~-<@y|<1GD<^ajo?FnXdN*Bav6* z^>=UTADyXx4@JZb|A2^@v6+$mzbx%B{7u?p_>Ty~e?!}2_@~+)!#~sZXqj052il&B zR1Een_^t;E)?qY$J{KSn4+CO0qWG3yC)n&iH{@I>IpXer3wZrlVo)}`+D42L9?0-l zsevTjO?ZN>9Etg`4grYBnYod?ZGd^OgiBc1?*-%c5JAH0*De_1sy^NrG2Hny>)q&q zx!V}I$>Dq1uH;v6^5R$j7@cumRY^IgJHSn;*c0sy9umGuE<*NTn`GJX+Wg7=b7X_( z%iZpEtFt?mg+$qD2#76)k`gNXw0T2xTK&@<>-;yZ&>KF##A)*KI!xiNzI32&^*8A7 zV)f2cBdVPNM?vuNdKn6SD@eYTWhpeqoeM)%5VV>ZyEHiA7&Sl)r5t5`iNSm!LcO!( zI^#NrIc5f$Vj)A@)}hl?+=b~%!i|Q`1=)#Hrieq%k6T{a&9y;5HTLEs7tDTF)t#L=QxAnhFtXT+!I!yIw;{ON+})n7o!*xh9)p-aJ^{oougVE2 zw?X!UWp8iHoY`Jhb@PnYH#{r^GO-u!tUUPvkEJ*Zfg+jxDEvI^4y`x|pXWEYc(f!5 z(X|_q(jvb-BWKh|p{f#ip^jD*02DJoT%@_L%JPyUb>1CbtN_Kgp@72XDqD1xhXnJqlwY#T+q zuog}b#q|ZpKqUj1ABOBbkYi_XIRjLC`yrk!x^l04_0p<<@F|)rUw2+sfOO6nLyQFN z!d$nhG!5@y*PsQN4W1WxNZ{AyCmGW0$N_LolJu~~Dbs-}nUG@}XhJ^O7Uo7wMGc3cuu6Z%iG5mL4RsPRF(Um`%7mm1w8FD2_X_a)5l0}nm z7CB@j+#>mC1_1$r8o%o(A@C>=IHEKnJU9db;!ktlM&HsG?BV>a=3#sqRU=<#a43edNj?XTBZW}xPzQ)`{$=%&?LizM>c$MlaOCe z_4c;(+uKXrGcY)#0#j-~PRIK;#`~U70PNjwrD25ow)IQJaZ>zZ(WoX}O-;*EZspiL z>jsd!kL<6sdi{RCUNQaF@eI~0u2QTVhk%M1rBQ8geh+?(y2p`A)6lPPp`Wxo7BGfw z2y@GF3rL#~WT2bOnDivcRO=O_g-YY0uSj!AbDESt<}xNk8h?S{rPGegY?~@yofGRz z(9FjmNIH#Qp|1g~vDk+jG-*xK#Bf0y$|W?#zH!%kgYwgT#@OM@g!hTeIWinEz==n0 zn67_7Wx8AW2xm*@&I>DaU@8&=^&uu=mzA$;+Ay~+YZ;U7gVL{c7y+zmgK;a|%sH$e zH?XKf#GDJnr2GS~fRbAJxZT05ZezbLKk2}6k(#7}vYe^=G|dUU(4%u1esNTWKyYs) zH7N@pjn+ylq^kx@;B7@CUNkMQH=v8p{?-st_D(Dc*QDwSDVMYxm$cGM>w#oS{lkyI z9qHqUMvI~5EM`j(OsgiI_-7|u(`0zg0!)bp}rT$6kR zpUU@yY(+N|1Klw+$k>t_v{{45MK(06O7Y5V$-9I1#o8Fq?tYLM9Ouu?m?4qg-8_Dy zo>bmbk&a(GlRW6WDZ&wr_i7f2pH*wLh#;w0?C za@J}WlphT8~8^5nVq3jwqW$ht%`$bT!7H9A>N71w|+K!=pOxpS!Ql3Hpv zF6)|9w<2tv*gj|1Je=H2RzOe2dxurZ$cJJWFOc|QMjfI1s9FmPSjIz4WkqbD@$6Vr^0+Sj}81 z6bvQ`h^mIdg5@Lf)eZcdL^?u))pWlm;um6|e$zUSD>wTkub$I6>VU$iBuiy;#*+*& z=cJ5F9T+bEU25oxW@4$ulObFm2SvKf*h>W6lT;@Vc6~$iB@_fHUItg z=JAz3(c`n=;9x=0)W#GUt%OZHK^uXVQUPc07~#wRe-0QFhQ7AtU}?l72h?SQZ{tQoxw2 z{(y}#H_&VLAq!+v%Y9bnYgpBj_P~O$7i_pTuNUPvDzHSnav?c^7_!)vvL-1;={b{T zpKV%2XYs}K&D8aKDOWU6>>;9?vAw!AD@327y8&4)u5}EcgQoaRzefCVhsYaE!bBm( zNb?~^z{^@!?bOg0W$11=9^hMiuD_hf3~z{3iD|<1M8ml{h+R{Y)3X)V?cQ7KW+JDW zwz!e-)jO-ec$Gch!)}lXO)Qp4Z;jw$^+_KBNhhi?*@&eWl|zKTBUV2 zGyu|WU&An~oy~%%M;BfaevZHkgWe!RV}EfGVaD9Smq?Cnl{KSfXroKdk_%E8_-*YvGrOYOQ;((t}0o;jvTk;2o#;1++5riS&0r?k_g| zuk$|t9x4BID9`Y(Tl~wQ%=kCij-HO`-;(VZ|EX-p_|IfJW~ToECM!wBEdDu(ccO9# zo&x_%D%1W@AQWoe`W;Nx#aL~z{?b42<@aD%mY4~R>2qs&`5RmHPQyVX+&boLGPO5# z2B6f+L6tqTDZ2l*V!vr`yZ>@ibL~jM8lCk!m zq*PiTs?49((~)7=4{}T5yd9-_jC9cj*(^`z1E2R_-W#wy-aXkp6)_F%^4t|zRLJ%! zLBzt*rW@*U3`W`&A{hh1uoJ!?(jS(Ukh2&$u|EPm1%ift)*BKli%BpecBxv_Wjc}g zuf#_RA^>rr5u+N3{^HVrD-AS+2lSg$=?OJ!1wTh9vTOyvm|KqpM@EcaaIs~(I9VLO zmdJA6JkeCxx%@~kQZnOI#RInO9)udzqn~FZlJ`XUF~(ZIVr<~m0(#|3WuSHZ>Lx~w zX;fPPFv}*EZLBi<`XH5k*m1D6XU8%PpUE;`;cL0F4934%VI78+{YVbGI&SF<8$*`b zxyJhrCI`1%2v7$oNfKq23-Alv(^ol|I|ZR0R4k5-m?>Go4p1<$q8S^d_E8y;_VCd$ zGhEm_^OXX`*`zlTjpt{*F_F!)iNn|XL#>5~aWu&-4T9g}T&(50e?5&y>Wla%5Di2s?cYZn<3Dar{oh3k{yCNU-^nTI zPqg3~&d3e%VRi$x5fyq;cw1#vhlbpo7F8`rL1}Gw?9pdDiXfq&7@*&+I35H%5gR-~ z4X-Pbn=r6o9~dkOa-DV6@_BVkxbw2JO9w+$dzM4m0B)C_OL5kj&!1qyOpocT9kvq> zo}HVRVofd5^y01jB6_jsV9}t<+RfMQXbmBUZR$;P0D}A1vDpg+&uZ)+Y*be{~5VIl4u5^Iu7#APV@2xKCtF8S5feMg65pi}c#q<4N*z zE2lB157a%5a~+!qynVhEH5O&Ds6P>;=_nIMB!P%+bsIQT{MOm#{zNXrg3UmvryMw9 zCFm-%3j#*IH=YkdmiCEQ;mzeYq?6!L|JWgsxq0C%u>H2a zGmuqANugO9GV6roi)yQ`2k6^omm-iL_aoC($&>|ng?Z}Q4C!**k=T5c_np!V53O9i zlo%X*&-h&j_Wbe=mpVl8*-bpLsvl%(J)BZWGMUH4Cla$J;-EHE`1bRS@Z);E>Rtc0 zTTi~-^%Ln@s+Jh49Q zK`h+CejiPDcLmMab_UlN${%h)`K8Z^ai?LCDc~Laoxic5@76C8+C)T#qLG+|Hn>mD z!k{M}VW&TfM+Vp`-Rt@r=J*W>G`GirmCP@sTJX1_IUi!+oX%(}`)$5aI`*4f%|=%) z^5Yu{64q*b^T(z1C1B3UW%AsC!7LudF8SGc>)5?o0o?x-rwu^v`*F4i3~6Ib zLx09~KOVi2g3P`&r{|uIagqzSc&Bq8T@oriLV?k#U+9#@8gytwFzu0+) z_z_+$3h!*(u*wZ&F znfA}TE&p8vtE>R05gJ!c!1Xhl6X4{d*2&?^GIt+Sk`^s@)x7nj-Y=L$6``{E@O;RZ4CXsZRRm705^3Ww4D5j$)^iF$w&!#AdN^*00ZeLB-QukF;FXLdVLvwCd?d{xev8XHc ztRAE>+s~&_tWEKbkw=D)z6F$Qw6@xv2*`-WBTQh2@$_LeY_M5Fx18Z>OmQ`ydV+{u< z93&<~*->@L%bgDupmHHkm*72uAK78Cm6|CPN~lK3#{{X>=-37od`DAv2p`L@LYIdQ zo0c3?WUgtWmimr@{~2ER8bn^771p(>V;G^LNWB! z&88|D)gHSbrbc2x%N+V485qYmKM1t9;>oj$a*K1-qja6UyYF9o?Jwa{if`A+l1FU4 z3JUVJt)K119dnSL=Sq4IZ7ps|=K5kAFGYbr`Ezt{jnB6i@`Sqzv$=}OlUh)n32LsR zo2$=aC%(e&+|0@>`Rnyr-hdJ34x&_T?Hb*5MK>3gmN$4BQb7|oY244U(O7Myd(CV@ z!=*qS<5hAC_Yfzxj`79iC7e96oXe^jbJ>AIE*{G$*MNzlCxZz&cXbI7;+YKB0;p7J zx#qpyVz=PRDQ@2r`1d^&0>{T2-L>?zleMFBeI}7QkvpV0U+&kF3RRO?5~{lfA)>&F zN7`bBRa}o}My6_z5UjW#3Onm&Kd6al`usyD(BiLNC(+HxaisjAzyTPFq7 zESK}<85e%h-VWxB;S8e}u7Gd|bj^VRPPKzR!tZmeX0a^((D@jqh!LsrW@zbupvGl# z$7)!SS2lS=8br@zMCvik3;w2BCbux+y052c*8qy-5P$5T$USkmWbYA6OiL(;bt!j$ z=iqpMvu8i5kNF zBfrY4yaW+39X#!}r|6r4-PJ}#z)e9QtZA#YjuZf!H@W}kG#~!XhyLp^+J&X}GH!;B zOV%5ngfxJGqMaP0J!m#8lr1sptV|uRZ&#aPoMag%K2Oyp)@k!^I76oiResHHlj*L~ zu!yRWko43z;+gHAbRBJ6)z0r2LZ4VT7SsWM_e=iqi6j382?$v`SsB^uIhxs6|LY*| zH^q$U&oeccnf~?oJ;r}3m@)n{!HkK8;eS8^YE#jF4#Rc-R(M1pT6`&Kw~D}m$B))~ z2APcw%*~EMM6~1WGA%CdYDM3HxA!+9MejG4C|8QzA#W;LQo}3ry zscw1Q0DDwD>l>I5BM5U?KVD6?vas@QmD2J2K7QNE*ti-xc&EYj&dBDHoGFRCR1dx6 zgs3YgjoYX;U`KnzI!3isUC;wRb*n!izf_oVZoIh!-2)V=Dn^_0``#rK^;X z!pE}gG}hXdipEF|eX!(aXY2?n8Ei%9BPbNtRF9X#Z{R|V0COORj2EREeB&@szmZGol|T*G2jF^Abu5diTser5~vtb z%zy?o)G0wJW!pR-*sjWbL*!-(aqsJEY!R+cU-#?V(FNK~bep-I5Mq2qbJ?VrDX*x* zkzzUP2|0)<;`-FsBdn?0UU(`JDmM4dc-mr%q#Tznz*G*7VhewdW)~6e3OW@VWIMq

~TkuZr*;3AL86e3m8P7cKg48*$2zL725Y9iLEw|SWkYtV^~4Y@lzZ|zk1t0-%SB>LVR!KlR}yj z<($jJVjO^4QBj0r$ zwi>K9SRHjyYk@jijl&1S+GGK~%vGx$QPN2wq3Z`?xWw5F2F@MicGHtd7=RiRC*Mj~ ziNNg<)awnv0HI%tr+3x8Tr;fW0mla&@Bv_npx4ea)f(5LSWcV06b6x<$gw zpx!F07X1Q zyYi@;Pm+24T>MkYVFVTfg71Lc(!y&dy=Z3CowV4)gwEcgmI@gcteD*SS- z-5+`PC9k&2Xsg6o7(-DicJFgxAfS;fQ3y&t z@G6ES+KB#k5;te;HW8)P|!JdP#l{Z#Jw`h9n1gF+E>L zZdcro2!$pVU(OgGx>>%sGR2F5T=+A=zefBy%!MM%Ov>@7HS8K)OVv-Xr5DQE%o(3$ zFR+}UL)Tyvf;$I3jsNBt>-`abV3@u{j|G3rAjEsG-F16#ysbO4HSKUB)PGRb1o@M1 zQ#AJX{w-NVIb0fBFJ7OoS%XYd9muZjV{q=#uZ6O%_36+lXfi@s??{NC;Ygz{CrF0b zjaC+j%O&L}MqFxnjjO?{|M8P<11J`%l7%k;q_t5m(Su#>G=} zU_Izb)mx{t-<-Te#8kEZHkY(?S3(F=d%Mi6=2$9ytlktl`;UgEv|l=5jk(m*KhhX# z`LJ9u4(kKDfqD0cceccMGk2B82ZbNrt&Gz-_sIJxr#(&$j^a*{1fyzswlowX^ejH= zCOgkXZg|ot>62eD6BC6?6&exEZD33HF1NzJb1f4(LF#Y%gcf#r2yXKbeQ)d2tV^+L zb9fXI^e{4WZ`%g2P!BDdH4T?3^^i3yPi#LsN4-8Uz)C>`71(f5$h=1OVl0;sn< zv0FFbVDKNn>;sNb50-^p>Ajl8u9l0!{E)oWm8GMOaJyH-r`)z3^X7bYvBv=MNxvGu z%Nl}Y{PZLpUnl`&BL+0=rj>K)djw?wIhipnZ0z&X-vk)1)d{>rEIL-Wc~AMH4^o0T z+9+l)x-7VsUq|4HW@nL41Xy6Bxbqr0YPnfj@oQ?@HT1PRWpf&*YPD$-lVugcW1IPIH;gC3+Ts)rjW=2}a%2>+!V;mec zcr5BMT+JlAZ$iGrB(IiP0#v+--@THRU(AYdgGZ~h^KTTRXKdJ;)L13$o9dGRdyQ^l$oO)p5;&Y?;wC#_g#zD@_C(Po{ zNl7q^B|Judftc#AM!)23cjO@mx63|dO@1_>=W7_N?ZWpKjCTZw5*62;tuPmruessu z-+LF-#cW0Ci1(Yy$d{*o2RVM z2KKQZ?$v3pN$?&RdFCWDyu~fqllN#UXj#N>gv4U33O3CAY56DFuj3*oNMsC|DQ&!8 zu&nrj9{*!%A8o67CKuj*ady=mL2qU$7aU=$7dP|6o&C8nB^#M;NoL}vbs`Bv@F#C! ze(9gLBrKayQAposbbsGJ3BNw;h(&Nm1PWBzR&D@#>%2pRBIYCcwEujwhkKfg2dez z*3+WweM*Y~5QSFso4(CJjyac&S%TN)(w9@QO5b!W+EwjwFX9UDG~*N0S9j+9s06_5 zIL615%hd6WIDrRcJ5gc@p#hh0mk4DmL0#RRve7-htjx!;< zfuX_y40Np|kB*i4ky6K90J%>v@BCGJ~?8iUV+2hWTdww)B&15PNqqgtxbISfSK;UgtN`b_y1S;jEz`3IC?EQkKV*GR%&gZ)*Sy*~Hng=&-=ZCt)#cwE<9y6ApX+ ztTcpsP67gmn}+9*dGoi9Y~WLlXpMrKqk0R6mh$xvyT-vPG~U}UPxbZi99bK5o4Hd1 z4LV7h74|boD;&@W2-PUW8dtSWPwmAfAKJ-kAG&@Yua7-sjL$!rfw0F9h%39IpILNg zY%ch9yZsw|y~)iBM++!!&4>`$bd1ne zj>04fKt6x9A471==S>-d{SsgqAOWGdZl|1MpkO94T4A7IEvO<=mJ7<|Yyh7E_sMQu zX(%hnIxnc4JGWdmi7StGStPK(6hjh-Gsc{q1hw&{sd1@G3pG@`(x49`9CnMDV@Nx- zl3PBWes!IzgPvhuE_s8-Wxb>MJz$1 zWapqj^9ft3gMx;AEz*I?HgJ5Kf`z+%s|e(Xe~$d-&_aoq5C0`nY}bkqa))x$6Sb@F z76>eCm(xUbBp)YL_EucV`VNs3|I+h*4r@5`3U_nEXZ5X|?^b5F;?!75rW7si+5G#f zqpA~Nd*41DUs#&nGzaqENYdJXYYJHcVh9|Ip*uauRq3%>#U0E$0)BcxXroYl3dXkg zG7wJVM8g*Nyj0M^lt9k6fs}P8LaGXG(u6KZM-ASKGR-t97AzbW1`jFdZx~iZdA!X6 z9zP^^MEV&x69R1`<*z6?;@v@Yx05+eZl=^C^{OoZRRt#sAJpCP8!D97*n#JvB1v#Kh4H+sBhH9^3 zyXc#ZGiXp7BL|TsmmdW}gsnhftjvS>3XX_SR+a`}Im(cFZkzv@hN|~FNa_1N4fR$j?%vQg zg|bbY!TnZ{2?penNxta549ponVl`E_$Y0|)yJ^Fgfb3%kVn!N*5XNTOFjiArf$O-p z%9jS9@!OtW}nL(aXU)7}|_ znU*4#NPYmNL*Qpt62p}7#b7joC98G9$E5U`H@dxYyE>mc_X`*)QGeveICp@Qj3d?( zEt2x2LlJK-@YYWL6(&mO@Hqnn!38ZM-`8ZaBS9`J?UyJ<+P#iZPXKHQb+3NBvtW|h zAADENPLJ(NbAGM2W5#G?qx6d;Z5AO=#9L(n)gBJp_nkWSgS)$!PZ|ChVjWhKY)4qM7v^CfB3Gx>;!%09L-mZe}=q_cN)J zsPyg`=~0kB?ug|B#mMte1{?A!_#I=p*7hQ$h5?BK8^p&tC&D8eg@5~4|1qg?K2=#e zX&hIWqPO>^OmkVY&{6lKm*j`Ns&eh`9v znZ2PuS9bG3)CwYCdgLkUO%isunxuC;Bsm*w%5(kfW?HBI=xsjV57(anh8HgoCY`r?}KQ{#Y1nhe<`SXbz-E zRpuh>ElLLTpWw?g=E2D-a6x|TQ7ISQpN=`BN31sd&^ddPnyMdqFsLM7=ylR>Le+Hu8EhZ!%B@s&3f zU)u{~{oTFEm}F?_O~w@$U@t!1cVJ9{KEdd_1%Q<-@=p*5RTCv((91%BfUjLGjUEjQ z*frBE>flDiKj5P|tK|C=p3BGyxV-O`N9SjkWZ~En3rI{5bUQZT76+-UKNnV=e~SrZ z>ZV9^qczb*fl5*;G5tD4)34gb4U*iHts`Jn5Fkd-w8=kCOdzKAsVm)G%YC~8@6*BS z%7sID%|>O-ss*4%(K>%g;qdc}a3xj(Xh{Sy0VLwMeNw<)q(@~)3`AQ% zRB9*SN}Qp^a;LI+nXD7%RSl0rp2ORr_T}t3j=Q7GyHD4vO&9Mc@2#u1PCV(BHhE@g z4-sPtg*%;ySaD}b?Jdb>H)gi2C=Y($P{&p?_pDfC4~1bxU+#^yBVJ*Ty|6C5HKyim z_0g>2kEX#@-LU9TFQ=1PV=SZF{PpqsNYAb$s!g?n6ovh#p=e!Oa~+e`c^kwph1hhO z1WARW7yYz&6yfZx1cwhog*nufHis{diz)LjU9a%`?37W~;Q$A=L#jXLw6A)yYUUay ziAEEvsyf=NV)9ptJM&}n&gKND;xxtiWf$ka-nuw0#l!}lR-IzHz7|=NsbSlaLD1E>JIyv*l zJcYvga!Ta%y@~`me5m0~G}L+;Ck{gESYkt6Mm#cuovD&5>Cgy`P1sFpNWRWI@_HIK zdUNJCg5FC6n-?^}_Um1>p6opXQc!E6MxSYt2c1k||Hf{voEr;1830)3^FATZ>+$A0PMZ}frYLMnrE97YW zy;!x8xDo*6B300QpS~2fOmb%q|yOYe2#?9z2umL>9!ptn1O$S^6>&bl)e-v`qp)-^8iZPL^BKd%@MA z(1GZtr_Vn&<)QObV(oEf-Ipi=X-y1-qUjIMV^)FPIQxhf|5np1ULyKQ$%a)W@{Gta z^!!8`69eZQg@c$fPhp=}1ht=0eEOL>FyswL3#z-@zIHrSd_73l;AeM|IkF;fos=i@ zB1 z1-pMul`O=B&BoHg!sep9emV1snqgEK=89fZ!`}0+CT0z6uwe^*;i8eUaIaIT-{`OX zL2y^;*gP{ltQ3!BF=du3E(SVy=1q7w`%S~QZ-m4Qzo400%wIl%K$wiY1T8`7WnthA zVvuFEh_iM{*M7+ivIUGQGE5dL8~`SCwnu6UxI=U+zXk)tmM)?Fty`AQbL?i!#zIlt^@TSmw}1wjcZL+9SsBMwl?sIE>FDcUZuYeZ`fz#S z(s57SR%@tSRaRWozqXRMPC|Md2k1gHgSrNhGZ6oz1L`@*b764x{G9Xncxav1_#+`t zV?}ZKjbC2X9fRBRhK_%wGQDd)G%6-nuhN5x;DsCn~!qH@iG z7y)8@8>FOereAh0j@f{s9KG3PAW-Kd5HL2z%=+W18!Z!_>vOODPVJ1>VRyDJrT{Z$ z8t|uTo&?iI3f{p6Zt8|2GdfF$kT6}~usW}E`IV3uz9%x23MJktXdJZ>A#2N4E6#Qm{6rflD9oQ9FGO*phYA$(^kMb2YU*rE8<$9=>#GblV!Lhl(IWJuj*X)<(aMotRW1*qR z`e=jez{8~4O6xm3U(d1D00wU8Bv$=l7%PKyo-Y3og+EmHQ%t49dujWgLKh4W^4tQhPE|z1t-dx&`rEs#Wf-4fZ?4w z#%{v??W*BjE{ZXZMERRXoF6Svt+voafoF(}=DO7fG*QRFlkp|2gQ9(gmClEJ-51%D zb@CD}RVVwh8Qlw*F=1Ja^k$?+IWf##(<;%$E18UzH3(uV+2ABM9yR zcZ3~(-7d4qNwur8ZITB&UfB(|GX@Zouwj%*4CPiN)wM1X!;P>VcLFf;3Ls@41`9k_0`nBrQ`>Alx-sbB;^7{Kvy7AMC z4Ggu8e8k0O|5#ZP^$}SH-dQo@?R+6OCN3dbER&z?z*^1PfjC{&LJvgC110ghrGNQz z{u_4fg-`_@eA5*)hUbMR=JPa8F@gKF;m44($^OCJ@mj;DP|kbT@yl>G*LS?=B)dH- z>X*{$7g09Ag_YW){*;6H+ueP$?AkLu{}Y%Lj~f181fA@Eryc!&!)=(@|L4PPraGX2 z7!W|Vy>kpCY1YRq>tD=-K`#Ow4T_d=hB|x<~mWbGAYVs#F6KZ=F;fLKz?>w|J*(^|mGeuO_l3 z;B5i|Ti4!^ic_W2=x-&F`|5h)CN-6%9{XoHOW)x74;xIKx83 zJ$A=#dez}L{#Gl#R-A)l_qxf3$F`VG=niQww)wuZy%P8o*(@zw#_X&=vZMI?Oza?F zCw~A>oIP9m@9bgnA_Agn51&JTS7Wj>`A}kDhxfK^#~4w&=tp0OP^PbqP=2=y#23_% z-)Ez!rT31IcQgFZwI$nknBIdP;iei#Ezilvm(hfpW6jy~Nj$q_l@TFhuh3+h90}Tf z@eQr_20-0kA5?{wz;adfgNHv!=Zhjr#hNxLIs<&2-pG=6p*q8MKmgWP-l+K zB}YKLK}gWT0BwL^m4kzn#r`wi-{IfFZwC*@8(wdk;u>m?+@>m5&8}PC_6wh19-ne| z!E1opA2w0YuFI}@$=F=7adEsXy$V!GGIRg@^hV%JHswov!ph8EEurs)`x@oMGG;z` zR(?Zh8L`>0t-jq{(YNtQ!Go~f`oMq+4;qxnhprOezg{33r-q&l!q^4PtG-evNW2R@ z-h`?B*EVxYr_i=rM@3)wxtt$_gjr^#X2AE)Q+pT*)7UyW57JWICO+uMelx1?paoqZ zIc)_189Az~`aLfj*7Y;`+Ct>E7RX?yZ@_PpMis&`5$wAB`+n#qaWXx;1#wGhOkVpyj^Rk$(r%~7+pkh;Uca2FCjSUrNv$`ZUGCxJ6#^3w% zcgq!oKeOwP2O>5#kg`-!KiZM9h__0HRYj%{aVhsfg$IJ{jaNj3^>to4k4HQT*v9V0 zpzS5UzSiJ3yfu7+^$C#@E09!ElB=XQYb^>qpG*c8=d)m#BHVJ)CfX;*_5aY9jbn{- zn*cVXs}H{itM|1}R2n8Tj-|RDDg$DZCdM%}sk6MX6SEmfp^(moi3l78J%;NdQ1z=e zD`#fBQLP4}=s`bSk?Jfvr{|m)f0*Hpz-9E*9icVeE)h|2v{L0t<~cYns27u?8oA4< z6g4cFSy(uah-{l0*gBLLW)6T$ibvNM%){+hfU^O*k|KE3%KytM~=psWmR)FAX@09U>UQXWgS9elux^G z4I%$QFI8L=yUKI7XqCv5T@osr;m_EZm9UM`PN4Y4iftAynK19#ehDykzqKp!od=#-zSsuZ2w8Yiw;6(O>2*`0mU;>K^&zxl*^!hN%d7d_=>gajxcK zK0I}FblD9h6K!4pFfA55)=V*LPt4xNPxutRO^w8OV`1N&DXK4U!zxag57)*^yr>$j zkR)Yq7~CWa$di*Jl2o1Za~`WDVcdlbwOneV$(GsNNX9aRn^a>O=fyDqjTG_@mQ9%# zXleFmQc*gi6sMV4W}Zm9_(8Km98FEPT{6|9(5GHc(Aqwx#0|XNm)N#J~M=YE}zh@ADM)e6{^k z95IE(L6joPYH(oPGGj$RMjyWxKsr^6Qx%&BCY@2{NhXSRG4KS4{D68WJ>4B5c!|B6 z)Mg@Y;2B~GqG7-f7uBVqr>v0_tcQif+yd#z5S7=rcPFp=!=CPKAh6Vpa=xvlaq*OO z4i)C0^lJRq6re{yp7QA;JC+uROWT@_nq%mvCy@I4{_8C{BF8D&>wU!`5CYsPmJ*nab%^jeqyc`{qU5IrZu~ zEU-3%EKePq22#z8#Rhdw3iavWQ>8o%otby4HmIH+p%6y0k>z*WDf7>Di>)ngDOd3=l8^>3Mxq$34cSytG+P=_nk#_^eNMl5fJP9&DHH*MStk7pWHM8 z2eRbDuV<(79RNczVYdfdz8GJ~Q<)Up(GMjxuClS4MXKX2376up(hf}@_^sjJln~B_{MAk* z{WWS_qhXy}U`F9i@{mgMwCVXphju=*@H)GAI$TA=Dvd<7J71RAt*=t<6(><_rpZ7H zW)4K`SiPuPqIMJ^M0?urZaV=NkAP`v3BR;rgsdwZ$z^A`*%2TSMQNksZAL^_{o-|A zPL|=(A++wxn>C%HHO9)1?3w$qKx3mJ@mZkrJHrToqcro9b&&fMXk!O>6Yqcjf$5=2 zao8L$9iC7XZ%R357)Hw`KF@0lQBTUrZ{2DoUN3DO+Ej>QoR+ePX%Nt#1Iwv7 ztg$)9bJyb&Ec`j2Ys*Y|DHG5(`^s4yf~anoSOFc;pa8Wijx=&k%cHuW%FD;oW9u;6 z^ArGgsha08)!~IR69{fs3$(n}^)^Rg_VnWoq9q^?R{d*b^|2e*5jqOn_~nCHff1A5 zB=)o(os2`25t+rlp_+2LY4Pi5fbm7|rn2{>KA%8n+G(tQB0NG&Zg~SS8US6w1 zL;+MGXj2&f?4ev{0J$Ex8w+_=(#NdK9WfOnp6vsT?~T6i>-p|JNA!gP4G-n<79OxS z&@@NdRk(oI!yh69+*kP8F}R$#Js zdJ5JaoiT&Gha6}&OLew?$giNCdrK0rh2ucn6Ov>=JZ-V%r^`0FncqQ90S0Hb1Bmi& z+7nxi)nLaEa>>@ij8CGIZgA>+H|Jn1G@Ic4?V2ZAcrXLRC~AL#-2v`w8?k68QtsH0 z{F*!>^tT3A)P&R*Z4S42UH7OhR|3saVum~XGkPEtk_-CjO>NGDhF%WsRaxcheF^BR zG{`L0IP|FF1gJIz@#z{#dKRy3Km5(Bwy&b_RoBVO z;vg4?pre{~s$;{A%VS7p&7BIi2ekR?nnkpAi{78k@fyEMV)U0K>$OLFiFPr=Ao_TE z77a<3O)SxqLkMoY+*?9&B=a;dwV_Zlp#pG)1)wL___9Y6d=!F!wwq;G^FR9P& zh?TI2N3~l4ut9&NX1>=C@q+Xub-w=e6p>ne#B_SA;){s}8Tr6X*zyL&r+KN|)p+cA zP4?etA1WN6uuM)3d`(2V4IDeW=0|uueY*PrZbrx0n5@Z97^MfX(Hhz=|Ni8L_5ov6 zvfuB=22YWu#3hGNM%xA)4GbcW$zcJF0ldaXH(LR*2M%Uf3`=h&CeA+DwxJI(jR^#( zIXvBgnn_Ygp45P0O=`0GQ8kHOt0uH+uK~+}lbeQ1ggi6HZma^~!#;LPOduIM>~h*_ z$p)y95EoXlv$yHo5Q9Gdwg!xF4=NiJ!b~8Zq?M&O`IThpl3Z#5L$Zp3vw|vmD`>k| zQL)WLJ4B(E;nsC+9Km5+>_|9T7|me+MnrgxbL}pwGIQpF3c{oS~79InD^d zD4COc5bAaC*;hj*Z2T_NI_wEQLKuoYa3W0}!CBj&paKw~f8cxG%4#0AYz@pTjooH} zD<@J|06qnw&w*k#fF$2Yh;9uGb2apY!11(A+Dqyx1!I-(S+`SX#t!?o?-xnJF5--{ z!4Yvf_*0+z9eDQVDSX2*CubVw7QcCJ&w``CM%ucq!ljunJ`I6%n6Xt`H$STVM~AkI5X;?Hu|Pkd)APZP5jnoI9i$J#H1YL)F2Cfp#m}z1Dx~VbzO)LCAum}F>c#6Jnb9)Z<0DEG%b@^z%b#C}}^D!K)oiw#N~9wAH0S%zZU&#<@I z01TXFvK)g}ew{{UTHD->@OQBMt3t(NgZX-#{*{Zp!vn9b4V&nri-u4W+lzFnSJH}OR{p&{&v-w{=iI%)?4igGO3Lf=X{Y+evpmp4ZYj@Cbdb(Xgb;T`ohEz(*4L-+Qsbbv^ zNBv5T{FSZGmML-jIJ7vf@dI;ST{h@}P(Kndar$|+_d*~M5%p@fa^i5cE@NN?(Sz>+ z%yv)ombmCfxwZfpyfCNNm<3{Ktw_3J^2mXIUBPVszFNk{^v`OUg&fw>j}773&5S?; zaOFP~)yYPrGT|4?W)}vl6n`w*s)0$`bqNmT$MCTr35chWe`7!vQbDRih=-QMx{anH ztm$#NmTncA%{gi$tjZ8%pBS#1nVP#4yQCR&YR66fX8Kr4`>6A{ntbW}=JMmjBMc%E ziw~j?qt*{@xIKhZ8lQ6|Nt3Qz*qtwdhALJ+RNJ;aov`AbcS9HFnldf-d zPZB7eHyT4-SRG7^YNc?i$F$N;dhMF}40!z7K?bQ4FH^9PQ96+|9+Me^M%f|=bqUoX z%poUM2)jZy?q@K7zX9H5zhSM*877Sno8Tr0jGadM!iOHFe<7a4ev_MRkGeQZ*gsruBkZ)gIt%DOyf60?=uiIuW&9H>E7#AhX?9 z(~#gI(2Z5p14TAQ#wDh$F}Rqk+RY776sxuJ<=nTtu=9ku(d4V-Vq)smk{+b{0EE7= zaJ{SBDmqkUMpK&Z0#s4eSG1D!BN&13oV+Uq?=hoOl&M()QZmxFuaxtI?HZ90C%qci z@@ck>_28$Z-Ps+5!=aeUNJ}K$E_v?<4<4RU0nMgq{wee^ZG7Ii6`}^6uWA#W^R$A| z#cTw%><=L#@Ue%U5xlSbuu4>PkLNd$R8z%pboMga@$L!;LiZer%6tU&Xi&j2284W# z(l9(II-*+*Wy_{Xyy8c&VCb?|*kT6NGOK6Sq!EYe#Cc)O^$zJ0A(2tp-bIh4-cD|H zV}TWErIF~k%KWH^>2u9iJ9DbBju6(XuMB$O z-%LpX91xd0TFFH}ETe^jLwdF0V)`W4yrQl5gA8Y`4}uts^O&)0+ZrCMk{GQT)T1Z) z?8Fy62WH=sb_Phw*wyxb{OXM7jtT}&F?pe~o(c!~j>?Kv8r7YuA$wL|PdHkk+407w zcG0UXpZQuwX<_3mf65}KSmx(@Sq;L4G&W?(9FVg6PHB2@xy#EUVM4eaY0>v9XY~9M z?J8RHrI@Uv$+hR?9(Og{GBXkok^Yl6S!g|*=H|Sng%-0}am7fuTsl`aB~^ChZTG?I zeZe~MW}r%cdKK23@C@c!)X6Fn{c~WV0_e{}cHw}&Fpg)5)#n-W8v~tli@Y@l;+=Za zT!1G;=apf(nKfOku<|D!(XbyI2jUau(?ZBw?e^sPbd(t&X}+Yg-gUuk{jN1Zy!jSJTWg@ym$L3ov7V%yIYQ{hZqAw z>)i=&A6t7?Y@LPncPp2Pw%e3KN|8!tu9{(PNPM+6e(GEW#kG7&LQIBO#LQ#@E#F~CdRrPv zL+ledvZ5V|<4J(@&-dE}?^*?0a`N||o^;pTr^{h`x_p&=EQg`uu->ZJELFn%S zXw^0y{p$hA@V9{H|0u+Bu+=yG_qlOwf66UX*M4#nUUpvk__PO$m!@MBntMg!n>APdz z&tTh+#eA`_0b9d*kL#M7UBUI4%;7RMun{~@p^Y)C9YjpWx&=dw)}X<)JNJHNe$2_? zvUu&{+P&iaa0lPX*^v3gx1?G)fcL^g^D*TSntIdoxok)J$EB>vSgb73yUUTc-6wtX zC(YQ-{kLdQbJ^GVw(KUS8d5vrR_yKxV|z2%M$}( z8K^rJdkzraW{ya+N)%kPD3#1)Ew=cS1S6~u^3LV7AQ0Sss?OYva!bD&n(OMVGr%n6 z&nz$+ES@UjAgHVd>u-M#C4+v}Vqtcs>d&$h+WUr*(;E-c;2$Q7}C?*&WT5;vKd#bmLaz@TrR?e zGE@*r7BX(yFQ5KXwJod+L`^|zoL@u3_4#dd@%Er!J0V&ao<1_@ zp}-Tt%D^m4ecf(6l@}34oL^X(5h|ZD1+1&M;m9t@ZzKp1>r%cy1czD|lqC$6`i>}# z1vUf=Yp2!w1Ow6=9}N_2rV^zGhqV%#Ega2J8ZBli?A4<*@r#z%phr-w1R#e{5`^7- ztiB-&9qlPce=bRT5rKl6o!PtEvx`%PN)$C~SQhklw_j6}gg~Ts#jKKH)&Ovw+jDmR zPzz#iFXX(ZA6&tY?YxZjIX;V|^K7eMi<{kxn?=qC57)wO>kqwASWC^p)Ee|un|ZT$ z(1zUgJv}x|=fO*mxQr?@F5$ti`L5<|N!uANh)5|t9kiv~o!#HM^H|2T6x?AEjW`qm zarb0n{d{yw_%3DohM3eG5~N~@tv7JOOM|Xk{s>2AjnztfJw-(z5VX`ohqYZx^INqi zb_W$a4o7e9y~=&#X@-4lpC8kE#t2~Rpsl!2!BPzCt$)yx-IV%P^j@$ z7(Qi#(lntcE0QqVgXVt`SPtEH>s>jli#ZCbnb35BVkcT>QC%a|@Mo}vXPIPqZ%m$W z4ym_h+qZ^IT6I-+Sx`jJ@9@;ioHz{-9!=lUk?F|CUP1yeh3*>MFObFeDx~PPd)B(n zFa>o0)(O`>mapPePv9|WZ&N-44^u=`p5|0j?4j5N!y8uDlkc<5 z&4s_SC`l7SI7zd+rExt`5?jIgOEqjKX61&Er>&XDZg((aCtu z3&WIy&$>g(Q~TCa$Cd{V+`*-Y9Xoc+v8BAoj71!heED*tPS3M_YLN9fAuVD4%HoP1 zF41*vEI7JbvkSH7%oKFjm!{So5^c6*ivv3#*Is4K;5W8V|6o*^ApU)}_k5hXY^#;U zV(G0Y>Gk7w(8fYU-nn?$hQmWKGmdNo-8i(UtwGqVp0Mla?N!*sX!KSA`s;3`=GPbA zH@EO^AU^iJDlcd)ys*A@{^O*L3C>6~EYUpNJtaKXTlOL^S$?dH{1lZkJv`5#40RsO zoC;OqMRBe4VxDW>-JC5+=%N6vHY|Ug;>QEQk<(=AngUzKT37SPy3Gb#b5pDAorG)7 zY}+rgYmk<0Aa42)UNV*NZ=_-?%du1jy79S@#29DfF82lmMgniaUa`G>UQea{jw-v{Gu`Yn=Rycvk_~pB z!RsPbrBuk}5#K|E*4==*uHj;K?La~S`mP4rfJs7U=O1fI9$*s2P zokGYFUIGiqMMr<#x3r5wv=`(BvlZgFg(*bxBM zEg*9*H^vUP*Z6d5pH)_f=r5QV**;Y|PfxRbtb4Y5F;6mc`#00TJ>m!iVr~0ZYhkYz zGh|ORz(`HzbZPfcqBM*5p21gSpVs1W9;#+!rayp)P;uz9K$}wG=vOn9qPAVd`eMf; zdi&ieL$-b7M^`Z|<1WuJo{$|bH=P3Y^AL9@MQ8<(H~|rhPRon=&ql z6zm7;>6A>ZgsU=H+LnQuk2;VGX3`k1?oU|T$f!M)dKdHyRRKZuPHmFEQ!WqQketR- zX5H|VL7@A_M;-=rh|OtJk2iT=j|-x;JTpp1fOcrIXJu5i`SGVKA+A>GP@mP=qsI>w zd?$Sl?M2BuM=u2ZzPY(qTTdr~GGVs5CS_I1Dhgp60}~yIrTKS2cYh?ih}1@ee2$Dm z4XKaD>Zw6FpTx@AS;r_QBW9ed7g7UOhqglb1+UieZTJ1`Am&kI=$11ixbu&Nl1q_p zUc&@wt^k^ ze?%N|fV68`FXVwpu>46pTQsxirk27Hq~~}X1;cL>k1g#-=c`$-LyFO?xv_so#v4|5 zbq6Mry7}de*l+Dwd;VTMY6CUd!oh*X>6AL}%7SOC%!FEu3j;jTWIgwSZccb`xaZp~ zeiL2m$FTMv(;;?Nj7Loe+SWn8-dE{0XqdVB4XTy1c%lX_MxHnj#sZrQ{;j)6UtJHNTHcV5M?ig6JX3TnVx8Q3kDJv{XsVF=vu?{4jS(w7Xa7S4%rTn7q{ zBh$mgzf5l@x@#R-ZB2`Q#}mL^Eh@UAI`GSj6ZW)eD3#CA(~Z_oJsOuVfTh>L!yj=P z&1LGIxclJX(R0iob%*t{GdEQr3H;fArFZA_IHW3*Nn0T2CaFSiqvA}M+lTX73z=sE zjJPMwuow(Bo^X9aBl+s8Vt8G?Ull0JZ=2d>vp?@>lBMsvJbdCECbc$UMt?UMl~f)< zVqbUG9M?ATB9_RgfpyZ5>#0CvAz)w3mqJctAuuBEv*5Er(`VUFAU!%he@^!@SavuG zwZil)@ z?ff#*@!O9df7cbmk>r;s!Q7Crc-K-uAPLOErhofcC6&%Wi05)g?K_`MNT51{x=3AL z9m>-&n?R7gKIAsQKBn9vKO${aqTK)&<+9;)=*n;Due36vDeLK(%J6>MxoiLO0UZqV zInEeu)0e+lJL0riFJ7{B!HsW4Loi-oEzm;H8_TUOfo*hrt|wk`8(s(yrlAf|@IDxbp=0a_(_rAM99{kOKrp!Q{exxKf() zlk$?QtF+?{(j%*{M}^hA8}?pQ-<#leW1H8e4WD(UeYJEeCn&FV@DKk<;S&xp)vZlG zhNdQl>PJL(&pZm01bO{i%*kFyUm;q3`J?IDLmgGugY-arJ-OXFbeR;})G_ENz}hAT z^~_d5u;xDv)_VQxA8-P2Ddr_t?#kf0gl7~RAuP~R+5`p>y}C0a!e;n0^LzpQrjOn7 z{;pl?@E$wzgH4lz*7rZxpA7#&VEF$*a8Pn~bTM*var|!x4*$thV)+M{{O>R)*#ApU ziT%Iulo(k4mzwJ}w*G@p>2q6mCKfM$XtjlArycAr0M*0i|0fG!S!nzZqF}gRS*@#! zUEust%!+x7i#NwVQ_p&?J7ed-EDZnMuS3>%bZ%&hqMiFQ`SenGs?!}E;u=>Z3_=Vx zLaaNz_p4%>?yv0bjGy%IKRo3(8Nv^vjUcdVkHEj7ZhccOKOb<+s@CmZ3=L$yaFGAuAeWhs#oYADbK?X9R zfc-D|*F;xIbOsSE1b3J}+(L=S>D(2<&E)9Hb&Il!_68b0*(oqFg0OGDj1|E!F6&M$ z06OJT_m~1v+qI+4(qJ0F$+$@dXxF$E0hJT#;@2OTbLHC0;zuNrHnL9k0WFP3{f zkKx^XZW3Y%2APX^G;mDt@GZ26CKz}|>PV#Vaa62j-OGE0gQ8Ze$2unXO9uwIv*00< zTIP|jR9NckX{VJTP{`IFzSV=yf;vu??2IL5Mh2t&!3K=8y@)lg<)j9I68)qqRCQ(PjKm z(8t3#1jqT9rDH%-$?;zNbb5%Gi~ zBR)feaK{Y<0yhLaR{+n6{gyA~+$|u~0M49SHohP4o_}HV)7#gubdg)+^#Cz?&34Lu zd*M8YQZb*+A~pONtp_WOJz7PES+Famz+*j4Qjih`p?{+(sR@VSlr-*C^AT=XGX5Y* zBh&&(lyF1Z0v8f2ppF2f9}ycH$1BWg0EJ4&Eny@+^a!!YxQ}1#&mNy;QNJuX|CGYW z*wY%%qx`d8LVX>?r;dIsk;lm+VxGjUr(Ht>5_A@P@TKzr&tSHPS4GLeYH{MZ z-}L3x=^J451p^Z%>j1sL8|JkH-WQA&q7O_M{S^tEgX+-YvrH;-<)LCYhXq4I*fhbV z*GZlu>VtZK8|2>?kdXgQF|23B^iLemT<}tCT`BH$GI!BZpuLRB9+KbB?++1ysKZ? z$(dSUs~MA9eBZiYSO$Bodu9h$QfsWCT1;a0%bNHrIkqPUwR z0Fg0ShpT@#;AXQ~Fu0@kAO5)(*B_FLyQ4$HOg6XV0;)0Txy00}hriVqfXJA9_1`GG z{8jXNT`SoL4|P5O#M%{{%(7m@4yAHdtF;M)b`G&PbmLkMdlpiJx74-_OdV)jP-eG^$k{z0k~)@EkUAn~ z$6Vh!1l+|u>3=Ecdj)5z=m9oWwhoMy$Gf8i3FOb{l9h6W>~!pN4s)T6+&1khR7GuHexm_ zs?f1KH|5I3<8X?s8-$R-AdQJ2;so!B2&)LG!SzD?{2JTjs0t0Os#YtV%KstkaG6!Q zo^)kj#&tCClsTQ8X8)6QZfWOUdu~6u=RO(f>qIbu2JT`f_svjxPzjI9ptvttiur=H zJHowFASng}rv_coCZ0$0*^oWGTI#$F2GM7J09U$$Bhw6ZVMO-=)VH!05F7k-yIgl{ z4FaT-lTv8l+|7nYWdJQ2{r~QQTuqQqP9TAF%l<_%JA#IO!vu0rhvSn`M7i_+>mMFX zE#X^v)|dAk`_JVz!Z;yAxS?GqrbLnwR5TG=9Aad2A}V^*TBA_Lcp*fK{f6WUt{Ek2 zAg9KP8d%lust2)#M~ki*dold1hp(4eH>HNris%U^HI!JVa_*$3lMc)=>7K&SdKwea zJV7$0c8ZMXzN%JJCt96z2jP``L&(r)2-m%8VRvb;V$ehirRFt8bxzF2{Aczzo8@k_ z!kDlf-lK^ioB2Y-O%lN1m!m?e@JB3WMSxU|nsNijN5ApDZ_ma( z9D~0&+#;Z!s9VJTQ9UOkv!r=06@~D;Oo8N{6vh~0DY;ki-<98z>Rp;T^9sI-OCJ~f z2XZ^cUQ^VzJa)4q&^!7(qzeyIt^UpR%uu5|n$}a7>XF=+_;f2abH$@OfhMC@u_k512+suW66ZK$nQEuYrUSl&!t16gzL5@8PwGs%a6>P>IN zQ;Y^k3Hq);wO}y(aXR6J-qF~qIs~i`PlaY^gmt7 zoJAAs>pFA%sIc64cP)4pM<-hci}Y0q;kguR9biDdhBe`pI?JB{%cdQ!UM|0F0#Nq1 zV?T*)*18PMX~SW;Fc`NBnK<j#am$i+y}-Omo%o3Y+8{#JQi$c5 z^@oLyC0R$u(CeX;`BclX^O;^gRkq|4rBu-6)Uyg_d5~t)C}l0ORuq9dACaqD@#lEZ zQ1@epn3QIeey^TmF1@YY%b5{_nt+Y@pcQuI%#-L%pnh4!0rM41t)mW2PY6m znUGnhM9QQALx4Pav-|#^WN0%{t}^hBl9)t6kqnRz6G|`M;{qc-WW3NkGM}7iy3>VS zk3SmBBMXO+Cy|_o6#l-&y9vl?mDZXV@Z>CjA)YCcN(2*XkA&iL*jT3pT+5e~2gw7q zjJROQDek|{+iJvPnx3BR$jH?Mg`4!pIJRHwH?Q4{&{5ei!rU+8u9E?F3{_X5*#0nN z(`;GRuQdGvG7=HsAEMWGBY$gGK|E0~YI9=POy#3z?K>eK4@&rck6OfarMgvU5bD#qp?R=;vlTF^UuFz)b2zbZJeZq=@RMfZD;Xh#{7Xn$lryW{ z!M0sAWxlf{aS3GYfc|i4&`!Fkyak=3zEH4cWn2oX1qcGD=(ezA z{Bk-*%S?`J^v?Wto_&hD_+BJh;$dgv*!o=XY7h&qB=x}US;le7%HLC%`!HS+twkqN zj^FG3Uij{cz`(smxmN_fSp1rxRfTE`Rt}&e@$T+qgF=;RBS0&cwPm-$N4BO)d{1z&$befq+2n|%6!j`V3t*(D z7ZZ_hyW79sfz}1P=8$<84=W?csa|Ql8S?8wv}&8YCG9>~^@0?hL$|QRb6|n*!g3L6 zvz;b{)rQJO#Ks19XkrIHbQFyQ^#LkrvME|?Ahp}BoT}X*OtoojX(%%@f(A{7xEpdF z^+;$Qk3T5S@5<}*-s^E{L(Rf$h4qkdHPZ7?4S=w4QLrY?km9^?eVSDX`03Ky)>Lw^ zzDc^P4yqoXpe_V98>m5@++M=-Q>V6CH3&>`+-pLyF0rlHd-6v z0Ojy1B~da)^QJE3!O{a)AAXaG>C*9A$)J&2Ek1`%Dx|^w(wO7{sxOz}QD`c!k$A(E)BY^+GW#!8Nh72?QwA zwIj;VUsgM;wIO$ApVJb%3i*~l`bAYZePF{(rQN|PgLQr=4mo%(FlbbhVaA<3PLL<9 zOe7-no;|5>!5t^R2!wf!<)2FDBHfG^qQ97eJ6-e^Oek0t@uV!WEiEjl5*2V$q*~}V zxZ?^HxX~I&fR`eIG3bIC=KLf!Xj(X*ZMMQZWaLX8!!NZ+XUm16=1!hTl{Ir75h_ui z&wK{ZE)hq}@WhUEh$&{>7fZbJ967{Y8A>mlT3JXSQvsNxbdQ3Gxb%u=SQ$rScKgfr zjCFc3KP=*o=$*}znM|5`WWS-(L3=K4Q!}AFWMY<8^T+(5u4*CW6q2ck$)o7V2CmDW z4(S-OQ`iT1N5=;^WXHSHOOt!A*Blr&%P}vshtb_IHZ*6FH#J#@q|{*R(Kk%yT~8ff zvHR}N^&Pk!{Q|NjwwYd(albos{ww4mu*KSJD>j^1o5gMu>y9I=_W}?Wq$P#)9LqfC zt`tU)Ng=|<=^|?a1%~`UFsVK>I!%et4{S@ks7`Brf|JNXg54$@Io2^x*jcD`nX;xi z%u{vD*2w!OD!LBBGGPtS(wmE!!b_Codo7?dnL^vFU1JjT^r{AOD|WE4Zj= zvB#~?Ztm|ew^X{&iL_U{8Rf&SjH9!163EtrfEKn@2 zA>QK(pqw{tU31G7h+O&esyBaXYdSQ^qx(A~Lp;m?1;ZG$F2F)CsTrW7-`GyO$>pNe zkr$F0GHew|&~0qT({RSZu&tNh;`!ma-N-E{;7zunH%2p`RC?0`Z!fvN9@L_d+{Nwx z_GD(&y4UhG1d>wh)lvo+D7~t;yUB6OfZ;PhoOxEt?TP^tVbWktzx(XY+2`?JS{@e9 zHTgi&4$}g&&lXMOiOz@y5!~h9 zoY0!~YaBGh1t%dL$s1djW(gCsh?m-qqu|W=%|U1zh8-2tx9Kbi5KAvkNv{v=1{jxp zB~RoZu}*n%ROUkUL_5d@&=wsP`(ZtU8=otgK2)535sJ{QQxgLXSh|Sw+s}5gJ`#es zHWN~n#~cD);Wl^Fz%XkEcgpzstAEY|PEUlgzA+qoW@DqYu!FCon)43!XRuzdK8789 zjCyf^{(a5?;D#Ra@c(7-A$7D3J6<)?kll8~P|9=Ou%8sIF>)af#~V9*jlIBFGwnkX zSDCx&_i8fAl_fTm9OJt;172P00Z;K zx3?s%5FkvW_a9x=-+K9vO?)7n15o^}>o;iO;L;yNK;50~Rp@QfPGe|NqAV+1WA9e6 z?o++oAccE}zZA<_pt1Ux6N+O2^##7r%;{@NpgLK7cv1XBGXKNRSLJVBpb&Yl9o zOOL;m+J55-N?s}Pf#Oab8Ch{}gNM@_ZOp2C^)wwhtdCZ&i?JZ1M zIqkv%X`2m_A@CdN_t zt#nnGwou_9<>*{wE$9rL{?1U#!HZ~N;@+mML32;#j4SX#4SzKst7jx8`~a2jpm#T7 z-R`%Lx}mCsV(Jx_z5_`bs`8%{aBP999>^9#_Qh%A@l+@PNd~5SH1gbjt#!hQy_5xL zrzC|Vj*2$~Nb$JclmvQCa@75>?NKhz^DXG^QI_^m;;(UotNK~~YT%#yil5R|%~8_l z^zD{wh!1X^?;(?(Nt_s4wahETau>;K8~8itWir4XX0WKKRq1C4Vq8?*RgUpd;4$y6&tBx0cnThO`AfsA z1$zp-^}@<wXR^z{wh%oTf~c!Z?+zZy2xO&S+g#s--$wq_?Jx8mXHZjtEnr3*6ghuB-4}wY#8NGp7J(~u$%*@zW(GaO;&m&`@ zk#l}7`;5Ep8lwvEcCTHV-n06)0I@?;-%lA~Z^J-wHJy|d%^LE!fuJk3(2Or&VT?oN z%wx1&>xOp1H4wS~=Bk+p!=d=rTpEEut_y+~1A92wVxQ<>UVb|Zqk&^x+*_rA7(cQ= zR#sFPP9Kv+Vz2}vPQ2b(PUmHJ@%S(hi~KP>%+ON59@9@Y9lD2`*e|zs`R4|20AI|( z8nO6Zx`dmBko6gZMSfQ<5c^b+!vU znWMckUvKR-LD<|KX8V4~&7NLPg{wJ;&OO1zdTe33v^6)4&p120pWpDZWV(Fx_bS_% zCRCF@8uwoFV0e6aIVzdQm`*cH`Bm%y6XlqqXNJA~L31dDcQ{Z(54xf5ZK_xGu5yAk z;VQY(PbF}aq8@gS!(x47{j|r5N1{Gw9!u1ECmc*?RBIpD3CKB-~yFu5`^BMxPz24 z=&XYV2G7>5MBi*xV?u0^k0bLB&oPm?noA_*I7d6N7XDs`t+PeOk1#mT0 zRh0#yq=YofkEmi97g!ud9f#j@XaOS6>hL}C6|Qf5Ae09*_J`v?g)igiUxv zEr3Vcx(a{{?&OctM8w1od30~k3^pH#15nCOh0FyFFban9 z^g~{Kb8-gZ_ybRkbb*y;f&rLlD2b&AkpG96|D>sN^sgFh{t&pYl&F8=XxOh^UR;0Q zd88tiAr>d4qBXVOEnq*GJve8V`mfEW-Fi7+Jm3%XrdVYN)A&yw7=R{~nwpp|8X7!2 zyr~o$x|(Tx6KPY?4&NJdb+rxv9)X!w|JVwqIrvu?1{?SzJf}{L|1ag8n;HPrswqD> z`rcm>i}}9n7kp)35}$RQU;KJ_BJJPR=1(>Deo%-ne4A=%?Vkx1c?AW4^(=L+b|747 zoM~Pl9U1K%ZGh3=b>3Wlr%XRU5FnE6?3~{h`7gP&$6u?*y!jCUe791&(?hd^`&%y4 zgT0f3@4xNud2Kdej`j_%4leI%A_V<|n{aRFJyU;qnq1$;O36z}sAx)R2a4|qxxxR4 z%!6~mP1oku{tExrko~FXhuT)_@yWON}EACgVF~D$N}J&W1>p! zgZ*8Y(qr1vdjv=H<&7l}^ACy0fu5h+1bTB2xN~4|00+{}+41+~{oT3eQ-iSP25_=s zT7$3fi-sQLTQR|<<_GBY%ixRuD!b$p=Kq{4U-X%-;20m6h1Q2_=GRvM>hKj0eEH3j z_-xZ%v~_ir{PV)U`8#sn2cEgEwdL_WH}40%0Q4gwRR!YM%GU77me&&1+(I_5J+iqn zeWADfGevu8`B31;04~L~H`e-P^zr3Ze8t=R>F}fe%hL-WZOyIyd-54yl2HRZKLXl6 z@g4{LCNSJb_@nYCn2xm&*WCbn-z=h7%KfJLq3?1++1bwBg z4S?SYZ;%Dd^NR@WAF=^>^pOB$@9gvptnQzU_+v{>hVKu01n_0`Wi~ScrSI?z@ZI#2 z=db(&cg$yL^i3E1>!A7Fr0V7ZwgJ>qJ;Nt^@rzbyesE!ZT`~4Sh}sJOId;hR7xOC) zO!>Fu;Ww3r7VhE6;K0}noW9}FcN=4DY`dYM;qmP|PP@qu7w32UDQNOn`!`1u0O1Uh zSx8+2Bm**dvzR3$?NbU>R*hfjo*_?VAHr&il)uJU)(zo6;^aCWSR^t~6R4>?iXSTP z4fabZ5J$4PLa^a_msKmNs_bT3MZ`_!3-udPp?G-IktKr<+>C-2T-7;Y33uuk?JU`G z*0hDRIRSt-E=T;%#hGuUUaq(3Z*k(KT64{*kQd5a6N?eYmVrBM5u!5^t(;q6S1+Hz zh)C8`M9ushCVKE;^uz%Bi>ncyDyF7ExG~?Heo?Q%h4-})f)V{C)_^uk!t!{?=pg1` z*tY5EJqf+KAmqNg@T^F}@lT@mpMIc}pkvvGKLyB)!g>9%T-jIo?vlE_qm39PyPE*} zv6r1L;U1J_+>CD}@s=QD6`ey*=<%`Z^&Lzgx~+Ma=r}g6+|aCGMNll0ph45A!Y;c& z$MK0t7NO~!u-RIc=70hRvb>vjO<=J0xI&I97B9GsNFzy~AJFY=K1Qj*kZk@QuKTP= zV82Zr;g+c=5%s$@tjR*0P4ChR zeeX1yXZ3xlA)>POB z(-~{C-Iy1+rX|}`vbTZ|6&H)}k`$x0rs)s_vI(!Sdhzy?xO-XHn<@GF=N@;w2mwrO2|zwi zszZ@*@3OjUdR~K=y70a2G_l~Q&d5*Co$GtvF@&5!s=m9JQ!({3FmxPlC0zM{=^Ye5 z{z^4}5{bP~aX}ZUkT|f%^rR)z-OZJod1A_-ny53o(pNGW?f&?sJt9^3mjIQKQ#8>@ z;C1yP*$CPV+PxxQ87}4y=3{*3v?XI6MZJXm+1B$ch>>sKHKcO7^RW3qiem&xRVU|=)_%T#ZXTn<)uN3_`^Au^l}5xBw`dDjh0 zCB=Ww;`msep-b`aHb=^)kC21#9@#frS<}}u^a-2vuGOMt4_d1wB8_C@Y;Lv74hqK0 z+`5wfV@^PKD}enh8-BO(7ey5{WbWi#OMOX+Quyi)wzji+JP{57GkUfNkA+@Y6WPrP z<9OR9rt)>0h#ceIxIUc4XW}vI0X=rBtT^u-o(-?9CND*PlHK^kCFtGZTBtWVl34rz zd=^&&LrR`>{Y*%43D!y>7P<3ERWXsg1ct83vphtI2j5`SOSfyd#>4d(gp;IveEVy`y1zzjo)@wfVcNnLGj7W!ExL7#%9r@^9iixz%;(`zyd0ADqLKfxCi}I%~sRjLCiC=%1*B%b1XcyG9m`p39&X)5*2T~ zPGY5y4KJ^LQHHk1GJt@Iz~d{Wd9V(oT@+rvnEU?T!;8yV-bP-$Wkd0`~ z#*LJXA;`lTZc#o-#WWtaar8dYeJcgo{~up-C_^{5FIG* zP-n4siDD|Ho}COgb`7BrY_iHS)_UnJLK!my;bB)f-NoAro8jmD8IzF^9V<4U@ziPl zmOGs5^O!r;6a;0){)$P%q5^ss=t%BKdu3YV`(SHOe~ByOOx5&^<+F$9Nh8W-Q2QmimeAUPeHG&B!Ol@EH=$n$(XA+ zb+)^No`}`6T|8RJ$-X>BHEqm$ir*YP)|0B?6u_@1pRN)v1Vdz3(1T@EL1a3jXcZD> zr8tEc9soj)XgXYAUKx)amWJXT-QB&M+S9{E)i*S_n_BU{f2`J)=awzeB$dRYu-g?@ zfXm8-R05S2VUF;!VGq)jJwTx>DP-sO)!p7|nSg81GBZKH14e*74#_MyL+0Y{&D`g+ zea+oBtkUSzoe+*|D~X+5jrH|6uy|%+%xujBsdAGFQkBQosw_+ht`=~!^5Wg}c3{+EtE*J9720CmD zpkt%V+W4T3cBe)n4BPtINy)dHJ9KUS7c#^;RouOpktKdrsR0(``h3sF`$Mk+*P{g)#=0nNG~>h)8{j@Vi;5I-q0DV{N0pA{W{s^P7YX zpqPI_r09@4u8T6Gc#a%g-jt1Z@)?9)=`kFu_lYVW=LDU+55~1mXNfm_K~ShVWAA!Q zemGXOSul&rKlPqc=wnaMmzqzto4so|@yoJoX2_lJ(9&FvbofX*cT_Lo2E$fStK8yu z0n)=oOY1!b?}LtRMQ4{?h3DPR3%3jTDIE1@$Xk1h*9lOlt(XL+vS8?Sl|buGB0+A9 zlB_jm>(kNl^*=DPCrSqbvkY0Cf^qCM6nZg?7Q0Nt=lA$gD9$wL8&zH;obYbwRQP6?SayiilOiWmymL6 z3v0Ln+NmFo$W=m45n&qEocbm3U;!}?$jbtj~!0hm&dC=c3$M5pewE1SoX z2i17eQ!<~U-y`RNfCV-*yV5p8X1OGn2xeq3WnKKiHX%dTkXG?kUqLj{{<&*Q1MGhYdw6RDbKtcz7;ABaa5zeOJ zPDsb3f{U;wlVc$Uz5jp5am?V{U$rcFEc#W2*E-`E=0tJrxbEQ{d=@}) zR7SOd3Rl5p?d}+2KpSUGbA7pt7rv3d#?@Bm?jK_2uS&4^36nXXjz_Z?YAxCQO z4_eS>gD=CcAtB_di4glLQuPD|t-0FIP8>yzWb~WltpZQWya--w2||zQ1(5l-t!(oo z`8pia&t%c6x3qW*L!W|aCiGcm(E&LJ&taP#bT4zXx>WSY!l1SdJ)<0@yvQjJ+!xN^ zTU%=NHqDbEYw0N|G~PKB6Qu@0HN zUA9YfPLn%X`-g~XZn{>BWbccxdXPBJw2_Fu-H z*u$NzR9DCILZ%G)PPx~r_=Mp$m=l4F1s$md28Cpl;*^6gO0(Oqf(urEcD*E!9xz}*;P}5Tr|>ZMqp}rH=fZ1R=wOByG=;~R zc;_-5WbMp0E_B&7dJ_Z{$8kXz+nFA9v(xc2Dw3Z6(U}N$IemNF)5EhOu{CVUZatwI z5hcCj4h!3UZ`TlSpP$9sR(Ji*3M{vE=6Cs=`;qA_l~`<0u8h*+pj<4r5d0l67-fs;i1u>LL%qyDWjg<(Flcy5wR?o zZfnb)I4aM`QSDtl%PhqNCSGXs4Zvl3gQeT+!t%#%e}@RVQu91f@Yu7EsK4vPmmqUI zQE~vKQK<)3wcyv7GH&~mTgtLk7Ei(<&~;IaTcs?#c}ee<1<1_jp+@E_=0ObJ8D6?JES`-vS!x`_!8}Ux;|h6 zgu^|?4KHK#t6r;RLwq>9s@zeua?`(Zfdd}7mOX5-Ffw6ADw0xI>%2gWUK;|`QP-*p z_;)Az(|>n)R-dYkq=#@?|4P*ylA{Ssw+x!nOtd&sKX4}EFWrrBO(+ESE`q9`g26z~ zk6cJ)!paTI?=zxwzXD4GQWTh!^67?J-}gSgFAU0{>!-j{#KmjW z%55sYyw;)rr9W9g@^5DP&auyu`pK74!IA>yx;x#w{jE9Hk%{B;gfdcO$-3iMyJp0d z=Z6c3;WdYjL%~hCSqUxOL*o`kAihM;KS<7!QMulpZdr8uGxDo7;qMD1#Bo>Vg6jhD zuu4&aaYZT6Zg-%H(M?6%h#O|?@;y$3d*?C8$N8CVk?5D86hah~x6k%N@-}iC#3^>* zqPyfnmHK162m5fS1>YgvM2tG*5rfIJQw8`ySv`!MusttNSLLleQAc7cCxq{G1NjbB68k=zP+Ji}aV4oF{)$ z$N1E&SKNFjWAt~$-4Z_xFJ2P5iMKvWi8kfJ`eaEOMQfkW1H0M6!VQE;VKtrn7=yGM z?9KS?04`+0<~$Z&P85w~ov4t!qqMq^%$P)6tll`E^5~ZWuOVrT%-^v~=#2ei^m#0| zK#JKoqH-uOd>``<00L&0ySEbQAZa7bi3ndQ0I_`B|}e5pYO)Gg10R zqaGBm;YQZnzA~(Aig?w51NWb4!bM-k`$A2mEMM~hlF1kU)Y z0;EFY9@8l)f@|2N05F`kM-^l7mF|Z~ppp zKh0F<@JywE56!@d@JSvc0=%>pPrAeVH_p0%wG+37MLiXf3#lU#d17fUreONXgiTqI z(KxI(Qo0uz1Z0qb_oGyfTpnUO0cJ+%zr(Jd(ps-gh#mR<-?F*WXaj36R#zjCPOw{= zpk!$vwY0M}e{!}z;U_>0e<7csj{ioc`S$fY+(M{CKVaqQgL%-B(V|pa{l#n2_LCH& zTKBJr>#R*ah=)BGLlC^J9sNWjn13kjB0HRbW_7bXw?q zWPWZsT$4n!qXTM+VM3l-E;?ySV9#?x^)&)trW1+&H^eFhZ!Q(gQUj)YI{I~~kll(< za~TpDB~wag*_&W9Pur_H&3Qk1!yn{k=8{o}iq4GyXG4Hekt5EY-riyBLV8j2zXV~$alr=m*&K={5YSW?0H{Ux1xATe6i@O0#;AF83m zMu+}#6i?ssplqLL7$L3ki`hQvihuwTbF9 zzUOLo+j|Q;PVL1EriOcHTW=qc)B;PTq+GBU49~YUxX_rhL1gUFM^WiUAtnAYHhphX zf(TJ_IIl>1D)4h4e*hyzcr#V5OMI#c`FQVA;lAs*S0h89R`ubxl=)dR&EQG^y)+|i z$)t7$MWu=grdIzCA#HrO;BJ4$RVF3B1X^2%@gne=noQ1LCrO0-^fneKHA&|v(_WhmA8sSd`R!Fk-BibN za1l@G8uTDRu5iETro?)`@$Ln}>%~%^xI$}apuXRcXxN~fe*Lp*ywtJs!F|<^qZ*uw z#GYMP_%q6^zk-7G#4R}?KcfrZY3ls_zDN*Prr}yb)51?@$4%xx%1hH`HV=)c$R1?SgWpFy9{Yc(r2KB#>Cr0>8E!H=}5k&9t8*z-g4wJ=>*Rb1_ zDN`Z{+Js=~t!IA29syS98nE`R)uGaI0>1@2WerfQE6s`19>Lqz!w9pCVAQMxTM!-& zHL6r50m|cr)gOh~TI_LqSfXEjXHG|1%OuHPKMI$rKl>!NMnxj)2P zzdeyNY-eDFvu+y>PZWmAZJ|(*6%pA@Yv@tnI^4{wj8Ehd3i%CI~Wn$3OF{rp6`@J?@lunTYNXlPh!+r`pEkSqExz@bzhWimJ7lgxwxVD!bN!1>hhC&#Z74RMnOYA9ip6iISnt~T zR6GuMJd!>1%^cTH$s)F@kwGUDh5IyAz`7{WJVMA|-}L%1&s)FeRO$-*UW~84}+^G$?XbU8nMnwhzVa=gz{wEJwNv zn69U1*eClBuq)eCWohy->P>KN_uyK(!KGJ%d@HZGNqGAvT?pRF4ggm7Di8j_i996FZ;CD1 zhmO@tr@+2ot=#r}0bn}xUA7@6wfjVm>CMG*Fo{f)mDf*A(T1?g2^8358A68vU`=a#A(uC__8UL-E7#4Sa@oh5dHP-Xd%L zBR3a=kH!O<%ppC9F`X)HaPOe5|D&;U4$dTa_x;AU?QHnQwr!hlys^0(+qSKZ-Z&fE z*2dXzW9#InTc_%tTjx&CAKg7&)7{fkU5%$dU(UgHBcfFe07*-nLNP1$mOx}Q-@f$Fr|GY3uanDvyzN%mOmew=FMkr+^6YcoBPSJSX2U%EO6Nx_{N4D_T=ZixyFv`A#yvLC>E((%yMdToYE|J9Ae{UVghst}!_zTL}9`Bo= zh~PD?Lr)F^gSh9D2biU%-g>wyz+@bV54yd#nilW;v{kz|8s?QWd848=z^p~_51w?I< zW=&TOgsz-+XeR2nv|KJ;nGR@oN}>119;$yO>5=o(z!f>c6}NZbMnLB17tI=O!H^0) z5CTgB`;~T`$saVkGZA-h4XQxdYoJ2rg>6mXTsqDP6UvX+Ww9^^h+#um}ae+N=dao1btkJk4jS< zqPQbf{0iF6D)GhH-`D<5A_wV4pcHtBcd7AloTNxoVL+=7k&f+!XS+eb^_Lv5jhlhH zV7>@yo6lc5qP8jV@pZ9>Enba|Ntg?=JRtKOQYb#R{SE8g?ux#j9-=|6t?o8P0k-Kr zcw$4TN}^rMH`0ldDT#%C>yov&n&!6i2fgT&Lil~huHuWAg7N5Y%mXfob%6=@!Fs>h zz-F(dtZ%XG+&6YI-PtkO)pea;SX^>p_;}S)#S>sv!JJ~gSe&REzC@2wI>_X4*xp)B zwnWs(#%O?}!ic>vAS1r0Vws=-JwK8~jKzpJHNJ{9!Hy8oYSZ=bYEfO!R-Sq^k5*_Q zwjH5QO{3RzCr#EWOmD@iXl3oyA92paMBT7gtFGS2G7jx3xZkX#T6wsU;mYkY0PCjA zCg<#7xA^V7*%YuBIk1WZ8i)L29KYolOGz9Gkk>zqaj#;<-Q!C4steL;o29h61AB$k zEeOHD4e5PE`TXy&g!NDHPV((=Vl_^B4nx(h>aO?$&^~vY<7@~yk$#mwnjOYaBqny2 z4WnSQcB(F)NGQN&&2&%qwb$V&2~9OW9iToPy-qSI*Apdi(??%EWdvhNf~vrwh`33#!eQLQ^6sObkoq_D~~>xrP!Utzz6UQ)ouoWSMnq_J~gNd;X7DvdeTm) z4!lGv(`>%(D4t*OOTxon_FBM?1*_oL5%LJYb$CFpAyg8P9ZXIKa(MZ~GU77T0ec3Q zeo@M{;b21msSXxz^WXJD1kYn(c{a>mI49|8H~An-RT|y#bxePACnLm8ZZC4C?ENf~ zxW6g3z6W|vWq5isrq^cg{W7NLOPrNZ=IiF zZ)qDr#inB|Bz$93GP)kgISmWRFMWmbeoQx2)?IsWz^-*2+S9?51S*Bcg`ya)hGK4* zti$V4i4Gm!8h;JjH6NmgnAdxM6G9Q?9vO`WDdN;uy5 z&p(niWPxTRrAqBze(W$@l%i%UnNa8Te&v}~n&rL97dkh=Yjma}*M8<+7P-BPt7y}a zW1X?%iBI~59qyIVKelcS!%#2DVT%k|loYkcwN-WN`D73BW#HTc@>0*wt1)-xX&*5R z!O!sACzUw$jb(VICm-f6@t?ot0B3Tbune4`!*%;2kMgxgT2^`6X(kdg@P<{lZ&1?` zPykYekI6Z6hHj6eVhE0_!qWmP3OBE_qTh!;P24^=QJ(4unSWu)~jf3XX+l}kt z$FF|v@v__x!wm%B#Gv0|1`dYd2~8(9gG@ZzEM*0|!y0a+RCLQE5Aq0$6t;e=7DRU-q zdUs?FatVcJ?E#~9t=JSEeRKJW_+d`@*pA5rGqL~rb^K?DCh(V!ACEp%1ZJYpwsPK8 z8S#FH=v~^c-_oAT8c6KD=30y~w}VI$*oOHhKN@{LH|v&$?$f9{<_J!|klD63g{^*F zw*ZQvUH({3ze`t(l%|V89|RR%A6$Qx4PT*R%7sQj7`hV6JAwB+H9>j7W}dsOPfKR| zKr+K3op{o`3(4n%jA#IPH3k$oOO}&5?Dqjp(UbVvC~br~LV^q3GdM4DO)IGLy zWHbSYYDlhy=sqasB=l?J;3Nk^>Ns5fjnU>x2V&bE*KELyO$~IxnO=l$Nvw&8t=Hhb z?`L`0JF8~6@Lt8Bu)K@&(tsRyIU=k!@T(1dTnayC9!}k*-#6Uo2`u9oBQlVnCIzT= zJ7UQW2G=NeE~5Y3?%}XmqX0N%iopGOy7#2ZnN_Ncm8qQe%S4B{_1<3<0O2tp%E$RK z?Duv%2PUyl#v_g^0R*?vY%?yWHNMU1uklZ3ScBK?U6ZOseF;a?4yg#-H4}W_H2jDv zfq%47o~C!vT0#{#13i_zWf2-&dsjw{a@fiet80V7UB0b*lr3Y?Fi#Oi90@&K@qJSh z#g-}G$Io#Eej<0e4=s!}Do!>w3XNS>iSiE>6gv;=}A$n)5?LswtmKpyT+n zV_hhvFneY*M8M86Nw4!I4Deico$d!TGpNjP(iI{)LOv7-sLARlkWMVAW2m=DLXViT zjZKh&(;XfTft69opFX??*%D6e>-|c$j$FH0b%exF&r>ytW@NZ`q z68{n)Z`x=mAbzHUmac`pJLbZ7zv$z0!_u9hI8iA?Q6wu-cK%aStRmdI7()xUCXlI5tp6=p*EUdU1Xt!z%aST-t`CTRr;iR4d|M@ z>s9P0oQq7PPM38zB`Y(6&fwIEz6*&$&P$?~1qHwR2A_{_-w9j(Di?-f`tsnCb_cN& zdctFDtO2Nd4o4^nJb8q1r*fX%k2|X(omN^Z5ZQFrhs_P~;qp?)0yi&_k$#-Jyb^Ia zq&&J3m1bdcKbq#o}8XQXB%TP)A#Nzi}PMj)`TygpuvwR^pM5`#rTay3cM8f}E zh`3NPOv-Q`eEPifvK?#BMYqXE=w|Sa$HBJ+Sd5%xo^8@9G#yG1)wNnndnQ_Lw`vvU zcs`eJW|qm+P32`sanZ^j`(^-e80dW``_RH$PPH#LKTk0)N1O7H)UZ-`bB~xzPk9c^ zQc(KHqNc$mckHYc-QZDMNQtp$@^8Wx5LJ$ZAqVxj-OG@JU-2-D(=^u!x`X&TY-6nW z=H$p@9;!p;WR6g&hxoKkv`wlUCvVD6NA3}6#}(v&=vtQj&9fP8xw^8}OUa~nn({1T zJ6QG%rZ$?hXiO%4*k=3FU&K#489B87TvTl>eGGGTGRP#*(GVY)?6fw})F0E4G*N#b zTa3?i7qyUJ*f&++pJ?Irgkl_d{^DIu#+4e1K#&R;NZkRV9<(8wx&t!Gx=O`zg!bYi z36MfiBl$av=)cpWrTs_<`n{VFEnm{(j&3`a{NpMTsHvHt3M;kNp%8|ez6^+A5IPAg zF^d_}Ga&0UDz19faaQ}*rf>Rao*al<^4&(dnNHkZAain0I+%J%^|dDTYXQnf#GQav z=dJBq0d{~f7{-IEzAumC$Sje4X6a5G>IYaQq@|eMPfOmU*^2gZB_mPORxbDLDJpf5 ze!^^3PpxEi0e8e2Rt#CdEf?f|2ac9D-OrJOD+~pT5V7a06@3%-hN-~HphTOK*bA=| z$OSLu3gx|i@R2MdyzHHxU_%EJ9s84Q|DCjngmrUdt@)h4Bo%){PVPbH@(SFK11d0c zdK>g^{R0Qy=s)CE150FSEDkUvy}UQM;+NUp27ZsKILH*qaPc;heP2u-c^ijg$3Z+h zWTm=Tesno?FWvNbt%jEvm<$hh-x*vDW0c@#Wpc+mTjh;W4Rx&BWD0>lXov^k{$2RN zdZ`B(=M%1xn#n_grdtPqz;84bC|j>`BiflZY$B}~3)DxrGnZ}EOO7m58%vQSpJ(|> zXqr+tnSWiz@3lCnOq!d&6R0aEu4b{-gJ1>4^jajX(f_E$Rb8!Os``YN#Ni1{uKInVu z(Jq{dNLNK7Z4%IT^QSj9+EWtQwnWO%71;A+(cSv`8x&qcA?rtP4q(EV6d85F90X_M ztd^^sZWLBBfChOio6T!cOcPp3e6mbuIV@D2QCg%Y0OYR2;{EVuzVX-=e1&eDXt%vv z%F)4L0K)Hu2v9?={^xLo+CP8CEwf$MuZuNxpUi0?bc|d13D1YOtvY!G=mQBfN+n(h z*P6!d4;~!hlFU6#oKcPyqy=IhT;~nA1Iy^Qq*_V4GsOJD@3`{?bpM#<(`e_{SeROe zm8tSx@{Os8X^Pqf|2(@h_%u1VR2=|Y$E&Fyk&MMMz)K{Hv&Ta<-mlzWhk0MLDp!&Z zx_~6y<5k{i5MQ>z@Afn@9HnukmX?Hp(*5o9fL)>BR(1CN6I>m2)OZ0NGhMFiE^R~{ zjyUlZZ?K5G)F>1k`y=ak#kyI>r7AoClF{}r^Bj|Q#!i-J0Mx1ull{0y`Gshg4ieve zLz@Bq^AA&ef^5<9Js^v#weS8H3qHdkDWthDqKL>Z*<0CWKV`qxF>2f=l}>De)?h^2 zhK!es{q(^?I6@;@C6YkQ*sFcqF`n)SHRfxsm3o3w8#KfJmpDR^79K12r5Y@r0(NCf{2|uHW0YYF7 zOuyK>DfTybe{ri7hrGNYS1?tD>-r%7oe(Ew@n?ZY^{4)8^H_2fK51l3ty!=R>UJUh zRGinRoH&MEMpxK{ydsUFUYmxE;K4TdQ*3RVfYpQETd2>N!blHx`x_S6W{};x3w+xF z$~5=1-#R9kk-7e4rp-PhR}!e~Q!33krXY$jw2;cjM-_a*AlYGzs@bDAvy){hW2;72 z88c969F57XhGk$ly`GXmhV9i6t$9yl0dHGGF_3rLrPYr(mNazB+}&QNS7Hk~oHb}U z2>d$YbID}K0%r^((?!Iq{3d6ItFAOQi8EC1&V9dv~d$}Yvk#m^%8t?|YiJn?OV=FhADbrgBxm+dgqL%FO} z30B2aN>!G^`1aa23o6y{xH&Tse=4{waUcYD zA@}y}>h7Fv6<^$`rD({7htC=PMTs3>a)#=)Z~7y~(&Z3tGiMQtQ}6d?=>*sjV#UzC zzza`ZO8lA9QOEtrder)cRW6S{e;c1&9O#ON$5`q~y(!roZ~4CtSf|Hd!Cc0X>RmH^ z7_&3|=CBI53SvB71TP$_nYF98&|-#_Ozt|x=BwRp&90_a04%!;f1_6Gl3-{{z^VR( zCg8_^isk5lHE;FboM0@oWI$J*a6;#sj5-g(F7~S!8h<@-*sc=VVhEK_eA=AI@9gZD z4LiE~OdE#!|A06Q(=nvc8qHG>hHQf7Ain2Agw{h(U62 zj5Y3Vh84=Io4s~ER6Sl_@0ozM!+^=OgN&8q#!tjLZYQek=d(2(1VxxIt!+o8u#kg)-VM1o%3-;miS;t)H#1rgSLI6&NYV z7z#o?qN37TI$ojJHjf7)SEZ;-ALQ`HME7X^7ng$E?Mrfd#O?LL2Ef$_LWBdXy9>H0 zwm2Fh7uUP`V^E6c3HK~VlzK`AGgnC~d%ZL+dAoz?NZ@|kQu~VhB1!9lkiW#!>pKNV zgl!TM98NV|O3ytKWE8Jkrm*e~yu_9Ytibe`QARQ2yUp-3XgkM*e7EL}KA&&UtEkmo z0)&Dq!9x>I>dtNl-uwQ#WrxvCkGS?YolO@$f;`j1T>IGDlTx;M{CET#Hb}ar&s8PU z)0z4H3vd3Lp~_f@UblwfPBL`+Mx~st9o-axZ)XVYkAnj47xA4eIN37^?Qa#yV<(VG z(lA=KC;VIQ8Px8RR!vZZq>m3=1!Asj$X$&wBn(ERCmwaux6kPiI@HkD0~CdZq1gFD z;aZdxibQ&gN{0K8O*BeYvGW-H5!@#*`9@z(t3G0IwelLLvsZ>IlCNmBLi- zaoYw6E0bK*a(`bp(@EXQbr5_6u5usMZ=wzpbKpoux^4@%h$olzhAgmR6V&YwQAKpsOv=Ik3(wHh>JhU$eD2qJl+X{?U3 z07DVel04Mp#x{7>i}r@|3IPJw2p4%m#As!zi|{H0DTdZaxQV=5Y`zV=SWz&4kd zND`w)a_{C)5XqeGe{<@!Ss|$U&5!u8Od_nbYRy{wC~yqOxi!uzk{d`f9spGKjNMAY zro<+QntjW?Q~9}T@;8Tw!T-&0OuNhUk}k@qi40qw*MUkv3obPWV>rtHYslKseosRg z!X!YrpWC7+Axb7D>i7MP2CgC9gpXKaiytcoR?R}lYtzGT;TwW^q-Uhzh*-?>B{B^))oNOPsTpn z98)PeUd(ekEO=b1;uP#KyHZYqe8^_X(t7iQkR`URb`HV6!QG z2z(BhQEV8=`fo;uww=d*Phhd*Y{vQ_dQez+mlJ9Pdeno+mey)U1j8Z612k85SCn*A zfHspI1tXeBz^ZEoYASYpY1*>&jk}Z~9mN{Wwuk$`E275oDdgeH`JhIe%Co!PmcuL- zcBT5qCvLwo&{X`kF~I;cV`bD*^sHfsF23D^IAET>;rFIA44;J)!F%-{SZG>%mVB~C zYwX@hI7;X0PXYD$tJh!&KMMKNK zM*mmEJg#@&1eTNeDr1sy2Pb?Nn708DcKqp}?PTJDCoIt=6b4B(YHlz7^Yk>fZtI;= zU^co=xQ-^Ng)&C5q-#lc?I81Jr8{=dx%&4z@h~iF>|I|msk7=8zbY*92&6kV=Sfnc8dVYNtY=EB$Tv5ak?l4V^#KrtbK&^BV#pcG-|&Uts2QEN)<5 z|2j^iuuRK7V!h6AI)bP$d{6lH6WOHD>=gD!5@P8=y)k0oP~_d;eP=qn@4YabceWh{ z<1-Q)`h0fe7*DQb_O<>T>#JA{ByJJDUKS2+^c}>M5mTsmp!yusd#=BEiNyn1@szdH z-P^-)4M3`d6hhP0z9cjB3nkB|0#{_Byphe+nGE3S#R0gdxE7Uf+O^~nr&MGfw`Ha1 z)HLqB73AFI6y-*hK}x7Oh{xu`M~f4u2M&oGT%zvc{H*otnlW*2`WEkDe~!RtpQ?k^ zU3z*a_+fal6#&te_IL_@z~%~(6XiYds`S-1UD6YTC(C)hGW3W_wbZsAH^ zCJlZS4cM~xxsVMzDIbajJd-yX3fYECHK8$z*s#z!$k-@LEFC*f) zaD=^G@7@;4KM*OypZ3OTpVT0uD|~Kq@X)|^^8xUAOoFo z&*otMRJo*L!ah7&vm%a>A5E9Tq5X=6^L0wx_jDIkJjVS?EcY+%>nRpt@$6|aYsZ%K zE=gV4D_@c4sZ&Rms`3d&{iMO3r2r%NCy?!`z9xQN?yayhOr;<#?@n25e!h{iO*W}+ z>F5Q*rulI_$ofg=;@jM|P@ctKcNxpwheAW<{PL{w!7`e^#wS@sFUGKc%tRQsulj9!ggzF;y%j`%`L8Y}lJyh5;Lkvvv4{c3D4jLxQ@5R?brrxN+90J@NEt8nciC z5WevSIVbm`n^fL54Exm$PW{bor32@fq_=g4@tbDsE8VbrHDI|s%_b-^tv`-Sj8#*o za`_uNcn@`dCedY8z2kcrDCdz&y#tvl4@@PlU=qWP<7`0gCT@L|t_t6q(+tG4HZJOm zg0}`f;2-Mx_dv@dR{~D%s^AKAbGP4eh+kl%2t|X>Qn9!br%Yh{AHKL!Dg@t z=^s0HX(HknN)utxRnUc&vF%%j=(Y6MF8e&(_p6dp9fC~#v>}?UC6eXgCE_Q~FSSC; z?xJP4_i6I09m#Rj?(p_dCJw9DD4t;ZuY9yA+WA!x3G^`omd!#z5WowLk(L#(8|LYK zqiW{-C*6&N(dJ5wzVc4YIK3DN?c1^-PeDEA0Hz}<`0T(qa09pBa&S)OxU|-;_=a@F zZ-+F7F^OdheRN8Us?j`u(+q0qcB*@IK~&Eet^?~QE~Q{%F!2@_OiF2u&ddX5vZBT( zpig0XaenM@q#qkk$KQ7=Sg3ol~EsaI!@&YjL!S)TFs7#f8D4aXA30J-sfJW{VXj>YE5D zhI>kRx4~IZ(1GSGbO7yUq%m1(tE5@N559Pa>Z}xa^1ty&m6kCQCrJv|5k!b7axhtQ zX)eKS--69X0`bJvj7MNme?XwbK}jlPL?s~vx?XwVSz^D3V2lXXhf9L|SC;ZQK)0Fc z%j=w})awwGqFkfjx6b<57R=P`K19z<=HVZAAa1~F@*Y2?(8`!D{`-V(~HlkYIEs4fSg^@Z>ih--4kEM~2t@=Pr_8ewf82Dv9q{q7keg8z`YOLFTj`4_C|)(DV1mXc zfMb4*`>5;qpvtGFpyG8t_9KWi;1g7O%6@`6lw&H)ZR7h!NKuJ;MTVndGDpEI_S6vY zVME&uo+|TUk-Op{im@E7y5wWL7|f^>m_>r*w;G`G5LD2r9i|%o;#!ogCaWk*v8ZH3 zo;^2P6T1;I(m5A1Mw`Zw)H3h>`Z}knN0p)ZT;|!!FhLD6 zVdY5VT!ENdda|ZUq*8Oi?DqsQwM68is7BQBmFgzZ8IBygR{-?2o<=1K$=|uK@SY5U za!aTk>UAHsja4R>oXViW%@%k(eEKF0ps1Wx4Oe@5G}mXMPE@8pti8VU|Y%&1*A^qpa{ z=VUQRJ5`b7c(AI->zd!1*K9u}2x)(WQ-;02+-2ddomaxl+&oTBWWl6Yk;F~2ts~dj zV{h8r?JM>J?KY}k+XSGsv=(Rdk!yvv_?Fw<*90~zhMlZ(H+2lsvTWu({tzhrzMXuZ zYyVu8uW)oQ_>d7HaeLeKpVntvCy~T>=BtN(D&e~jI9~TV2s*J2$W(_P$I<5fJenjs?V(7G ztOn`SCUhw$c6}&j{e2Qju13p7XSwRv-FbkIq^h9@hR1nZ`#nfm>WNMppE4^Ryi}Z2 zl5|ISAEE$EQrI@mkMxGSmM8v67+g{|#SL5%Su&TxSe04UwW2k2xVHhkXg3)?nggjf zngjRKvGET%&SO5AW>!8m5)FRY!na;S1z`Wel?(ekX@FMCTxf_oTv!*&V+i&&n$dBr zjn;9hu_R{zB9 zzV=uBd6#OzWp&Fplz`m@BeU(0OMrgxZLv-m7}snxixS;5J~42=`D)4A4!8f5!5s0M z&H(ORBh(%Tv)8wgCQ#`oEGWdoLPa928(@RK~J@>kI=OL!`P z(`cGBH~eS270W1{>c?ojnnyy2sObT;yxH-{X-}q_$LMZN-_SfwU+UP$=+&A>#JA6& zBE6uQJH22(7rmgG-xc2TXLL@;XUdRAM1L~DAfxc<8MW}~k8uz6s2>&70raUh*B+|ML{}u)kv@Z$1-qVvJAJd1KEYmfl?TCv7;rEw z{sbhJzAkuD0k88tA&PF$|0Bkt z?CfCfW(IU7rExU3Q2!L}WC5@<16bK<;hDsofhMjFpHDuc{}Z%!uopLR1(MQ;^Rn=8 zGc&V%x_@4}pD7d^%>UmH)tyZo9f9VgOp+#cEG{4;)5!g+NimHgJ)7C&!vbL{rlDvmN%b_0#!Tdk0uVc%IwF)Y$re# zx6Anc4`V8m0-6RupUo;-6hV3M5YPk(QjM^4K?w83 znW`1$G*{6gSB#$|isa^BPap(pl-oKHL~c_bfq~$}HYo9uI~=huAwYCv38d1@vI*aX z%3*OF-R&s1l~$Ff68c*k@+-~QLbf%-`fox+V;#woNQX7RMQ{k=f}C-ktvOxy*WhcR zoQdF5h`7b}Vx#mWLJnvsD=Ng}n+mo75{YUw=F;sm$n1EyDO>1)CGi;c$5>U`5LnNp7ogV%E}^{GNjCy))u{-W}H0894?S>>9C%W zGU@g-(lkK58Tv6;E(}OG3Wh<2zJjIUIBeoq?*-UApNF-+FqQNZW(8C?VR`_AZ$Xk3 z`U}3ZXRb?O*bfiXH}Pdi)?oA(#35+2t+bP&KzCECU;JYW?=Kf;Xn&m8vmbdS`MPcd zPXxqeG9R5TVedu+O2{OkP7S1OYqN|J_cbM3z86n5iy>B%=ExMA)Mrh_y4Ih zSCF`Dn)>EBOpI}>GYrQbb0oM5v-$ed<*;m)@fJosfF!I4sAr~4 zvB2{Z_2?%~=*%IhwAAHHzZD{0|G3oo1$NOm)XG!FJwWX!BZNusXo`TUz1(q|svRLw z;xTVtvFhcxpA`4h{DM5Nl)+ARjW-R(72lO9$>nz?Z7`ikc5xVrA>xVUPB&NAcdwSq zG~;wZ$|HRCg%CcX{y^qNEkS+bc4?-xRFtWwTdnxrF+xP`oE9irHO*IuUtc^tlzF2F7YN+fN^4txz8#=;dP>Yh>r~g2)aw$e4 zI*RRlfyAaj0HJTuP?`ssdd5ldwg!BcTmD(^l}yX_p!>*uC;4Lw{=y^|?v`zFZyFk) z+(s&sf00paA`skHH*e7jfnxyxxJ9~OlKtHV2o9#L^a~Q3#z627k;Pt8#5x74@vlz4 zY$8eGYf5)_C4V>OBHQ#GXprV>5m-EiHnp0FNsndq5yX-L-FNc#i0 z(G)zCKiKD2z{+5Iz-CyC@En`VWfdO5`9*02RjL`PhC;GqwgFQ!TYzG2Z`G_FPa$?4BF?YUNaTv|5QeaJzYaYoCYR?&CQNAh z)tWR>lnnP`E-Ns0eUqemyOOu@Q;rKbAH{t+0?S3ttpn3_qw<4j#X|EjNRPmA+cQ~R zxy)!d^|~Ely-%cm))qE(S&U5_&dA?WQxavnb>GF}%k0L4%8szgL<^dg!&gS0@~9|; z9(jQ(GD_O^#Ob=cDIO{m%YhHa59?H73r-d>(J%W_MKt=`$1D^lT2PeTC7cx)ibK3p zyzOvOTQGTX-ELVtHtxlqArvZy((Dd*sXTg31{zbEd`rM;S#7Tu7?1Je3%wc5ug{4@ zNv;ts&1dPwb&~op={J|(&YrxBFXs5Mvexi|_kM!R7tjTeo%wMn@O|ZKbUo0Yvl2ZQ zY>52v*%N6p4VYA^XBlDM8a|bHdLz~0!-P!UBI~y=eg?SkR%Le&|1@?PWS{B6dvQzJ zw6lJCAy~5?T)<8~b+qd2=JR#Xo^u+#7R(&+ZxJ=G@D0({M!=SDL_JtEj-0S_;s|E1 z?mFN8tJohjF&<`HbU#0}iFpom^?QYFG(n)@iW5~1?#bPJmwNG^7 zNj{3kln5R{eoR^>4NlhBVj^)9gII?q=-?s3bvuo@sV>!eNhdKfs72fN6jP_V@h46F z!60=Zjb{U7 zHCbh!@0qpO-+EL0sLmh&B@k%rgr;o?1ME;UIT77rMg0gpe#C;d3l+IF_82ED!K&Iu zk@ZHs$KB$OwgtQIUOx`6R>>_8Xw;jH?91+ZXj*RXZnqm;Hp*WCK7%W&PoAUQnbq}G z{txM@Im}$Kv^B8f-g=v>)E=~%x9SX|&7ic*eYDqSh4SN^fVPw7tO_Ts533S%8M@(+ z+#BKLH%FibO4IG}n2$vGiTHDw-_+ccpcPN9|C}lYg)Z_eAmDh{zJjM+elH?VaX02y zdwcsyOO_w*=x+h>m+W3v+-?OLE4%^zyHc7Zg}%Hen|)R`3tLEA7s9MSc4xV7ze}?Q zZtUxP7VQ2OcR;7OBGE-{+wq9&fg<#$=e-3_E~nOqd0CjAb?7{3suGEras8G!n6o-G7&G~e#?;Md&04T3hAtKNM9FWzt6P+g z{Vsgp-U|W}6;OdbIM4p9*So44-h!4sgw+GYY(`C=R>v-58(DL96=QZ8<6ceDSDCA< zD($NqD^2`dD&`_%#a3#aZRpB}l~&spI%a9E?VSX*I5KVt%4+ily0~Kn7)5UL zZXyYthtVt*rKfVPSbgmR!3nG56l_k11CW_tXPA`Vm3mR3C2vq$M%cfZ*oA#EcG^ML zdU6Q|9}rZ(xip#UU!n!6L%N%FZgr!O1Sc z%*?^fBQC+oBFVumDMseFgg`R8*#>j;IR5~3^Wxbb6H}1arFW5Pr%5-3}vQ-eiJIa1F^0|mK6@%tVlIZ z=b<>x&$}Hi+IERJX|#?kVsDAOBOD0vo%Q~F=_oQ5dBPkYT(lx`ilOjETa-OhQTa&P z-O3{qUGu@{bSir;{{2D1tMofQCEr0mMl>O+_yVF=2q+werT@FZU0h9^T|J#Yo0yY} Pg^LHCib_IB68^scLfwML diff --git a/trixy/trixy-lang_parser/example/comments.tri b/trixy/trixy-lang_parser/example/comments.tri deleted file mode 100644 index 597996a..0000000 --- a/trixy/trixy-lang_parser/example/comments.tri +++ /dev/null @@ -1,12 +0,0 @@ -fn print(message: String); - -/// First doc comment -// Some more text -nasp trinitrix { - /// Second doc comment - fn hi(name: String) -> String; -} - - -// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: -// vim: syntax=rust diff --git a/trixy/trixy-lang_parser/example/empty.tri b/trixy/trixy-lang_parser/example/empty.tri deleted file mode 100644 index e69de29..0000000 diff --git a/trixy/trixy-lang_parser/example/failing.tri b/trixy/trixy-lang_parser/example/failing.tri deleted file mode 100644 index 7227248..0000000 --- a/trixy/trixy-lang_parser/example/failing.tri +++ /dev/null @@ -1,9 +0,0 @@ -fn print(message: CommandTransferValue); - -nasp trinitrix { {} - fn hi honner(name: String) -> String; ; -} - - -// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: -// vim: syntax=rust diff --git a/trixy/trixy-lang_parser/example/failing_comments.tri b/trixy/trixy-lang_parser/example/failing_comments.tri deleted file mode 100644 index 7aa985b..0000000 --- a/trixy/trixy-lang_parser/example/failing_comments.tri +++ /dev/null @@ -1,13 +0,0 @@ -fn print(message: CommandTransferValue); - -/// Some doc comment -// Some more text -nasp trinitrix { - fn hi(name: String) -> String; -} - -/// Trailing doc comment (I will fail) - - -// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: -// vim: syntax=rust diff --git a/trixy/trixy-lang_parser/example/failing_types.tri b/trixy/trixy-lang_parser/example/failing_types.tri deleted file mode 100644 index 8e5ed74..0000000 --- a/trixy/trixy-lang_parser/example/failing_types.tri +++ /dev/null @@ -1,10 +0,0 @@ -struct Callback { - func: Function, - timeout: Integer, -}; - -fn execute_callback(callback: Name); - - -// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: -// vim: syntax=rust diff --git a/trixy/trixy-lang_parser/example/full.tri b/trixy/trixy-lang_parser/example/full.tri deleted file mode 100644 index 9b2f065..0000000 --- a/trixy/trixy-lang_parser/example/full.tri +++ /dev/null @@ -1,136 +0,0 @@ -/// Prints to the output, with a newline. -// HACK(@soispha): The stdlib Lua `print()` function has stdout as output hardcoded, -// redirecting stdout seems too much like a hack thus we are just redefining the print function -// to output to a controlled output. <2023-09-09> -//fn print(input: CommandTransferValue); - -nasp trinitrix { - /// Language specific functions, which mirror the `trinitrix.api` namespace. - /// That is, if you have to choose between a `std` and a `api` function choose the `std` - /// one as it will most likely be more high-level and easier to use (as it isn't abstracted - /// over multiple languages). Feel free to drop down to the lower level api, if you feel - /// like that more, it should be as stable and user-oriented as the `std` functions - nasp std {} - - /// Debug only functions, these are effectively useless - nasp debug { - enum UserGreet { - Friendly, - Angrily, - Hastly - }; - struct GreetedUser { - names: Vec, - new: GreetedUser, - state: UserGreet - }; - /// Greets the user - fn greet(input: String) -> String; - - /// Returns a table of greeted users - fn greet_multiple() -> GreetedUser; - } - - /// General API to change stuff in Trinitrix - nasp api { - /// Closes the application - fn exit(); - - /// Send a message to the current room - /// The send message is interpreted literally. - fn room_message_send(msg: String); - - /// Open the help pages at the first occurrence of - /// the input string if it is Some, otherwise open - /// the help pages at the start - fn help(input: Option); - - // Register a function to be used with the Trinitrix API - // (This function is actually implemented in the std namespace) - /* fn register_function(function: RawFunction); */ - - /// Function that change the UI, or UI state - nasp ui { - /// Shows the command line - fn command_line_show(); - - /// Hides the command line - fn command_line_hide(); - - /// Go to the next plane - fn cycle_planes(); - /// Go to the previous plane - fn cycle_planes_rev(); - - /// Sets the current app mode to Normal / navigation mode - fn set_mode_normal(); - /// Sets the current app mode to Insert / editing mode - fn set_mode_insert(); - } - - /// Manipulate keymappings, the mode is specified as a String build up of all mode - /// the keymapping should be active in. The mapping works as follows: - /// n => normal Mode - /// c => command Mode - /// i => insert Mode - /// - /// The key works in a similar matter, specifying the required keypresses to trigger the - /// callback. For example "aba" for require the user to press "a" then "b" then "a" again - /// to trigger the mapping. Special characters are encoded as follows: - /// "ba" => "Ctrl+a" then "b" then "a" - /// "" => "A" or "Shift+a" - /// "A" => "A" - /// " " => "Alt+a" () or "Meta+a"() (most terminals can't really differentiate between these characters) - /// "a" => "a" then "Ctrl+b" then "Ctrl+a" (also works for Shift, Alt and Super) - /// "" => "Ctrl+Shift+Alt+b" (the ordering doesn't matter) - /// "a " => "a" then a literal space (" ") - /// "å🙂" => "å" then "🙂" (full Unicode support!) - /// "" => escape key - /// "" => F3 key - /// "" => backspace key (and so forth) - /// "" => a literal "-" - /// "" or "" => a literal "<" - /// "" or "" => a literal ">" - /// - /// The callback MUST be registered first by calling - /// `trinitrix.api.register_function()` the returned value can than be used to - /// set the keymap. - nasp keymaps { - /// Add a new keymapping - fn add(mode: String, key: String, callback: Function); - - /// Remove a keymapping - /// - /// Does nothing, if the keymapping doesn't exists - fn remove(mode: String, key: String); - - /// List declared keymappings - fn get(mode: String); - } - - /// Functions only used internally within Trinitrix - nasp raw { - /// Send an error to the default error output - fn raise_error(input: String); - - /// Send output to the default output - /// This is mainly used to display the final - /// output of evaluated lua commands. - fn display_output(input: String); - - /// Input a character without checking for possible keymaps - /// If the current state does not expect input, this character is ignored - /// The encoding is the same as in the `trinitrix.api.keymaps` commands - fn send_input_unprocessed(input: String); - - /// This namespace is used to store some command specific data (like functions, as - /// ensuring memory locations stay allocated in garbage collected language is hard) - /// - /// Treat it as an implementation detail - nasp __private {} - } - } -} - -// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: -// vim: syntax=rust diff --git a/trixy/trixy-lang_parser/example/multiple.tri b/trixy/trixy-lang_parser/example/multiple.tri deleted file mode 100644 index 0fca007..0000000 --- a/trixy/trixy-lang_parser/example/multiple.tri +++ /dev/null @@ -1,13 +0,0 @@ -fn print(message: CommandTransferValue); - -nasp trinitrix { - fn hi(name: String) -> String; -} - -nasp trinitrix { - fn ho(name: String, name2: String) -> String; -} - - -// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: -// vim: syntax=rust diff --git a/trixy/trixy-lang_parser/example/simple.tri b/trixy/trixy-lang_parser/example/simple.tri deleted file mode 100644 index c9b5c9a..0000000 --- a/trixy/trixy-lang_parser/example/simple.tri +++ /dev/null @@ -1,9 +0,0 @@ -fn print(message: CommandTransferValue); - -nasp trinitrix { - fn hi(name: String) -> String; -} - - -// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: -// vim: syntax=rust diff --git a/trixy/trixy-lang_parser/example/types.tri b/trixy/trixy-lang_parser/example/types.tri deleted file mode 100644 index b599445..0000000 --- a/trixy/trixy-lang_parser/example/types.tri +++ /dev/null @@ -1,18 +0,0 @@ -nasp trinitrix { - struct Callback { - func: Function, - timeout: Integer, - }; - - enum CallbackPriority { - High, - Medium, - Low, - }; - - fn execute_callback(callback: Callback, priority: CallbackPriority); -} - - -// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: -// vim: syntax=rust diff --git a/trixy/trixy-lang_parser/generate_docs b/trixy/trixy-lang_parser/generate_docs deleted file mode 100755 index f84a636..0000000 --- a/trixy/trixy-lang_parser/generate_docs +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env sh - - - -ebnf2pdf make "./docs/grammar.ebnf" -mv grammar.ebnf.pdf ./docs/grammar.pdf - - -# vim: ft=sh diff --git a/trixy/trixy-lang_parser/src/command_spec/checked.rs b/trixy/trixy-lang_parser/src/command_spec/checked.rs deleted file mode 100644 index 30d0eda..0000000 --- a/trixy/trixy-lang_parser/src/command_spec/checked.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! This module contains the already type checked types. - -use std::fmt::Display; - -use crate::lexing::TokenKind; - -use super::unchecked; - -/// These are the "primitive" types used in trixy, you can use any of them to create new structures -pub const BASE_TYPES: [ConstIdentifier; 8] = [ - Identifier::from("Integer"), - Identifier::from("Float"), - Identifier::from("Decimal"), - Identifier::from("String"), - Identifier::from("Function"), - Identifier::from("Option"), - Identifier::from("Result"), - Identifier::from("Vec"), -]; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Namespace { - pub name: Identifier, - - pub functions: Vec, - pub structures: Vec, - pub enumerations: Vec, - pub namespaces: Vec, - pub attributes: Vec, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct CommandSpec { - pub structures: Vec, - pub enumerations: Vec, - pub functions: Vec, - pub namespaces: Vec, -} - -impl From for CommandSpec { - fn from(value: Namespace) -> Self { - Self { - structures: value.structures, - enumerations: value.enumerations, - functions: value.functions, - namespaces: value.namespaces, - } - } -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Structure { - pub identifier: Identifier, - pub contents: Vec, - pub attributes: Vec, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Enumeration { - pub identifier: Identifier, - pub states: Vec, - pub attributes: Vec, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Function { - pub identifier: Identifier, - pub inputs: Vec, - pub output: Option, - pub attributes: Vec, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Type { - pub identifier: Identifier, - pub generic_args: Vec, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct NamedType { - pub name: Identifier, - pub r#type: Type, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct DocNamedType { - pub name: Identifier, - pub r#type: Type, - pub attributes: Vec, -} - -impl From for Identifier { - fn from(value: TokenKind) -> Self { - match value { - TokenKind::Identifier(ident) => Identifier { name: ident }, - _ => { - panic!( - "Tried to convert a non Identifier TokenKind to a Identefier. This is a bug - Token was: '{}' - ", - value - ) - } - } - } -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub enum Attribute { - #[allow(non_camel_case_types)] - doc(String), -} -impl From for Attribute { - fn from(value: unchecked::Attribute) -> Self { - match value { - unchecked::Attribute::doc { content: name, .. } => Self::doc(name), - } - } -} - -/// An Identifier -/// These include -/// - Variable names -/// - Function names -/// - Namespace names -/// - Type names -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub struct Identifier { - pub name: String, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub struct DocIdentifier { - pub name: String, - pub attributes: Vec, -} - -/// A const version of [Identifier] -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct ConstIdentifier { - pub name: &'static str, -} - -impl Display for Identifier { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.name) - } -} - -impl Identifier { - const fn from(value: &'static str) -> ConstIdentifier { - ConstIdentifier { name: value } - } -} diff --git a/trixy/trixy-lang_parser/src/command_spec/mod.rs b/trixy/trixy-lang_parser/src/command_spec/mod.rs deleted file mode 100644 index 1bf868c..0000000 --- a/trixy/trixy-lang_parser/src/command_spec/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod checked; -pub mod unchecked; diff --git a/trixy/trixy-lang_parser/src/command_spec/unchecked.rs b/trixy/trixy-lang_parser/src/command_spec/unchecked.rs deleted file mode 100644 index 5757ee0..0000000 --- a/trixy/trixy-lang_parser/src/command_spec/unchecked.rs +++ /dev/null @@ -1,127 +0,0 @@ -//! This module contains the not type checked types. -//! These are generated on the first pass of the parser, to be later converted into the checked -//! ones. - -use std::fmt::{Display, Write}; - -use crate::lexing::{Token, TokenSpan}; - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)] -pub struct CommandSpec { - pub structures: Vec, - pub enumerations: Vec, - pub functions: Vec, - pub namespaces: Vec, -} - -impl From for Namespace { - fn from(value: CommandSpec) -> Self { - Self { - name: Token::get_dummy(), - functions: value.functions, - structures: value.structures, - enumerations: value.enumerations, - namespaces: value.namespaces, - attributes: vec![], - } - } -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub struct Namespace { - pub name: Token, // Will later become an Identifier - - pub functions: Vec, - pub structures: Vec, - pub enumerations: Vec, - pub namespaces: Vec, - - pub attributes: Vec, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum Declaration { - Function(Function), - Structure(Structure), - Enumeration(Enumeration), - Namespace(Namespace), -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub enum Attribute { - #[allow(non_camel_case_types)] - doc { content: String, span: TokenSpan }, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub struct Function { - pub identifier: Token, // Will later become an Identifier - pub inputs: Vec, - pub output: Option, - pub attributes: Vec, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub struct Structure { - pub identifier: Token, // Will later become an Identifier - pub contents: Vec, - pub attributes: Vec, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub struct Enumeration { - pub identifier: Token, // Will later become an Identifier - pub states: Vec, // Will later become an Identifier - pub attributes: Vec, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub struct DocToken { - pub token: Token, - pub attributes: Vec, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub struct DocNamedType { - pub name: Token, // Will later become an Identifier - pub r#type: Type, - pub attributes: Vec, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub struct NamedType { - pub name: Token, // Will later become an Identifier - pub r#type: Type, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub struct Type { - pub identifier: Token, // Will later become an Identifier - pub generic_args: Vec, -} - -impl Display for Type { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let ident = match self.identifier.kind() { - crate::lexing::TokenKind::Identifier(ident) => ident, - _ => panic!("Tried to display a non identifier token in the Type display implementation. This is a bug"), - }; - - f.write_str(ident)?; - if !self.generic_args.is_empty() { - f.write_char('<')?; - let mut first_run = true; - for arg in &self.generic_args { - if !first_run { - f.write_str(", ")?; - } else { - first_run = false; - } - write!(f, "{}", arg)?; - } - f.write_char('>') - } else { - f.write_str("") - } - } -} diff --git a/trixy/trixy-lang_parser/src/error.rs b/trixy/trixy-lang_parser/src/error.rs deleted file mode 100644 index a98f518..0000000 --- a/trixy/trixy-lang_parser/src/error.rs +++ /dev/null @@ -1,194 +0,0 @@ -use core::fmt; - -use thiserror::Error; - -use crate::{ - lexing::{error::SpannedLexingError, TokenSpan}, - parsing::checked::error::SpannedParsingError, -}; - -#[derive(Error, Debug)] -pub enum TrixyError { - #[error(transparent)] - Lexing(#[from] SpannedLexingError), - #[error(transparent)] - Parsing(#[from] SpannedParsingError), -} - -/// The context of an Error. -#[derive(Debug, Clone)] -pub struct ErrorContext { - /// The span of the error in the source file - pub span: TokenSpan, - /// The span of the error in the context line relative to the context line - pub contexted_span: TokenSpan, - /// The line above the error - pub line_above: String, - /// The line below the error - pub line_below: String, - /// The line in which the error occurred - pub line: String, - /// The line number of the main error line - pub line_number: usize, -} - -impl ErrorContext { - pub fn from_span(span: TokenSpan, original_file: &str) -> Self { - let line_number = original_file - .chars() - .take(span.start) - .filter(|a| a == &'\n') - .count() - // This is here, as we are missing one newline with the method above - + 1; - - let lines: Vec<_> = original_file.lines().collect(); - - let line = (*lines - .get(line_number - 1) - .expect("This should work, as have *at least* one (index = 0) line")) - .to_owned(); - - let contexted_span = { - let matched_line: Vec<_> = original_file.match_indices(&line).collect(); - let (index, matched_line) = matched_line.first().expect("This first index should always match, as we took the line from the string in the first place"); - debug_assert_eq!(matched_line, &&line); - TokenSpan { - start: span.start - index, - end: span.end - index, - } - }; - - let line_above = if line_number == 1 { - // We only have one line, so no line above - "".to_owned() - } else { - (*lines - .get((line_number - 1) - 1) - .expect("We checked that this should work")) - .to_owned() - }; - - let line_below = if lines.len() - 1 > line_number { - // We have a line after the current line - (*lines - .get((line_number + 1) - 1) - .expect("We checked that this should work")) - .to_owned() - } else { - "".to_owned() - }; - - Self { - span, - contexted_span, - line_above, - line_below, - line, - line_number, - } - } - - pub fn from_index(start: usize, orginal_file: &str) -> Self { - let span = TokenSpan { - start, - end: start + 1, - }; - Self::from_span(span, orginal_file) - } - - pub fn get_error_line(&self, source_error: &str) -> String { - // deconstruct the structure - let ErrorContext { - contexted_span, - line_number, - .. - } = self; - - let mut output = String::new(); - output.push_str("\x1b[92;1m"); - - // pad to accommodate the line number printing. - // 32 -> needs two spaces padding to print it - line_number.to_string().chars().for_each(|_| { - output.push(' '); - }); - - // pad to the beginning of the error - for _ in 0..contexted_span.start { - output.push(' '); - } - - // push the error markers - for _ in contexted_span.start..contexted_span.end { - output.push('^'); - } - - // // pad until end of line - // for _ in contexted_span.end..(line.len() - 1) { - // output.push('-'); - // } - // - // additional space to avoid having to end with a '-' - output.push(' '); - - output.push_str("help: "); - - output.push_str(source_error); - output.push_str("\x1b[0m"); - output - } -} - -pub trait AdditionalHelp { - fn additional_help(&self) -> String; -} - -pub trait ErrorContextDisplay: fmt::Display { - type Error; - - fn error_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - where - ::Error: std::fmt::Display + AdditionalHelp, - { - let error_line = self - .context() - .get_error_line(&self.source().additional_help()); - - writeln!(f, "\x1b[31;1merror: \x1b[37;1m{}\x1b[0m", self.source())?; - - if !self.line_above().is_empty() { - writeln!( - f, - "\x1b[32;1m{} |\x1b[0m {}", - self.line_number() - 1, - self.line_above() - )?; - } - writeln!( - f, - "\x1b[36;1m{} |\x1b[0m {}", - self.line_number(), - self.line() - )?; - writeln!(f, " {}", error_line)?; - if !self.line_below().is_empty() { - writeln!( - f, - "\x1b[32;1m{} |\x1b[0m {}", - self.line_number() + 1, - self.line_below() - ) - } else { - write!(f, "") - } - } - - // getters - fn context(&self) -> &ErrorContext; - fn source(&self) -> &Self::Error; - fn line_number(&self) -> usize; - fn line_above(&self) -> &str; - fn line_below(&self) -> &str; - fn line(&self) -> &str; -} diff --git a/trixy/trixy-lang_parser/src/lexing/error.rs b/trixy/trixy-lang_parser/src/lexing/error.rs deleted file mode 100644 index ecaf92e..0000000 --- a/trixy/trixy-lang_parser/src/lexing/error.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::{error::Error, fmt::Display}; -use thiserror::Error; - -use crate::error::{AdditionalHelp, ErrorContext, ErrorContextDisplay}; - -#[derive(Error, Debug)] -pub enum LexingError { - #[error("No matches were found")] - NoMatchesTaken, - #[error("Expected an token, but reached end of file")] - UnexpectedEOF, - #[error("Char ('{0}') is not a know token!")] - UnknownCharacter(char), - #[error("The Arrow token must be of the form: ->")] - ExpectedArrow, - #[error("The Comment token must start with two slashes")] - ExpectedComment, -} - -impl AdditionalHelp for LexingError { - fn additional_help(&self) -> String { - match self { - LexingError::NoMatchesTaken => "This token does not produce a possible match".to_owned(), - LexingError::UnexpectedEOF => "This eof was completely unexpected".to_owned(), - LexingError::ExpectedArrow => "The `-` token is interpretet as a started arrow (`->`), but we could not find the arrow tip (`>`)".to_owned(), - LexingError::UnknownCharacter(char) => { - format!("This char: `{char}`; is not a valid token") - }, - LexingError::ExpectedComment => "The '/' started comment parsing, but I could not find a matching '/'".to_owned(), - } - } -} - -#[derive(Debug)] -pub struct SpannedLexingError { - pub source: LexingError, - pub context: ErrorContext, -} - -impl Error for SpannedLexingError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(&self.source) - } -} - -impl ErrorContextDisplay for SpannedLexingError { - type Error = LexingError; - - fn context(&self) -> &crate::error::ErrorContext { - &self.context - } - - fn line_number(&self) -> usize { - self.context.line_number - } - - fn line_above(&self) -> &str { - &self.context.line_above - } - - fn line_below(&self) -> &str { - &self.context.line_below - } - - fn line(&self) -> &str { - &self.context.line - } - - fn source(&self) -> &::Error { - &self.source - } -} -impl Display for SpannedLexingError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.error_fmt(f) - } -} diff --git a/trixy/trixy-lang_parser/src/lexing/mod.rs b/trixy/trixy-lang_parser/src/lexing/mod.rs deleted file mode 100644 index 77fd918..0000000 --- a/trixy/trixy-lang_parser/src/lexing/mod.rs +++ /dev/null @@ -1,307 +0,0 @@ -use std::fmt::Display; - -use self::{error::SpannedLexingError, tokenizer::Tokenizer}; - -pub mod error; -mod tokenizer; - -#[cfg(test)] -mod test; - -#[derive(Debug, PartialEq, PartialOrd, Ord, Eq)] -pub struct TokenStream { - pub original_file: String, - tokens: Vec, -} - -impl TokenStream { - /// Turn a string of valid Trixy code into a list of tokens, including the - /// location of that token's start and end point in the original source code. - /// - /// Note the token indices represent the half-open interval `[start, end)`, - /// equivalent to `start .. end` in Rust. - pub fn lex(src: &str) -> Result { - let mut tokenizer = Tokenizer::new(src); - let mut tokens = Vec::new(); - - while let Some(tok) = tokenizer.next_token()? { - tokens.push(tok); - } - - // filter out comments - let tokens = tokens - .into_iter() - .filter(|token| !matches!(token.kind, TokenKind::Comment(_))) - .collect(); - - Ok(Self { - tokens, - original_file: src.to_owned(), - }) - } - - /// Get a token by index - pub fn get(&self, index: usize) -> Option<&Token> { - self.tokens.get(index) - } - - /// Get a reference to the uppermost token, without modifying the token list - pub fn peek(&self) -> Option<&Token> { - self.tokens.last() - } - - /// Remove to the uppermost token - pub fn pop(&mut self) -> Token { - self.tokens.pop().expect("This should not be emtpy") - } - - /// Reverses the underlying tokes vector - /// This is facilitates using the pop and peek methods to parse the tokens from the beginning, - /// not the end - pub fn reverse(&mut self) { - self.tokens.reverse() - } - - /// Check if the TokenStream is empty. - pub fn is_empty(&self) -> bool { - self.tokens.is_empty() - } -} - -/// A token span is recorded in chars starting from the beginning of the file: -/// A token span like this, for example: -/// ```dont_run -///# use trixy_lang_parser::lexing::TokenSpan; -/// TokenSpan { -/// start: 20, -/// end: 23, -/// } -/// ``` -/// signals, that the token starts at the 20th char in the source file and ends on the 23rd. -#[derive(Debug, Default, PartialEq, PartialOrd, Ord, Eq, Clone, Copy)] -pub struct TokenSpan { - /// The start of the token span - pub start: usize, - /// The end of the token span - pub end: usize, -} - -impl TokenSpan { - pub fn from_range(start: TokenSpan, end: TokenSpan) -> Self { - Self { - start: start.start, - end: end.end, - } - } -} - -/// A Token -#[derive(Debug, Default, PartialEq, PartialOrd, Ord, Eq, Clone)] -pub struct Token { - /// The token's original location in the source file - pub span: TokenSpan, - pub kind: TokenKind, -} - -impl Token { - /// Return the TokenKind of a token - pub fn kind(&self) -> &TokenKind { - &self.kind - } - - /// Return the TokenSpan of a token - pub fn span(&self) -> &TokenSpan { - &self.span - } - - /// Get a dummy token, this is intended for error handling - pub fn get_dummy() -> Token { - Self { - span: TokenSpan { start: 0, end: 0 }, - kind: TokenKind::Dummy, - } - } -} - -/// Possibly kinds of tokens -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub enum TokenKind { - Keyword(Keyword), - Identifier(String), - Colon, - Semicolon, - Comma, - Arrow, - BraceOpen, - BraceClose, - ParenOpen, - ParenClose, - SquareOpen, - SquareClose, - - DocComment(String), - Comment(String), - - /// This is not a real TokenKind, but only used for error handling - #[default] - Dummy, -} - -impl TokenKind { - pub fn same_kind(&self, other: &TokenKind) -> bool { - if let TokenKind::Identifier(_) = self { - if let TokenKind::Identifier(_) = other { - return true; - } - } - if let TokenKind::Comment(_) = self { - if let TokenKind::Comment(_) = other { - return true; - } - } - if let TokenKind::DocComment(_) = self { - if let TokenKind::DocComment(_) = other { - return true; - } - } - self == other - } -} - -impl Display for TokenKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TokenKind::Keyword(word) => write!(f, "KEYWORD({})", word), - TokenKind::Identifier(ident) => { - if ident.is_empty() { - write!(f, "IDENTIFIER") - } else { - write!(f, "IDENTIFIER({})", ident) - } - } - TokenKind::Colon => f.write_str("COLON"), - TokenKind::Semicolon => f.write_str("SEMICOLON"), - TokenKind::Comma => f.write_str("COMMA"), - TokenKind::Arrow => f.write_str("ARROW"), - TokenKind::BraceOpen => f.write_str("BRACEOPEN"), - TokenKind::BraceClose => f.write_str("BRACECLOSE"), - TokenKind::ParenOpen => f.write_str("PARENOPEN"), - TokenKind::ParenClose => f.write_str("PARENCLOSE"), - TokenKind::Dummy => f.write_str("DUMMY"), - TokenKind::SquareOpen => f.write_str("SQUAREOPEN"), - TokenKind::SquareClose => f.write_str("SQUARECLOSE"), - TokenKind::DocComment(text) => write!(f, "DOCCOMMENT({})", text), - TokenKind::Comment(text) => write!(f, "COMMENT({})", text), - } - } -} - -/// Keywords used in the language -#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone, Copy)] -pub enum Keyword { - /// Start a namespace declaration - #[allow(non_camel_case_types)] - nasp, - /// Start a function declaration - #[allow(non_camel_case_types)] - r#fn, - /// Start a structure declaration - #[allow(non_camel_case_types)] - r#struct, - /// Start a enum declaration - #[allow(non_camel_case_types)] - r#enum, -} - -impl Display for Keyword { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Keyword::nasp => f.write_str("nasp"), - Keyword::r#fn => f.write_str("fn"), - Keyword::r#struct => f.write_str("struct"), - Keyword::r#enum => f.write_str("enum"), - } - } -} - -/// Shorthand macro for generating a token from *anything* which can be -/// converted into a `TokenKind`, or any of the `TokenKind` variants. -/// -/// # Examples -/// -/// ``` -/// use trixy_lang_parser::token; -/// # fn main() { -/// token![nasp]; -/// token![;]; -/// token![Arrow]; -/// # } -/// ``` -#[macro_export] -macro_rules! token { - [Semicolon] => { $crate::lexing::TokenKind::Semicolon }; - [;] => { $crate::lexing::TokenKind::Semicolon }; - [Colon] => { $crate::lexing::TokenKind::Colon }; - [:] => { $crate::lexing::TokenKind::Colon }; - [Comma] => { $crate::lexing::TokenKind::Comma }; - [,] => { $crate::lexing::TokenKind::Comma }; - [Arrow] => { $crate::lexing::TokenKind::Arrow }; - [->] => { $crate::lexing::TokenKind::Arrow }; - [SquareOpen] => { $crate::lexing::TokenKind::SquareOpen }; - [<] => { $crate::lexing::TokenKind::SquareOpen }; - [SquareClose] => { $crate::lexing::TokenKind::SquareClose }; - [>] => { $crate::lexing::TokenKind::SquareClose}; - [BraceOpen] => { $crate::lexing::TokenKind::BraceOpen }; - // [{] => { $crate::lexing::TokenKind::BraceOpen }; - [BraceClose] => { $crate::lexing::TokenKind::BraceClose }; - // [}] => { $crate::lexing::TokenKind::BraceClose }; - [ParenOpen] => { $crate::lexing::TokenKind::ParenOpen }; - // [(] => { $crate::lexing::TokenKind::ParenthesisOpen }; - [ParenClose] => { $crate::lexing::TokenKind::ParenClose }; - // [)] => { $crate::lexing::TokenKind::ParenthesisClose }; - - [nasp] => { $crate::lexing::TokenKind::Keyword($crate::lexing::Keyword::nasp) }; - [fn] => { $crate::lexing::TokenKind::Keyword($crate::lexing::Keyword::r#fn) }; - [struct] => { $crate::lexing::TokenKind::Keyword($crate::lexing::Keyword::r#struct) }; - [enum] => { $crate::lexing::TokenKind::Keyword($crate::lexing::Keyword::r#enum) }; - - // This is only works for checking for a identifier or comment - // see the `same_kind` method on TokenKind - [Ident] => { $crate::lexing::TokenKind::Identifier("".to_owned()) }; - [Identifier] => { $crate::lexing::TokenKind::Identifier("".to_owned()) }; - [DocComment] => { $crate::lexing::TokenKind::DocComment("".to_owned()) }; - [DocCommentMatch] => { $crate::lexing::TokenKind::DocComment(_doc_comment) }; - [Comment] => { $crate::lexing::TokenKind::Comment("".to_owned()) }; -} - -#[cfg(test)] -mod tests { - use super::TokenKind; - use crate::token; - - macro_rules! token_macro_test { - ($name:ident, $from:tt, => $to:expr) => { - #[test] - fn $name() { - let got: TokenKind = token![$from]; - let should_be = $to; - - assert_eq!(got, should_be); - } - }; - ($name:ident, $from:tt, => $to:expr) => { - #[test] - fn $name() { - let got: TokenKind = token![$from]; - let should_be = $to; - - assert_eq!(got, should_be); - } - }; - } - - token_macro_test!(tok_expands_to_arrow, ->, => TokenKind::Arrow); - token_macro_test!(tok_expands_to_semicolon, Semicolon, => TokenKind::Semicolon); - token_macro_test!(tok_expands_to_nasp, nasp, => TokenKind::Keyword(crate::lexing::Keyword::nasp)); - token_macro_test!(tok_expands_to_fn, fn, => TokenKind::Keyword(crate::lexing::Keyword::r#fn)); -} diff --git a/trixy/trixy-lang_parser/src/lexing/test.rs b/trixy/trixy-lang_parser/src/lexing/test.rs deleted file mode 100644 index 396f1cb..0000000 --- a/trixy/trixy-lang_parser/src/lexing/test.rs +++ /dev/null @@ -1,194 +0,0 @@ -use crate::lexing::{Keyword, Token, TokenKind, TokenSpan}; - -use super::TokenStream; - -use pretty_assertions::assert_eq; - -#[test] -fn test_lexing_trixy() { - let input = " -nasp commands { - fn expect(event: String) -> String; -} -"; - let token_stream = TokenStream::lex(input).unwrap(); - let expected_token_stream = { - let tokens = vec![ - Token { - span: TokenSpan { start: 1, end: 5 }, - kind: TokenKind::Keyword(Keyword::nasp), - }, - Token { - span: TokenSpan { start: 6, end: 14 }, - kind: TokenKind::Identifier("commands".to_owned()), - }, - Token { - span: TokenSpan { start: 15, end: 16 }, - kind: TokenKind::BraceOpen, - }, - Token { - span: TokenSpan { start: 21, end: 23 }, - kind: TokenKind::Keyword(Keyword::r#fn), - }, - Token { - span: TokenSpan { start: 24, end: 30 }, - kind: TokenKind::Identifier("expect".to_owned()), - }, - Token { - span: TokenSpan { start: 30, end: 31 }, - kind: TokenKind::ParenOpen, - }, - Token { - span: TokenSpan { start: 31, end: 36 }, - kind: TokenKind::Identifier("event".to_owned()), - }, - Token { - span: TokenSpan { start: 36, end: 37 }, - kind: TokenKind::Colon, - }, - Token { - span: TokenSpan { start: 38, end: 44 }, - kind: TokenKind::Identifier("String".to_owned()), - }, - Token { - span: TokenSpan { start: 44, end: 45 }, - kind: TokenKind::ParenClose, - }, - Token { - span: TokenSpan { start: 46, end: 48 }, - kind: TokenKind::Arrow, - }, - Token { - span: TokenSpan { start: 49, end: 55 }, - kind: TokenKind::Identifier("String".to_owned()), - }, - Token { - span: TokenSpan { start: 55, end: 56 }, - kind: TokenKind::Semicolon, - }, - Token { - span: TokenSpan { start: 57, end: 58 }, - kind: TokenKind::BraceClose, - }, - ]; - TokenStream { - tokens, - original_file: input.to_owned(), - } - }; - assert_eq!(token_stream, expected_token_stream) -} - -#[test] -fn test_failing_lexing() { - let input = " -nasp trinitrix { - nasp - commands { - fn hi(strings: String) -> String; - } -} -"; - let token_stream = TokenStream::lex(input); - eprintln!("{}", token_stream.as_ref().unwrap_err()); - - // uncomment the next line to see the error message, without having to remove cargo's output filter - // assert!(!token_stream.is_err()); - assert!(token_stream.is_err()); -} - -#[test] -fn test_multiple_tokens() { - let input = " -nasp nasp {{ -}} -"; - let token_stream = TokenStream::lex(input).unwrap(); - let expected_token_stream = { - let tokens = vec![ - Token { - span: TokenSpan { start: 1, end: 5 }, - kind: TokenKind::Keyword(Keyword::nasp), - }, - Token { - span: TokenSpan { start: 6, end: 10 }, - kind: TokenKind::Keyword(Keyword::nasp), - }, - Token { - span: TokenSpan { start: 11, end: 12 }, - kind: TokenKind::BraceOpen, - }, - Token { - span: TokenSpan { start: 12, end: 13 }, - kind: TokenKind::BraceOpen, - }, - Token { - span: TokenSpan { start: 14, end: 15 }, - kind: TokenKind::BraceClose, - }, - Token { - span: TokenSpan { start: 15, end: 16 }, - kind: TokenKind::BraceClose, - }, - ]; - TokenStream { - tokens, - original_file: input.to_owned(), - } - }; - assert_eq!(token_stream, expected_token_stream) -} - -#[test] -fn test_comments() { - let input = " - // Some comment - nasp nasp {{ - - }} - // NOTE(@soispha): We do not support nested multi line comments <2023-12-16> - /* Some - * multi - * line - * comment - */ -"; - let token_stream = TokenStream::lex(input) - .map_err(|e| { - eprintln!("{}", e); - panic!(); - }) - .unwrap(); - let expected_token_stream = { - let tokens = vec![ - Token { - span: TokenSpan { start: 33, end: 37 }, - kind: TokenKind::Keyword(Keyword::nasp), - }, - Token { - span: TokenSpan { start: 38, end: 42 }, - kind: TokenKind::Keyword(Keyword::nasp), - }, - Token { - span: TokenSpan { start: 43, end: 44 }, - kind: TokenKind::BraceOpen, - }, - Token { - span: TokenSpan { start: 44, end: 45 }, - kind: TokenKind::BraceOpen, - }, - Token { - span: TokenSpan { start: 55, end: 56 }, - kind: TokenKind::BraceClose, - }, - Token { - span: TokenSpan { start: 56, end: 57 }, - kind: TokenKind::BraceClose, - }, - ]; - TokenStream { - tokens, - original_file: input.to_owned(), - } - }; - assert_eq!(token_stream, expected_token_stream) -} diff --git a/trixy/trixy-lang_parser/src/lexing/tokenizer.rs b/trixy/trixy-lang_parser/src/lexing/tokenizer.rs deleted file mode 100644 index 6662f07..0000000 --- a/trixy/trixy-lang_parser/src/lexing/tokenizer.rs +++ /dev/null @@ -1,226 +0,0 @@ -// This code is heavily inspired by: https://michael-f-bryan.github.io/static-analyser-in-rust/book/lex.html - -use crate::{ - error::ErrorContext, - lexing::{Keyword, TokenSpan}, -}; - -use super::{ - error::{LexingError, SpannedLexingError}, - Token, TokenKind, -}; - -pub(super) struct Tokenizer<'a> { - current_index: usize, - remaining_text: &'a str, - original_text: &'a str, -} - -impl<'a> Tokenizer<'a> { - pub(super) fn new(input: &'a str) -> Self { - Self { - current_index: 0, - remaining_text: input, - original_text: input, - } - } - pub(super) fn next_token(&mut self) -> Result, SpannedLexingError> { - self.skip_ignored_tokens(); - if self.remaining_text.is_empty() { - Ok(None) - } else { - let start = self.current_index; - - let (token_kind, index) = self.get_next_tokenkind().map_err(|e| { - let context = ErrorContext::from_index(start, self.original_text); - - SpannedLexingError { source: e, context } - })?; - - self.chomp(index); // end - start - let end = self.current_index; - Ok(Some(Token { - span: TokenSpan { start, end }, - kind: token_kind, - })) - } - } - - fn get_next_tokenkind(&mut self) -> Result<(TokenKind, usize), LexingError> { - let next = match self.remaining_text.chars().next() { - Some(c) => c, - None => return Err(LexingError::UnexpectedEOF), - }; - - let (tok, length) = match next { - '(' => (TokenKind::ParenOpen, 1), - ')' => (TokenKind::ParenClose, 1), - '{' => (TokenKind::BraceOpen, 1), - '}' => (TokenKind::BraceClose, 1), - ':' => (TokenKind::Colon, 1), - ';' => (TokenKind::Semicolon, 1), - ',' => (TokenKind::Comma, 1), - '<' => (TokenKind::SquareOpen, 1), - '>' => (TokenKind::SquareClose, 1), - - '-' => tokenize_arrow(self.remaining_text)?, - '/' => tokenize_comment(self.remaining_text)?, - - // can't use a OR (`|`) here, as the guard takes precedence - c if c.is_alphabetic() => tokenize_ident(self.remaining_text)?, - '_' => tokenize_ident(self.remaining_text)?, - - other => return Err(LexingError::UnknownCharacter(other)), - }; - - Ok((tok, length)) - } - - fn skip_ignored_tokens(&mut self) { - loop { - let ws = self.skip_whitespace(); - let comments = self.skip_block_comment(); - if ws + comments == 0 { - return; - } - } - } - - /// Skip past any whitespace characters - fn skip_whitespace(&mut self) -> usize { - let mut remaining = self.remaining_text; - - // Filter out whitespace - let _ws = { - let ws = match take_while(remaining, |ch| ch.is_whitespace()) { - Ok((_, bytes_skipped)) => bytes_skipped, - _ => 0, - }; - remaining = &remaining[ws..]; - ws - }; - - let skip = self.remaining_text.len() - remaining.len(); - self.chomp(skip); - skip - } - fn skip_block_comment(&mut self) -> usize { - let pairs = [("/*", "*/")]; - - let src = self.remaining_text; - - for &(pattern, matcher) in &pairs { - if src.starts_with(pattern) { - let leftovers = skip_until(src, matcher); - let skip = src.len() - leftovers.len(); - self.chomp(skip); - return skip; - } - } - - 0 - } - - fn chomp(&mut self, chars_to_chomp: usize) { - self.remaining_text = &self.remaining_text[chars_to_chomp..]; - self.current_index += chars_to_chomp; - } -} - -fn tokenize_comment(text: &str) -> Result<(TokenKind, usize), LexingError> { - // every token starts with two slashes - let slashes: &str = &text[..2]; - if slashes != "//" { - Err(LexingError::ExpectedComment) - } else { - let text: &str = &text[2..]; - if let Some('/') = text.chars().next() { - let text = &text[1..]; - let (doc_comment, chars_read) = take_while(text, |ch| ch != '\n' && ch != '\r')?; - - // trim whitespace - let doc_comment = doc_comment.trim_start(); - let doc_comment = doc_comment.trim_end(); - - return Ok(( - TokenKind::DocComment(doc_comment.to_owned()), - chars_read + 3, - )); - } - let (comment, chars_read) = take_while(text, |ch| ch != '\n' && ch != '\r')?; - - // trim whitespace - let comment = comment.trim_start(); - let comment = comment.trim_end(); - - Ok((TokenKind::Comment(comment.to_owned()), chars_read + 2)) - } -} - -fn tokenize_ident(text: &str) -> Result<(TokenKind, usize), LexingError> { - let (got, chars_read) = take_while(text, |ch| ch == '_' || ch.is_alphanumeric())?; - - // Filter out keywords - let tokenkind = match got { - "nasp" => TokenKind::Keyword(Keyword::nasp), - "fn" => TokenKind::Keyword(Keyword::r#fn), - "struct" => TokenKind::Keyword(Keyword::r#struct), - "enum" => TokenKind::Keyword(Keyword::r#enum), - other => TokenKind::Identifier(other.to_string()), - }; - - Ok((tokenkind, chars_read)) -} - -fn tokenize_arrow(text: &str) -> Result<(TokenKind, usize), LexingError> { - let mut chars = text.chars(); - if let Some(char) = chars.next() { - if char == '-' { - if let Some(char) = chars.next() { - if char == '>' { - return Ok((TokenKind::Arrow, 2)); - } - } - } - } - // This is a implicit else as the other if clauses return - Err(LexingError::ExpectedArrow) -} - -/// Consumes bytes while a predicate evaluates to true. -fn take_while(data: &str, mut pred: F) -> Result<(&str, usize), LexingError> -where - F: FnMut(char) -> bool, -{ - let mut current_index = 0; - - for ch in data.chars() { - let should_continue = pred(ch); - - if !should_continue { - break; - } - - current_index += ch.len_utf8(); - } - - if current_index == 0 { - Err(LexingError::NoMatchesTaken) - } else { - Ok((&data[..current_index], current_index)) - } -} - -/// Skips input until the remaining string pattern starts with the pattern -fn skip_until<'a>(mut src: &'a str, pattern: &str) -> &'a str { - while !src.is_empty() && !src.starts_with(pattern) { - let next_char_size = src - .chars() - .next() - .expect("The string isn't empty") - .len_utf8(); - src = &src[next_char_size..]; - } - - &src[pattern.len()..] -} diff --git a/trixy/trixy-lang_parser/src/lib.rs b/trixy/trixy-lang_parser/src/lib.rs deleted file mode 100644 index 65f69b3..0000000 --- a/trixy/trixy-lang_parser/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -use error::TrixyError; - -use crate::lexing::TokenStream; - -use self::command_spec::checked::CommandSpec; - -mod command_spec; -pub mod error; -pub mod lexing; -pub mod parsing; - -pub fn parse_trixy_lang(input: &str) -> Result> { - let input_tokens = TokenStream::lex(input) - .map_err(|err| Box::new(err.into()))? - .parse() - .map_err(Into::::into)?; - Ok(input_tokens) -} diff --git a/trixy/trixy-lang_parser/src/main.rs b/trixy/trixy-lang_parser/src/main.rs deleted file mode 100644 index aefc806..0000000 --- a/trixy/trixy-lang_parser/src/main.rs +++ /dev/null @@ -1,110 +0,0 @@ -use std::{fs, process::exit}; - -use trixy_lang_parser::lexing::TokenStream; - -use std::path::PathBuf; - -use clap::{Parser, Subcommand}; - -/// A helper command for the trixy-lang_parser crate -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -pub struct Args { - #[command(subcommand)] - /// The subcommand to execute - pub subcommand: Command, -} -#[derive(Subcommand, Debug)] -pub enum Command { - #[clap(value_parser)] - /// Only try to tokenize the file - Tokenize { - #[clap(value_parser)] - /// The file containing the trixy code to tokenize - file: PathBuf, - }, - /// Check syntax, without type checking - Parse { - #[clap(value_parser)] - /// The file containing the trixy code to parse - file: PathBuf, - }, - /// Type check - Process { - #[clap(value_parser)] - /// The file containing the trixy code to process - file: PathBuf, - }, -} - -pub fn main() { - let args = Args::parse(); - match args.subcommand { - Command::Tokenize { file } => { - let input = fs::read_to_string(file).unwrap(); - - let input_tokens = match TokenStream::lex(&input) { - Ok(err) => err, - Err(ok) => { - eprintln!("{}", ok); - exit(1); - } - }; - - println!("{:#?}", input_tokens); - } - Command::Parse { file } => { - let input = fs::read_to_string(file).unwrap(); - - let input_tokens = match TokenStream::lex(&input) { - Ok(ok) => ok, - Err(err) => { - eprintln!("Error while tokenizing:"); - eprintln!("{}", err); - exit(1); - } - }; - - let parsed = match input_tokens.parse_unchecked() { - Ok(ok) => ok, - Err(err) => { - eprintln!("Error while doing the first (unchecked) parsing run:"); - eprintln!("{}", err); - exit(1) - } - }; - println!("{:#?}", parsed); - } - Command::Process { file } => { - let input = fs::read_to_string(file).unwrap(); - - let input_tokens = match TokenStream::lex(&input) { - Ok(ok) => ok, - Err(err) => { - eprintln!("Error while tokenizing:"); - eprintln!("{}", err); - exit(1); - } - }; - - let parsed = match input_tokens.parse_unchecked() { - Ok(ok) => ok, - Err(err) => { - eprintln!("Error while doing the first (unchecked) parsing run:"); - eprintln!("{}", err); - exit(1) - } - }; - - let processed = match parsed.process(input) { - Ok(ok) => ok, - Err(err) => { - eprintln!("Error while doing the seconde (checked) parsing run:"); - eprintln!("{}", err); - exit(1) - } - }; - println!("{:#?}", processed); - } - } -} diff --git a/trixy/trixy-lang_parser/src/parsing/checked/error.rs b/trixy/trixy-lang_parser/src/parsing/checked/error.rs deleted file mode 100644 index e088199..0000000 --- a/trixy/trixy-lang_parser/src/parsing/checked/error.rs +++ /dev/null @@ -1,82 +0,0 @@ -use thiserror::Error; - -use std::{error::Error, fmt::Display}; - -use crate::{ - command_spec::checked::Identifier, - error::{AdditionalHelp, ErrorContext, ErrorContextDisplay}, - lexing::TokenSpan, - parsing::unchecked::error::SpannedParsingError as OldSpannedParsingError, -}; - -#[derive(Error, Debug)] -pub enum ParsingError { - #[error("The type ('{r#type}') was not declared before!")] - TypeNotDeclared { r#type: Identifier, span: TokenSpan }, - #[error(transparent)] - PreParseError(#[from] OldSpannedParsingError), -} - -impl ParsingError { - pub fn span(&self) -> &TokenSpan { - match self { - ParsingError::TypeNotDeclared { span, .. } => span, - ParsingError::PreParseError(err) => err.source.span(), - } - } -} - -impl AdditionalHelp for ParsingError { - fn additional_help(&self) -> String { - match self { - ParsingError::TypeNotDeclared { .. } => "This type should have been mentioned in the namespaces above, or in the namespace of this type usage".to_owned(), - ParsingError::PreParseError(err) => ErrorContextDisplay::source(err).additional_help(), - } - } -} - -#[derive(Debug)] -pub struct SpannedParsingError { - pub source: Box, - pub context: ErrorContext, -} - -impl Error for SpannedParsingError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(&self.source) - } -} - -impl Display for SpannedParsingError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.error_fmt(f) - } -} - -impl ErrorContextDisplay for SpannedParsingError { - type Error = ParsingError; - - fn context(&self) -> &crate::error::ErrorContext { - &self.context - } - - fn line_number(&self) -> usize { - self.context.line_number - } - - fn line_above(&self) -> &str { - &self.context.line_above - } - - fn line_below(&self) -> &str { - &self.context.line_below - } - - fn line(&self) -> &str { - &self.context.line - } - - fn source(&self) -> &::Error { - &self.source - } -} diff --git a/trixy/trixy-lang_parser/src/parsing/checked/mod.rs b/trixy/trixy-lang_parser/src/parsing/checked/mod.rs deleted file mode 100644 index 669fd1b..0000000 --- a/trixy/trixy-lang_parser/src/parsing/checked/mod.rs +++ /dev/null @@ -1,261 +0,0 @@ -use std::mem; - -use crate::{ - command_spec::{ - checked::{ - CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier, NamedType, - Namespace, Structure, Type, BASE_TYPES, - }, - unchecked::{ - CommandSpec as UncheckedCommandSpec, DocNamedType as UncheckedDocNamedType, - Enumeration as UncheckedEnumeration, Function as UncheckedFunction, - NamedType as UncheckedNamedType, Namespace as UncheckedNamespace, - Structure as UncheckedStructure, Type as UncheckedType, - }, - }, - error::ErrorContext, - lexing::{TokenKind, TokenStream}, -}; - -use self::error::{ParsingError, SpannedParsingError}; - -pub mod error; -#[cfg(test)] -mod test; - -struct Parser { - command_spec: UncheckedCommandSpec, - structures: Vec, - enumerations: Vec, - original_file: String, -} - -impl TokenStream { - pub fn parse(mut self) -> Result { - let original_file = mem::take(&mut self.original_file); - - let unchecked = self.parse_unchecked().map_err(|err| { - let span = *err.source.span(); - SpannedParsingError { - source: Box::new(ParsingError::from(err)), - context: ErrorContext::from_span(span, &original_file), - } - })?; - - let checked = Parser { - command_spec: unchecked, - structures: vec![], - enumerations: vec![], - original_file, - } - .parse()?; - Ok(checked) - } -} - -impl UncheckedCommandSpec { - pub fn process(self, original_file: String) -> Result { - let checked = Parser { - command_spec: self, - structures: vec![], - enumerations: vec![], - original_file, - } - .parse()?; - Ok(checked) - } -} - -macro_rules! pass_attrs_along { - ($name:ident) => { - $name.attributes.into_iter().map(|a| a.into()).collect() - }; -} - -impl Parser { - fn parse(mut self) -> Result { - let namespace: UncheckedNamespace = - UncheckedNamespace::from(mem::take(&mut self.command_spec)); - let namespace = self.process_namespace(namespace).map_err(|err| { - let span = *err.span(); - SpannedParsingError { - source: Box::new(err), - context: ErrorContext::from_span(span, &self.original_file), - } - })?; - Ok(namespace.into()) - } - - fn process_namespace( - &mut self, - namespace: UncheckedNamespace, - ) -> Result { - let name = match namespace.name.kind { - TokenKind::Identifier(ident) => Identifier { name: ident }, - // This is not really used, so the value put here does not matter - TokenKind::Dummy => Identifier { - name: "".to_owned(), - }, - _ => unreachable!("This should never be more than these two enum veriants"), - }; - - let mut enumerations = vec![]; - let mut enumerations_counter = 0; - for enumeration in namespace.enumerations { - enumerations.push(self.process_enumeration(enumeration)?); - enumerations_counter += 1; - } - let mut structures = vec![]; - let mut structures_counter = 0; - for structure in namespace.structures { - structures.push(self.process_structure(structure)?); - structures_counter += 1; - } - - let mut functions = vec![]; - for function in namespace.functions { - functions.push(self.process_function(function)?); - } - let mut namespaces = vec![]; - for namespace in namespace.namespaces { - namespaces.push(self.process_namespace(namespace)?); - } - - // Remove added enums and structs again - (0..structures_counter).for_each(|_| { - self.structures.pop(); - }); - (0..enumerations_counter).for_each(|_| { - self.enumerations.pop(); - }); - - Ok(Namespace { - name, - functions, - structures, - enumerations, - namespaces, - attributes: pass_attrs_along!(namespace), - }) - } - - fn process_function( - &mut self, - mut function: UncheckedFunction, - ) -> Result { - let identifier = mem::take(&mut function.identifier.kind).into(); - let mut inputs = vec![]; - for input in function.inputs { - inputs.push(self.process_named_type(input)?); - } - let output = if let Some(r#type) = function.output { - Some(self.process_type(r#type)?) - } else { - None - }; - - Ok(Function { - identifier, - inputs, - output, - attributes: pass_attrs_along!(function), - }) - } - - fn process_enumeration( - &mut self, - mut enumeration: UncheckedEnumeration, - ) -> Result { - self.enumerations.push(enumeration.clone()); - - let identifier = mem::take(&mut enumeration.identifier.kind).into(); - - let mut states = vec![]; - for mut state in enumeration.states { - states.push({ - let ident: Identifier = mem::take(&mut state.token.kind).into(); - DocIdentifier { - name: ident.name, - attributes: pass_attrs_along!(state), - } - }) - } - - Ok(Enumeration { - identifier, - states, - attributes: pass_attrs_along!(enumeration), - }) - } - - fn process_structure( - &mut self, - mut structure: UncheckedStructure, - ) -> Result { - self.structures.push(structure.clone()); - - let identifier: Identifier = mem::take(&mut structure.identifier.kind).into(); - let mut contents = vec![]; - for named_type in structure.contents { - contents.push(self.process_doc_named_type(named_type)?); - } - - Ok(Structure { - identifier, - contents, - attributes: pass_attrs_along!(structure), - }) - } - - fn process_named_type( - &mut self, - mut named_type: UncheckedNamedType, - ) -> Result { - let name: Identifier = mem::take(&mut named_type.name.kind).into(); - let r#type: Type = self.process_type(named_type.r#type)?; - Ok(NamedType { name, r#type }) - } - fn process_doc_named_type( - &mut self, - mut doc_named_type: UncheckedDocNamedType, - ) -> Result { - let name: Identifier = mem::take(&mut doc_named_type.name.kind).into(); - let r#type: Type = self.process_type(doc_named_type.r#type)?; - Ok(DocNamedType { - name, - r#type, - attributes: pass_attrs_along!(doc_named_type), - }) - } - - fn process_type(&mut self, mut r#type: UncheckedType) -> Result { - let identifier: Identifier = mem::take(&mut r#type.identifier.kind).into(); - - if !self - .structures - .iter() - .map(|r#struct| Into::::into(r#struct.identifier.kind.clone())) - .any(|ident| ident == identifier) - && !self - .enumerations - .iter() - .map(|r#enum| Into::::into(r#enum.identifier.kind.clone())) - .any(|ident| ident == identifier) - && !BASE_TYPES.iter().any(|ident| ident.name == identifier.name) - { - return Err(ParsingError::TypeNotDeclared { - r#type: identifier, - span: r#type.identifier.span, - }); - } - - let mut generic_args = vec![]; - for generic_arg in r#type.generic_args { - generic_args.push(self.process_type(generic_arg)?); - } - Ok(Type { - identifier, - generic_args, - }) - } -} diff --git a/trixy/trixy-lang_parser/src/parsing/checked/test.rs b/trixy/trixy-lang_parser/src/parsing/checked/test.rs deleted file mode 100644 index adf7a85..0000000 --- a/trixy/trixy-lang_parser/src/parsing/checked/test.rs +++ /dev/null @@ -1,215 +0,0 @@ -use crate::command_spec::checked::{ - Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier, - NamedType, Namespace, Structure, Type, -}; -use crate::lexing::TokenStream; - -use pretty_assertions::assert_eq; - -#[test] -fn test_full() { - let input = "nasp trinitrix { - struct Callback { - func: Function, - timeout: Integer, - }; - - enum CallbackPriority { - High, - Medium, - Low, - }; - - fn execute_callback(callback: Callback, priority: CallbackPriority); -}"; - let output = TokenStream::lex(&input).unwrap().parse().unwrap(); - let expected = CommandSpec { - structures: vec![], - enumerations: vec![], - functions: vec![], - namespaces: vec![Namespace { - name: Identifier { - name: "trinitrix".to_owned(), - }, - functions: vec![Function { - identifier: Identifier { - name: "execute_callback".to_owned(), - }, - inputs: vec![ - NamedType { - name: Identifier { - name: "callback".to_owned(), - }, - r#type: Type { - identifier: Identifier { - name: "Callback".to_owned(), - }, - generic_args: vec![], - }, - }, - NamedType { - name: Identifier { - name: "priority".to_owned(), - }, - r#type: Type { - identifier: Identifier { - name: "CallbackPriority".to_owned(), - }, - generic_args: vec![], - }, - }, - ], - output: None, - attributes: vec![], - }], - structures: vec![Structure { - identifier: Identifier { - name: "Callback".to_owned(), - }, - contents: vec![ - DocNamedType { - name: Identifier { - name: "func".to_owned(), - }, - r#type: Type { - identifier: Identifier { - name: "Function".to_owned(), - }, - generic_args: vec![], - }, - attributes: vec![], - }, - DocNamedType { - name: Identifier { - name: "timeout".to_owned(), - }, - r#type: Type { - identifier: Identifier { - name: "Integer".to_owned(), - }, - generic_args: vec![], - }, - attributes: vec![], - }, - ], - attributes: vec![], - }], - enumerations: vec![Enumeration { - identifier: Identifier { - name: "CallbackPriority".to_owned(), - }, - states: vec![ - DocIdentifier { - name: "High".to_owned(), - attributes: vec![], - }, - DocIdentifier { - name: "Medium".to_owned(), - attributes: vec![], - }, - DocIdentifier { - name: "Low".to_owned(), - attributes: vec![], - }, - ], - attributes: vec![], - }], - namespaces: vec![], - attributes: vec![], - }], - }; - assert_eq!(output, expected); -} - -#[test] -fn test_failing() { - let input = "struct Callback { - func: Function, - timeout: Integer, -}; - -// The type \"Name\" should not be defined -fn execute_callback(callback: Name); -"; - let output = TokenStream::lex(&input).unwrap().parse(); - match *(output.unwrap_err().source) { - super::error::ParsingError::TypeNotDeclared { r#type, .. } => { - assert_eq!( - r#type, - Identifier { - name: "Name".to_owned() - } - ) - } - _ => panic!("Wrong error in test!"), - }; -} - -#[test] -fn test_comments() { - let input = "fn print(message: String); - -/// First doc comment -// Some more text -nasp trinitrix { - /// Second doc comment - fn hi(name: String) -> String; -} -"; - let output = TokenStream::lex(&input).unwrap().parse().unwrap(); - let expected = CommandSpec { - structures: vec![], - enumerations: vec![], - functions: vec![Function { - identifier: Identifier { - name: "print".to_owned(), - }, - inputs: vec![NamedType { - name: Identifier { - name: "message".to_owned(), - }, - r#type: Type { - identifier: Identifier { - name: "String".to_owned(), - }, - generic_args: vec![], - }, - }], - output: None, - attributes: vec![], - }], - namespaces: vec![Namespace { - name: Identifier { - name: "trinitrix".to_owned(), - }, - functions: vec![Function { - identifier: Identifier { - name: "hi".to_owned(), - }, - inputs: vec![NamedType { - name: Identifier { - name: "name".to_owned(), - }, - r#type: Type { - identifier: Identifier { - name: "String".to_owned(), - }, - generic_args: vec![], - }, - }], - output: Some(Type { - identifier: Identifier { - name: "String".to_owned(), - }, - generic_args: vec![], - }), - attributes: vec![Attribute::doc("Second doc comment".to_owned())], - }], - structures: vec![], - enumerations: vec![], - namespaces: vec![], - attributes: vec![Attribute::doc("First doc comment".to_owned())], - }], - }; - assert_eq!(output, expected); -} diff --git a/trixy/trixy-lang_parser/src/parsing/mod.rs b/trixy/trixy-lang_parser/src/parsing/mod.rs deleted file mode 100644 index f1506dc..0000000 --- a/trixy/trixy-lang_parser/src/parsing/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod checked; -mod unchecked; diff --git a/trixy/trixy-lang_parser/src/parsing/unchecked/error.rs b/trixy/trixy-lang_parser/src/parsing/unchecked/error.rs deleted file mode 100644 index f15c5d5..0000000 --- a/trixy/trixy-lang_parser/src/parsing/unchecked/error.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::{error::Error, fmt::Display}; -use thiserror::Error; - -use crate::{ - command_spec::unchecked::Attribute, - error::{AdditionalHelp, ErrorContext, ErrorContextDisplay}, - lexing::{TokenKind, TokenSpan}, -}; - -#[derive(Error, Debug, Clone)] -pub enum ParsingError { - #[error("Expected '{expected}', but received: '{actual}'")] - ExpectedDifferentToken { - expected: TokenKind, - actual: TokenKind, - span: TokenSpan, - }, - - #[error("Expected '{expected}', but the token stream stopped")] - UnexpectedEOF { - expected: TokenKind, - span: TokenSpan, - }, - - #[error("Expected a Keyword to start a new declaration, but found: '{actual}'")] - ExpectedKeyword { actual: TokenKind, span: TokenSpan }, - - #[error("DocComment does not have target")] - TrailingDocComment { - comments: Vec, - span: TokenSpan, - }, -} -impl ParsingError { - pub fn span(&self) -> &TokenSpan { - match self { - ParsingError::ExpectedDifferentToken { span, .. } => span, - ParsingError::ExpectedKeyword { span, .. } => span, - ParsingError::TrailingDocComment { span, .. } => span, - ParsingError::UnexpectedEOF { span, .. } => span, - } - } - - pub fn get_span(&self) -> TokenSpan { - *self.span() - } -} - -impl AdditionalHelp for ParsingError { - fn additional_help(&self) -> String { - match self { - ParsingError::ExpectedDifferentToken { - expected, - actual, - .. - } => format!( - "I expected a '{}' here, but you put a '{}' there!", - expected, actual - ), - ParsingError::ExpectedKeyword { actual, .. } => format!( - "I expected a keyword (that is something like 'fn' or 'nasp') but you put a '{}' there!", - actual), - ParsingError::TrailingDocComment { .. } => "I expected some target (a function, namespace, enum, or something like this) which this doc comment annotates, but you put nothing there".to_owned(), - ParsingError::UnexpectedEOF { expected, .. } => format!("Put the expected token ('{expected}') here."), - } - } -} - -#[derive(Debug, Clone)] -pub struct SpannedParsingError { - pub source: Box, - pub context: ErrorContext, -} - -impl Error for SpannedParsingError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(&self.source) - } -} - -impl Display for SpannedParsingError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.error_fmt(f) - } -} - -impl ErrorContextDisplay for SpannedParsingError { - type Error = ParsingError; - - fn context(&self) -> &crate::error::ErrorContext { - &self.context - } - - fn line_number(&self) -> usize { - self.context.line_number - } - - fn line_above(&self) -> &str { - &self.context.line_above - } - - fn line_below(&self) -> &str { - &self.context.line_below - } - - fn line(&self) -> &str { - &self.context.line - } - - fn source(&self) -> &::Error { - &self.source - } -} diff --git a/trixy/trixy-lang_parser/src/parsing/unchecked/mod.rs b/trixy/trixy-lang_parser/src/parsing/unchecked/mod.rs deleted file mode 100644 index b1175e0..0000000 --- a/trixy/trixy-lang_parser/src/parsing/unchecked/mod.rs +++ /dev/null @@ -1,372 +0,0 @@ -use std::mem; - -use crate::{ - command_spec::unchecked::{ - Attribute, CommandSpec, Declaration, DocNamedType, DocToken, Enumeration, Function, - NamedType, Namespace, Structure, Type, - }, - error::ErrorContext, - lexing::{Token, TokenKind, TokenSpan, TokenStream}, - token, -}; - -use self::error::{ParsingError, SpannedParsingError}; - -pub mod error; -#[cfg(test)] -mod test; - -impl TokenStream { - pub fn parse_unchecked(self) -> Result { - let mut parser = Parser::new(self); - parser.parse() - } -} - -pub(super) struct Parser { - token_stream: TokenStream, - active_doc_comments: Vec, - last_span: TokenSpan, -} - -impl Parser { - fn new(mut token_stream: TokenStream) -> Self { - token_stream.reverse(); - Self { - token_stream, - active_doc_comments: vec![], - last_span: TokenSpan::default(), - } - } - - fn parse(&mut self) -> Result { - let mut output = CommandSpec::default(); - while !self.token_stream.is_empty() { - let next = self.parse_next().map_err(|err| { - let span = err.get_span(); - SpannedParsingError { - source: Box::new(err), - context: ErrorContext::from_span(span, &self.token_stream.original_file), - } - })?; - match next { - Declaration::Function(function) => output.functions.push(function), - Declaration::Structure(structure) => output.structures.push(structure), - Declaration::Enumeration(enumeration) => output.enumerations.push(enumeration), - Declaration::Namespace(namespace) => output.namespaces.push(namespace), - } - } - - Ok(output) - } - - fn parse_next(&mut self) -> Result { - // Use of [peek_raw] here is fine, as we know that the function is only called, when - // something should still be contained in the token stream - match self.peek_raw().kind() { - token![nasp] => Ok(Declaration::Namespace(self.parse_namespace()?)), - token![fn] => Ok(Declaration::Function(self.parse_function()?)), - token![struct] => Ok(Declaration::Structure(self.parse_structure()?)), - token![enum] => Ok(Declaration::Enumeration(self.parse_enumeration()?)), - token![DocCommentMatch] => { - while self.expect_peek(token![DocComment]) { - let comment_to_push = { - let doc_comment = self.expect(token![DocComment])?; - let span = *doc_comment.span(); - let name = if let TokenKind::DocComment(content) = doc_comment.kind { - content - } else { - unreachable!("The expect should have accounted for that case"); - }; - - Attribute::doc { - content: name, - span, - } - }; - self.active_doc_comments.push(comment_to_push); - } - - if self.token_stream.is_empty() { - fn get_span(attr: Option<&Attribute>) -> TokenSpan { - match attr.expect("Something should be here") { - Attribute::doc { span, .. } => *span, - } - } - - let span = TokenSpan::from_range( - get_span(self.active_doc_comments.first()), - get_span(self.active_doc_comments.last()), - ); - Err(ParsingError::TrailingDocComment { - comments: mem::take(&mut self.active_doc_comments), - span, - }) - } else { - self.parse_next() - } - } - _ => { - let err = ParsingError::ExpectedKeyword { - span: *self.peek_raw().span(), - actual: self.peek_raw().kind().clone(), - }; - - Err(err) - } - } - } - - fn parse_type(&mut self) -> Result { - let identifier = self.expect(token![Ident])?; - - let mut generic_args = vec![]; - if self.expect_peek(token![<]) { - self.expect(token![<])?; - if self.expect_peek(token![Ident]) { - generic_args.push(self.parse_type()?); - } - while self.expect_peek(token![Comma]) { - generic_args.push(self.parse_type()?); - } - self.expect(token![>])?; - } - Ok(Type { - identifier, - generic_args, - }) - } - - fn parse_doc_comments(&mut self) -> Result, ParsingError> { - let mut attrs = mem::take(&mut self.active_doc_comments); - - while self.expect_peek(token![DocComment]) { - attrs.push({ - let doc_comment = self.expect(token![DocComment])?; - let span = *doc_comment.span(); - let name = if let TokenKind::DocComment(content) = doc_comment.kind { - content - } else { - unreachable!("The expect should have accounted for that case"); - }; - Attribute::doc { - content: name, - span, - } - }); - } - Ok(attrs) - } - - fn parse_namespace(&mut self) -> Result { - let attributes = self.parse_doc_comments()?; - self.expect(token![nasp])?; - - let mut namespace = Namespace { - name: self.expect(token![Ident])?, - attributes, - ..Default::default() - }; - - self.expect(token![BraceOpen])?; - - while !self.expect_peek(token![BraceClose]) { - let next = self.parse_next()?; - match next { - Declaration::Function(function) => namespace.functions.push(function), - Declaration::Structure(structure) => namespace.structures.push(structure), - Declaration::Enumeration(enumeration) => namespace.enumerations.push(enumeration), - Declaration::Namespace(input_namespace) => { - namespace.namespaces.push(input_namespace) - } - } - } - - self.expect(token![BraceClose])?; - Ok(namespace) - } - - fn parse_enumeration(&mut self) -> Result { - let attributes = self.parse_doc_comments()?; - self.expect(token![enum])?; - let identifier = self.expect(token![Ident])?; - self.expect(token![BraceOpen])?; - - let mut states = vec![]; - if self.expect_peek(token![Ident]) { - let attributes = self.parse_doc_comments()?; - states.push(DocToken { - token: self.expect(token![Ident])?, - attributes, - }); - } - while self.expect_peek(token![Comma]) { - self.expect(token![Comma])?; - if self.expect_peek(token![Ident]) { - let attributes = self.parse_doc_comments()?; - states.push(DocToken { - token: self.expect(token![Ident])?, - attributes, - }); - } else { - break; - } - } - self.expect(token![BraceClose])?; - self.expect(token![;])?; - Ok(Enumeration { - identifier, - states, - attributes, - }) - } - - fn parse_structure(&mut self) -> Result { - let attributes = self.parse_doc_comments()?; - self.expect(token![struct])?; - let name = self.expect(token![Ident])?; - self.expect(token![BraceOpen])?; - - let mut contents = vec![]; - if self.expect_peek(token![Ident]) { - contents.push(self.parse_doc_named_type()?); - } - while self.expect_peek(token![Comma]) { - self.expect(token![Comma])?; - if self.expect_peek(token![Ident]) { - contents.push(self.parse_doc_named_type()?); - } else { - break; - } - } - self.expect(token![BraceClose])?; - self.expect(token![;])?; - - Ok(Structure { - identifier: name, - contents, - attributes, - }) - } - - fn parse_named_type(&mut self) -> Result { - let name = self.expect(token![Ident])?; - self.expect(token![Colon])?; - let r#type = self.parse_type()?; - Ok(NamedType { name, r#type }) - } - - fn parse_doc_named_type(&mut self) -> Result { - let attributes = self.parse_doc_comments()?; - let name = self.expect(token![Ident])?; - self.expect(token![Colon])?; - let r#type = self.parse_type()?; - Ok(DocNamedType { - name, - r#type, - attributes, - }) - } - - fn parse_function(&mut self) -> Result { - let attributes = self.parse_doc_comments()?; - self.expect(token![fn])?; - let name = self.expect(token![Ident])?; - self.expect(token![ParenOpen])?; - let mut inputs = vec![]; - - if self.expect_peek(token![Ident]) { - inputs.push(self.parse_named_type()?); - } - while self.expect_peek(token![Comma]) { - self.expect(token![Comma])?; - inputs.push(self.parse_named_type()?); - } - - self.expect(token![ParenClose])?; - let mut output_type = None; - if self.expect_peek(token![->]) { - self.expect(token![->])?; - output_type = Some(self.parse_type()?); - } - self.expect(token![;])?; - Ok(Function { - identifier: name, - inputs, - output: output_type, - attributes, - }) - } - - /// Expect a token in the next input position: - /// For example: - /// - /// ```dont_run - /// use trixy_lang_parser::{ - /// lexing::{Keyword, TokenKind, TokenStream}, - /// parsing::unchecked::Parser, - /// token, - /// }; - /// - /// # fn main() { - /// let token_stream = TokenStream::lex("nasp {}").unwrap(); - /// let parser = Parser::new(token_stream); - /// assert_eq!(parser.expect(token![nasp]).unwrap(), TokenKind::Keyword(Keyword::nasp)); - /// assert_eq!(parser.expect(token![BraceOpen]).unwrap(), TokenKind::BraceOpen); - /// assert_eq!(parser.expect(token![BraceClose]).unwrap(), TokenKind::BraceClose); - /// assert!(parser.expect(token![BraceClose]).is_err()); - /// # } - /// ``` - /// - pub(super) fn expect(&mut self, token: TokenKind) -> Result { - let actual_token = if let Some(token) = self.peek() { - token - } else { - return Err(ParsingError::UnexpectedEOF { - expected: token, - span: self.last_span, - }); - }; - if actual_token.kind().same_kind(&token) { - Ok(self.pop()) - } else { - let err = ParsingError::ExpectedDifferentToken { - expected: token, - actual: actual_token.kind().clone(), - span: *actual_token.span(), - }; - - Err(err) - } - } - - /// Check if the next token is of the specified TokenKind. - /// Does not alter the token_stream - fn expect_peek(&self, token: TokenKind) -> bool { - let actual_token = match self.peek() { - Some(ok) => ok, - None => return false, - }; - actual_token.kind().same_kind(&token) - } - - /// Looks at the next token without removing it - fn peek(&self) -> Option<&Token> { - self.token_stream.peek() - } - - /// Looks at the next token without removing it. - /// Unwraps the option returned from [peek], only use it, if you know that a token must exist - fn peek_raw(&self) -> &Token { - self.token_stream.peek().expect("The token should exist") - } - - /// Removes the next token - fn pop(&mut self) -> Token { - self.last_span = *self - .peek() - .expect("Calling pop should mean, that a token was first peeked for") - .span(); - self.token_stream.pop() - } -} diff --git a/trixy/trixy-lang_parser/src/parsing/unchecked/test.rs b/trixy/trixy-lang_parser/src/parsing/unchecked/test.rs deleted file mode 100644 index b5568fb..0000000 --- a/trixy/trixy-lang_parser/src/parsing/unchecked/test.rs +++ /dev/null @@ -1,101 +0,0 @@ -use pretty_assertions::assert_eq; - -use crate::{ - command_spec::unchecked::{CommandSpec, Function, NamedType, Namespace, Type}, - lexing::{Token, TokenKind, TokenSpan, TokenStream}, -}; - -use super::error::ParsingError; - -#[test] -fn test_failing() { - let input = " -fn print(message: CommandTransferValue); - -nasp trinitrix { {} - fn hi honner(name: String) -> String; ; -} - -"; - let parsed = TokenStream::lex(input).unwrap().parse_unchecked(); - let err = parsed.unwrap_err().source; - match *err { - ParsingError::ExpectedKeyword { .. } => {} - _ => panic!("Wrong error"), - } -} - -#[test] -fn test_full() { - let input = "fn print(message: CommandTransferValue); - -nasp trinitrix { - fn hi(name: String) -> String; -} -"; - let parsed = TokenStream::lex(input).unwrap().parse_unchecked().unwrap(); - let expected = CommandSpec { - structures: vec![], - enumerations: vec![], - functions: vec![Function { - identifier: Token { - span: TokenSpan { start: 3, end: 8 }, - kind: TokenKind::Identifier("print".to_owned()), - }, - inputs: vec![NamedType { - name: Token { - span: TokenSpan { start: 9, end: 16 }, - kind: TokenKind::Identifier("message".to_owned()), - }, - r#type: Type { - identifier: Token { - span: TokenSpan { start: 18, end: 38 }, - kind: TokenKind::Identifier("CommandTransferValue".to_owned()), - }, - generic_args: vec![], - }, - }], - output: None, - attributes: vec![], - }], - namespaces: vec![Namespace { - name: Token { - span: TokenSpan { start: 47, end: 56 }, - kind: TokenKind::Identifier("trinitrix".to_owned()), - }, - functions: vec![Function { - identifier: Token { - span: TokenSpan { start: 66, end: 68 }, - kind: TokenKind::Identifier("hi".to_owned()), - }, - inputs: vec![NamedType { - name: Token { - span: TokenSpan { start: 69, end: 73 }, - kind: TokenKind::Identifier("name".to_owned()), - }, - r#type: Type { - identifier: Token { - span: TokenSpan { start: 75, end: 81 }, - kind: TokenKind::Identifier("String".to_owned()), - }, - generic_args: vec![], - }, - }], - output: Some(Type { - identifier: Token { - span: TokenSpan { start: 86, end: 92 }, - kind: TokenKind::Identifier("String".to_owned()), - }, - generic_args: vec![], - }), - attributes: vec![], - }], - structures: vec![], - enumerations: vec![], - namespaces: vec![], - attributes: vec![], - }], - }; - - assert_eq!(parsed, expected); -} diff --git a/update.sh b/update.sh index 307d47f..66d4fec 100755 --- a/update.sh +++ b/update.sh @@ -9,11 +9,9 @@ update() { } update . -update ./language_macros -update ./keymaps -git add Cargo.lock Cargo.toml flake.lock ./language_macros/Cargo.toml +git add Cargo.lock Cargo.toml flake.lock # vim: ft=sh