diff --git a/.gitignore b/.gitignore index 5446927..0c02103 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target notes.txt +ui/git_backup .gitignore \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index b929ffa..9962db1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,18 +20,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ai" -version = "0.1.0" -dependencies = [ - "clap 4.5.4", - "colored", - "logos", - "thiserror", - "tokei", - "wasm-bindgen", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -481,6 +469,19 @@ dependencies = [ "walkdir", ] +[[package]] +name = "grad" +version = "0.1.0" +dependencies = [ + "clap 4.5.4", + "colored", + "logos", + "serde", + "thiserror", + "tokei", + "wasm-bindgen", +] + [[package]] name = "grep-matcher" version = "0.1.7" @@ -1000,18 +1001,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.198" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 3df0286..121d600 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "ai" +name = "grad" version = "0.1.0" edition = "2021" -description = "A programming language with built-in autograd" +description = "A programming language" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,6 +13,4 @@ wasm-bindgen = "0.2.92" tokei = "12.1.2" thiserror = "1.0.59" colored = "2.1.0" - -[lib] -crate-type = ["cdylib"] \ No newline at end of file +serde = "1.0.203" diff --git a/README.md b/README.md index 1c9e37a..4a64cbe 100644 --- a/README.md +++ b/README.md @@ -1,143 +1,288 @@ # [name] -A simple bytecode compiler written in Rust with stack-based VM. - -## Overview - - - - +This project implements a custom programming language `grad` and its compiler, written in Rust. The compiler follows a multi-stage process to transform source code into executable bytecode, which is then interpreted by a custom Virtual Machine (VM). ## Getting Started -Try the language in the [playground]([name].vercel.app). +Try the language in the [playground](grad.vercel.app). ### Example ```bash -cargo install [name] +cargo install grad echo "let a = 10; print(a);" > example.rs [name] run example.rs ``` -Try more examples in the [examples](./examples) directory. +## Table of Contents + +1. [Compiler Overview](#compiler-overview) +2. [Lexical Analysis](#lexical-analysis) +3. [Parsing](#parsing) +4. [Abstract Syntax Tree (AST)](#abstract-syntax-tree-ast) +5. [Code Generation](#code-generation) +6. [Virtual Machine](#virtual-machine) +7. [String Interning](#string-interning) +8. [Example: Program Compilation and Execution](#example-program-compilation-and-execution) +9. [Future Improvements](#future-improvements) -## Features +## Compiler Overview -- [x] Variables -- [x] Arithmetic operations -- [x] Logical operations -- [x] Control flow (if, else) -- [x] Loops (while) -- [ ] Functions -- [ ] Closures -- [ ] Autograd -- [ ] Tensor Support +The compiler follows these main stages: -## Implementation +1. [Lexical Analysis](./src/scanner.rs) - Tokenizes the input source code. +2. [Parsing](./src/ast.rs) - Builds an Abstract Syntax Tree (AST). +3. [Code Generation](./src/compiler.rs) - Transforms the AST into bytecode. +4. [Virtual Machine](./src/vm.rs) - Executes the generated bytecode. -The source code goes through the following stages: +Each stage is implemented as a separate module, allowing for modularity and ease of maintenance. -1. [Tokenization](#tokenization) -2. [Parsing](#parsing) -3. [Compilation](#compilation) -4. [Execution](#execution) +## Lexical Analysis -### Tokenization +The lexical analysis is performed by the `Lexer` struct. It tokenizes the input source code into a series of [`Token`s](./src/scanner.rs). -[scanner.rs](./src/scanner.rs) +```rust +pub struct Lexer { + pub tokens: Vec, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct Token { + pub token_type: TokenType, + pub lexeme: String, + pub span: std::ops::Range, +} +``` -Here the scanner/lexer take the raw source code and converts in into a list of [predefined tokens](./src/scanner.rs). +The `Lexer` uses [logos](https://github.com/maciejhirsz/logos) to identify different token types such as keywords, identifiers, literals, and operators. It also return the span (start and end positions) of each token in the source code. -Here I am using rust [logos](https://github.com/maciejhirsz/logos) to create the lexer, which converts the source code into vector of tokens. +## Parsing -For example, the source code: +The parsing stage is implemented using a recursive descent parser with [Pratt parsing](https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html) for expressions. The `Parser` struct is responsible for this stage: ```rust -let a = 10; -print(a); +pub struct Parser<'a> { + lexer: &'a mut Lexer, +} ``` -Get's converted into: +The parser uses methods like `parse_statement()`, `parse_expression()`, and various other parsing functions to build the Abstract Syntax Tree (AST). + +The expression parsing uses the [Pratt parsing](https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html) technique for handling operator precedence: ```rust -[LET, Identifier, EQUAL, Number(10.0), SEMICOLON, PRINT, LeftParen, Identifier, RightParen, SEMICOLON] +fn expr_bp(lexer: &mut Lexer, min_bp: u8) -> ParseResult { + // ... (Pratt parsing implementation by matklad) +} ``` -### Parsing +This allows for efficient and correct parsing of complex expressions with different operator precedences. -[parser.rs](./src/ast.rs) +The AST is represented using the `ASTNode` enum: + +```rust +pub enum ASTNode { + IntNumber(i64), + FloatNumber(f64), + Identifier(String), + Boolean(bool), + String(String), + Op(Ops, Vec), + Callee(String, Vec), + Let(String, Vec), + Assign(String, Vec), + If(Vec, Vec, Option>), + While(Vec, Vec), + Print(Vec), + Function(String, Vec, Vec), + Block(Vec), +} +``` -The parser takes the list of tokens and converts it into an Abstract Syntax Tree (AST). Here we also handle the precedence of the operators, syntax errors, syntatic sugar like `+=`, `-=`, etc. +This structure allows for representing various language constructs, including literals, variables, function calls, control flow statements, and more. -For example, the tokens: +## Code Generation + +The code generation phase transforms the AST into bytecode that can be executed by the Virtual Machine. This process is handled by the `Compiler` struct: + +```rust +pub struct Compiler { + chunk: Chunk, + interner: Interner, + locals: Vec, + local_count: usize, + scope_depth: u8, + functions: Vec, + function_count: usize, +} +``` + +The compiler emits bytecode instructions represented by the `OpCode` enum: ```rust -[LET, Identifier, EQUAL, Number(10.0), SEMICOLON, PRINT, LeftParen, Identifier, RightParen, SEMICOLON] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[repr(u8)] +pub enum OpCode { + OpConstant, + OpNil, + OpTrue, + OpFalse, + // ... (other opcodes) +} ``` -Get's converted into: +These instructions, along with their operands, are stored in a `Chunk`: ```rust -Let("a", [Number(10.0)]) -Print([Identifier("a")]) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Chunk { + pub code: Vec, + pub constants: Vec, +} ``` -I am using a combination of recursive descent parser and [pratt parser](https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html) to parse the tokens. +The `VectorType` enum allows for efficient storage of both opcodes and constant indices in the same vector. -Pratt parser is used to parse the expressions, and recursive descent parser for everything else. +## Virtual Machine -### Compilation +The Virtual Machine (VM) is responsible for executing the generated bytecode. It's implemented in the `VM` struct: -[compiler.rs](./src/compiler.rs) +```rust +pub struct VM { + pub chunk: Chunk, + ip: usize, + stack: [ValueType; STACK_MAX], + stack_top: usize, + pub interner: Interner, + globals: HashMap, + call_frames: Vec, + frame_index: usize, +} +``` + +The VM uses a stack-based architecture for executing instructions. It maintains a stack for operands and local variables, a global variable table, and call frames for function calls. + +The main execution loop of the VM interprets each opcode and performs the corresponding operation: + +```rust +pub fn run(&mut self) -> Result { + // ... (main execution loop) +} +``` -The compiler takes the AST and converts it into a list of bytecode instructions. +## String Interning -For example, the AST: +To optimize string handling, the compiler uses string interning via the `Interner` struct: ```rust -Let("a", [Number(10.0)]) -Print([Identifier("a")]) +pub struct Interner { + pub map: HashMap, + vec: Vec, +} ``` -Get's converted into: +This technique allows for efficient storage and comparison of strings by assigning unique indices to each unique string. + +## Example: Program Compilation and Execution + +Here's a simple program to demonstrate how it progresses through each stage of the compiler. + +### Sample Program ``` -0 OP_CONSTANT cons->[1] | tnsr->10 -2 OP_DEFINE_GLOBAL cons->[0] | intr->a -4 OP_GET_GLOBAL cons->[2] | intr->a -6 OP_PRINT -7 OP_RETURN +let a = 4.0; +let b = 2**2; + +print(a + b); ``` -[Chunk](./src/chunk.rs) - It's stored the bytecode instructions along with any constants like strings, numbers, etc. +### Stage 1: Lexical Analysis + +The lexer breaks down the program into tokens: -### Execution +``` +[ + Token { token_type: PRINT, lexeme: "print", span: 0..5 } + Token { token_type: LeftParen, lexeme: "(", span: 5..6 } + Token { token_type: String, lexeme: "\"Hello, world!\"", span: 6..21 } + Token { token_type: RightParen, lexeme: ")", span: 21..22 } + Token { token_type: SEMICOLON, lexeme: ";", span: 22..23 } + Token { token_type: LET, lexeme: "let", span: 25..28 } + Token { token_type: Identifier, lexeme: "a", span: 29..30 } + ... +] +``` + +### Stage 2: Parsing and AST Generation -[vm.rs](./src/vm.rs) +The parser creates an Abstract Syntax Tree: -The VM takes the list of bytecode instructions and executes them. It is simply a loop that reads the instructions and executes them. +``` +Print + String(""Hello, world!"") +Let(a) + FloatNumber(4) +Let(b) + Op(PostfixOp(StarStar)) + IntNumber(2) + IntNumber(2) +Print + Op(BinaryOp(Add)) + Identifier(a) + Identifier(b) +``` -The VM has a stack-based architecture, which means that the instructions are executed by pushing and popping values from the stack. +### Stage 3: Code Generation -For example, the bytecode: +The compiler generates bytecode: ``` -0 OP_CONSTANT cons->[1] | tnsr->10 -2 OP_DEFINE_GLOBAL cons->[0] | intr->a -4 OP_GET_GLOBAL cons->[2] | intr->a -6 OP_PRINT -7 OP_RETURN +0000 OP_CONSTANT 0 | intr->"Hello, world!" +0002 OP_PRINT +0003 OP_CONSTANT 2 | 4 +0005 OP_DEFINE_GLOBAL 1 | intr->a +0007 OP_CONSTANT 4 | 2 +0009 OP_CONSTANT 5 | 2 +0011 OP_POWER +0012 OP_DEFINE_GLOBAL 3 | intr->b +0014 OP_GET_GLOBAL 6 | intr->a +0016 OP_GET_GLOBAL 7 | intr->b +0018 OP_ADD +0019 OP_PRINT +0020 OP_RETURN ``` -Get's executed and prints: +### Stage 4: Execution + +The VM executes the bytecode, resulting in the output: ``` -10 +Hello, world! +8 ``` -## References +## Future Improvements + +While this compiler implements core functionality, there are several areas for potential improvement: + +1. **Type System**: Implement a static type system with type inference for improved safety and performance. + +2. **Optimization**: Add optimization passes to improve the generated bytecode's efficiency. + +3. **Error Handling**: Enhance error reporting with more detailed messages and source code locations. + +4. **Standard Library**: Develop a comprehensive standard library for common operations. + +5. **Garbage Collection**: Implement a garbage collector for automatic memory management. + +6. **Just-In-Time (JIT) Compilation**: Add a JIT compiler for improved runtime performance. + +7. **Concurrency**: Implement language-level constructs for concurrent programming. + +8. **Modules**: Add a module system for better code organization and reusability. + +9. **Debugger**: Create a debugger for easier development and troubleshooting. + +10. **REPL**: Implement a Read-Eval-Print Loop for interactive programming. -- [Crafting Interpreters](https://craftinginterpreters.com/) -- [Pratt Parsing](https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html) -- [Logos](https://github.com/maciejhirsz/logos) +These improvements would significantly enhance the language's capabilities, performance, and developer experience. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 1c9e37a..0000000 --- a/docs/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# [name] - -A simple bytecode compiler written in Rust with stack-based VM. - -## Overview - - - - - -## Getting Started - -Try the language in the [playground]([name].vercel.app). - -### Example - -```bash -cargo install [name] -echo "let a = 10; print(a);" > example.rs -[name] run example.rs -``` - -Try more examples in the [examples](./examples) directory. - -## Features - -- [x] Variables -- [x] Arithmetic operations -- [x] Logical operations -- [x] Control flow (if, else) -- [x] Loops (while) -- [ ] Functions -- [ ] Closures -- [ ] Autograd -- [ ] Tensor Support - -## Implementation - -The source code goes through the following stages: - -1. [Tokenization](#tokenization) -2. [Parsing](#parsing) -3. [Compilation](#compilation) -4. [Execution](#execution) - -### Tokenization - -[scanner.rs](./src/scanner.rs) - -Here the scanner/lexer take the raw source code and converts in into a list of [predefined tokens](./src/scanner.rs). - -Here I am using rust [logos](https://github.com/maciejhirsz/logos) to create the lexer, which converts the source code into vector of tokens. - -For example, the source code: - -```rust -let a = 10; -print(a); -``` - -Get's converted into: - -```rust -[LET, Identifier, EQUAL, Number(10.0), SEMICOLON, PRINT, LeftParen, Identifier, RightParen, SEMICOLON] -``` - -### Parsing - -[parser.rs](./src/ast.rs) - -The parser takes the list of tokens and converts it into an Abstract Syntax Tree (AST). Here we also handle the precedence of the operators, syntax errors, syntatic sugar like `+=`, `-=`, etc. - -For example, the tokens: - -```rust -[LET, Identifier, EQUAL, Number(10.0), SEMICOLON, PRINT, LeftParen, Identifier, RightParen, SEMICOLON] -``` - -Get's converted into: - -```rust -Let("a", [Number(10.0)]) -Print([Identifier("a")]) -``` - -I am using a combination of recursive descent parser and [pratt parser](https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html) to parse the tokens. - -Pratt parser is used to parse the expressions, and recursive descent parser for everything else. - -### Compilation - -[compiler.rs](./src/compiler.rs) - -The compiler takes the AST and converts it into a list of bytecode instructions. - -For example, the AST: - -```rust -Let("a", [Number(10.0)]) -Print([Identifier("a")]) -``` - -Get's converted into: - -``` -0 OP_CONSTANT cons->[1] | tnsr->10 -2 OP_DEFINE_GLOBAL cons->[0] | intr->a -4 OP_GET_GLOBAL cons->[2] | intr->a -6 OP_PRINT -7 OP_RETURN -``` - -[Chunk](./src/chunk.rs) - It's stored the bytecode instructions along with any constants like strings, numbers, etc. - -### Execution - -[vm.rs](./src/vm.rs) - -The VM takes the list of bytecode instructions and executes them. It is simply a loop that reads the instructions and executes them. - -The VM has a stack-based architecture, which means that the instructions are executed by pushing and popping values from the stack. - -For example, the bytecode: - -``` -0 OP_CONSTANT cons->[1] | tnsr->10 -2 OP_DEFINE_GLOBAL cons->[0] | intr->a -4 OP_GET_GLOBAL cons->[2] | intr->a -6 OP_PRINT -7 OP_RETURN -``` - -Get's executed and prints: - -``` -10 -``` - -## References - -- [Crafting Interpreters](https://craftinginterpreters.com/) -- [Pratt Parsing](https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html) -- [Logos](https://github.com/maciejhirsz/logos) diff --git a/docs/ai.d.ts b/docs/ai.d.ts deleted file mode 100644 index 7103c73..0000000 --- a/docs/ai.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** -* @param {string} src -* @returns {string} -*/ -export function run_source(src: string): string; - -export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; - -export interface InitOutput { - readonly memory: WebAssembly.Memory; - readonly run_source: (a: number, b: number, c: number) => void; - readonly __wbindgen_add_to_stack_pointer: (a: number) => number; - readonly __wbindgen_malloc: (a: number, b: number) => number; - readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; - readonly __wbindgen_free: (a: number, b: number, c: number) => void; -} - -export type SyncInitInput = BufferSource | WebAssembly.Module; -/** -* Instantiates the given `module`, which can either be bytes or -* a precompiled `WebAssembly.Module`. -* -* @param {SyncInitInput} module -* -* @returns {InitOutput} -*/ -export function initSync(module: SyncInitInput): InitOutput; - -/** -* If `module_or_path` is {RequestInfo} or {URL}, makes a request and -* for everything else, calls `WebAssembly.instantiate` directly. -* -* @param {InitInput | Promise} module_or_path -* -* @returns {Promise} -*/ -export default function __wbg_init (module_or_path?: InitInput | Promise): Promise; diff --git a/docs/ai.js b/docs/ai.js deleted file mode 100644 index a831b10..0000000 --- a/docs/ai.js +++ /dev/null @@ -1,196 +0,0 @@ -let wasm; - -let WASM_VECTOR_LEN = 0; - -let cachedUint8Memory0 = null; - -function getUint8Memory0() { - if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { - cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); - } - return cachedUint8Memory0; -} - -const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); - -const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' - ? function (arg, view) { - return cachedTextEncoder.encodeInto(arg, view); -} - : function (arg, view) { - const buf = cachedTextEncoder.encode(arg); - view.set(buf); - return { - read: arg.length, - written: buf.length - }; -}); - -function passStringToWasm0(arg, malloc, realloc) { - - if (realloc === undefined) { - const buf = cachedTextEncoder.encode(arg); - const ptr = malloc(buf.length, 1) >>> 0; - getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); - WASM_VECTOR_LEN = buf.length; - return ptr; - } - - let len = arg.length; - let ptr = malloc(len, 1) >>> 0; - - const mem = getUint8Memory0(); - - let offset = 0; - - for (; offset < len; offset++) { - const code = arg.charCodeAt(offset); - if (code > 0x7F) break; - mem[ptr + offset] = code; - } - - if (offset !== len) { - if (offset !== 0) { - arg = arg.slice(offset); - } - ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; - const view = getUint8Memory0().subarray(ptr + offset, ptr + len); - const ret = encodeString(arg, view); - - offset += ret.written; - ptr = realloc(ptr, len, offset, 1) >>> 0; - } - - WASM_VECTOR_LEN = offset; - return ptr; -} - -let cachedInt32Memory0 = null; - -function getInt32Memory0() { - if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { - cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); - } - return cachedInt32Memory0; -} - -const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); - -if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; - -function getStringFromWasm0(ptr, len) { - ptr = ptr >>> 0; - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); -} -/** -* @param {string} src -* @returns {string} -*/ -export function run_source(src) { - let deferred2_0; - let deferred2_1; - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - wasm.run_source(retptr, ptr0, len0); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - deferred2_0 = r0; - deferred2_1 = r1; - return getStringFromWasm0(r0, r1); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); - } -} - -async function __wbg_load(module, imports) { - if (typeof Response === 'function' && module instanceof Response) { - if (typeof WebAssembly.instantiateStreaming === 'function') { - try { - return await WebAssembly.instantiateStreaming(module, imports); - - } catch (e) { - if (module.headers.get('Content-Type') != 'application/wasm') { - console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); - - } else { - throw e; - } - } - } - - const bytes = await module.arrayBuffer(); - return await WebAssembly.instantiate(bytes, imports); - - } else { - const instance = await WebAssembly.instantiate(module, imports); - - if (instance instanceof WebAssembly.Instance) { - return { instance, module }; - - } else { - return instance; - } - } -} - -function __wbg_get_imports() { - const imports = {}; - imports.wbg = {}; - - return imports; -} - -function __wbg_init_memory(imports, maybe_memory) { - -} - -function __wbg_finalize_init(instance, module) { - wasm = instance.exports; - __wbg_init.__wbindgen_wasm_module = module; - cachedInt32Memory0 = null; - cachedUint8Memory0 = null; - - - return wasm; -} - -function initSync(module) { - if (wasm !== undefined) return wasm; - - const imports = __wbg_get_imports(); - - __wbg_init_memory(imports); - - if (!(module instanceof WebAssembly.Module)) { - module = new WebAssembly.Module(module); - } - - const instance = new WebAssembly.Instance(module, imports); - - return __wbg_finalize_init(instance, module); -} - -async function __wbg_init(input) { - if (wasm !== undefined) return wasm; - - if (typeof input === 'undefined') { - input = new URL('ai_bg.wasm', import.meta.url); - } - const imports = __wbg_get_imports(); - - if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { - input = fetch(input); - } - - __wbg_init_memory(imports); - - const { instance, module } = await __wbg_load(await input, imports); - - return __wbg_finalize_init(instance, module); -} - -export { initSync } -export default __wbg_init; diff --git a/docs/ai_bg.js b/docs/ai_bg.js deleted file mode 100644 index 7c9f32d..0000000 --- a/docs/ai_bg.js +++ /dev/null @@ -1,115 +0,0 @@ -let wasm; -export function __wbg_set_wasm(val) { - wasm = val; -} - - -let WASM_VECTOR_LEN = 0; - -let cachedUint8Memory0 = null; - -function getUint8Memory0() { - if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { - cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); - } - return cachedUint8Memory0; -} - -const lTextEncoder = typeof TextEncoder === 'undefined' ? (0, module.require)('util').TextEncoder : TextEncoder; - -let cachedTextEncoder = new lTextEncoder('utf-8'); - -const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' - ? function (arg, view) { - return cachedTextEncoder.encodeInto(arg, view); -} - : function (arg, view) { - const buf = cachedTextEncoder.encode(arg); - view.set(buf); - return { - read: arg.length, - written: buf.length - }; -}); - -function passStringToWasm0(arg, malloc, realloc) { - - if (realloc === undefined) { - const buf = cachedTextEncoder.encode(arg); - const ptr = malloc(buf.length, 1) >>> 0; - getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); - WASM_VECTOR_LEN = buf.length; - return ptr; - } - - let len = arg.length; - let ptr = malloc(len, 1) >>> 0; - - const mem = getUint8Memory0(); - - let offset = 0; - - for (; offset < len; offset++) { - const code = arg.charCodeAt(offset); - if (code > 0x7F) break; - mem[ptr + offset] = code; - } - - if (offset !== len) { - if (offset !== 0) { - arg = arg.slice(offset); - } - ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; - const view = getUint8Memory0().subarray(ptr + offset, ptr + len); - const ret = encodeString(arg, view); - - offset += ret.written; - ptr = realloc(ptr, len, offset, 1) >>> 0; - } - - WASM_VECTOR_LEN = offset; - return ptr; -} - -let cachedInt32Memory0 = null; - -function getInt32Memory0() { - if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { - cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); - } - return cachedInt32Memory0; -} - -const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; - -let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); - -cachedTextDecoder.decode(); - -function getStringFromWasm0(ptr, len) { - ptr = ptr >>> 0; - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); -} -/** -* @param {string} src -* @returns {string} -*/ -export function run_source(src) { - let deferred2_0; - let deferred2_1; - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - wasm.run_source(retptr, ptr0, len0); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - deferred2_0 = r0; - deferred2_1 = r1; - return getStringFromWasm0(r0, r1); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); - } -} - diff --git a/docs/ai_bg.wasm b/docs/ai_bg.wasm deleted file mode 100644 index d448136..0000000 Binary files a/docs/ai_bg.wasm and /dev/null differ diff --git a/docs/ai_bg.wasm.d.ts b/docs/ai_bg.wasm.d.ts deleted file mode 100644 index e04b6b5..0000000 --- a/docs/ai_bg.wasm.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -export const memory: WebAssembly.Memory; -export function run_source(a: number, b: number, c: number): void; -export function __wbindgen_add_to_stack_pointer(a: number): number; -export function __wbindgen_malloc(a: number, b: number): number; -export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number; -export function __wbindgen_free(a: number, b: number, c: number): void; diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index bf55351..0000000 --- a/docs/index.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - Rust WASM Example - - -

Rust WASM Example

- -
- -
-

-
-    
-
-
\ No newline at end of file
diff --git a/docs/package.json b/docs/package.json
deleted file mode 100644
index 552d5bf..0000000
--- a/docs/package.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "name": "ai",
-  "description": "A programming language with built-in autograd",
-  "version": "0.1.0",
-  "files": [
-    "ai_bg.wasm",
-    "ai.js",
-    "ai.d.ts"
-  ],
-  "module": "ai.js",
-  "types": "ai.d.ts",
-  "sideEffects": [
-    "./snippets/*"
-  ]
-}
\ No newline at end of file
diff --git a/src/ast.rs b/src/ast.rs
index 2028367..fe6803f 100644
--- a/src/ast.rs
+++ b/src/ast.rs
@@ -1,69 +1,94 @@
-use crate::{
-    scanner::{Lexer, TokenType},
-    vm,
-};
-use std::fmt;
+use core::fmt;
 
-#[derive(Debug, Clone)]
+use crate::scanner::{Lexer, TokenType};
+use serde::{Deserialize, Serialize};
+
+/// Represents a node in the Abstract Syntax Tree (AST)
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub enum ASTNode {
-    // Number(f64),
     IntNumber(i64),
     FloatNumber(f64),
     Identifier(String),
     Boolean(bool),
     String(String),
     Op(Ops, Vec),
-    Callee(String, Vec), // function call with arguments
+    Callee(String, Vec),
     Let(String, Vec),
     Assign(String, Vec),
-    If(Vec, Vec, Option>), // condition, then, else
-    While(Vec, Vec),                    // condition, body
+    If(Vec, Vec, Option>),
+    While(Vec, Vec),
     Print(Vec),
     Function(String, Vec, Vec),
-    Block(Vec), // depth, statements
+    Block(Vec),
 }
 
-#[derive(Debug, Clone, Copy, PartialEq)]
+/// Represents binary operations
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
 pub enum BinaryOp {
     Add,
     Sub,
     Mul,
     Div,
-    At, // dot product
-
-    // comparison
-    Eq, // ==
-    Ne, // !=
-    Lt, // <
-    Le, // <=
-    Gt, // >
-    Ge, // >=
+    At,
+    Eq,
+    Ne,
+    Lt,
+    Le,
+    Gt,
+    Ge,
 }
 
-#[derive(Debug, Clone, Copy, PartialEq)]
+/// Represents unary operations
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
 pub enum UnaryOp {
     Negate,
-    Not, // ! - logical not
+    Not,
 }
 
-#[derive(Debug, Clone, Copy, PartialEq)]
+/// Represents postfix operations
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
 pub enum PostfixOp {
     Index,
     Call,
-    StarStar, // exponentiation
+    StarStar,
 }
 
-#[derive(Debug, Clone, Copy, PartialEq)]
+/// Combines all operation types
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
 pub enum Ops {
     BinaryOp(BinaryOp),
     UnaryOp(UnaryOp),
     PostfixOp(PostfixOp),
 }
 
-//////////////////////////////
-/// Recursive Descent Parser
-//////////////////////////////
+#[derive(Debug)]
+pub enum ParseError {
+    UnexpectedToken(TokenType, String),
+    MissingToken(TokenType, String),
+    InvalidOperator(String),
+    SyntaxError(String),
+}
+
+impl std::fmt::Display for ParseError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            ParseError::UnexpectedToken(token, context) => {
+                write!(f, "Unexpected token {:?} {}", token, context)
+            }
+            ParseError::MissingToken(token, context) => {
+                write!(f, "Missing token {:?} {}", token, context)
+            }
+            ParseError::InvalidOperator(msg) => write!(f, "Invalid operator: {}", msg),
+            ParseError::SyntaxError(msg) => write!(f, "Syntax error: {}", msg),
+        }
+    }
+}
+
+impl std::error::Error for ParseError {}
 
+type ParseResult = Result;
+
+/// Parser struct for recursive descent parsing
 pub struct Parser<'a> {
     lexer: &'a mut Lexer,
 }
@@ -72,94 +97,166 @@ impl<'a> Parser<'a> {
     pub fn new(lexer: &mut Lexer) -> Parser {
         Parser { lexer }
     }
-    pub fn parse(&mut self) -> Vec {
-        let mut statements = vec![];
 
-        let mut all_errors = vec![];
+    /// Main parsing function
+    pub fn parse(&mut self) -> ParseResult> {
+        let mut statements = vec![];
 
         while self.lexer.peek().token_type != TokenType::EOF {
-            let statement = match self.lexer.peek().token_type {
-                TokenType::PRINT => self.parse_print(),
-                TokenType::LET => self.parse_let(),
-                TokenType::FN => self.parse_function(),
-                TokenType::LeftBrace => {
-                    self.lexer.next();
-                    let statements = self.parse();
-                    assert_eq!(self.lexer.next().token_type, TokenType::RightBrace);
-                    ASTNode::Block(statements)
-                }
-                TokenType::RightBrace => break,
-                // contains equal pr +=, -=, *=, /=
-                TokenType::Identifier
-                    if self.lexer.peek_n_type(2).contains(&TokenType::EQUAL)
-                        || self.lexer.peek_n_type(2).contains(&TokenType::PlusEqual)
-                        || self.lexer.peek_n_type(2).contains(&TokenType::MinusEqual)
-                        || self.lexer.peek_n_type(2).contains(&TokenType::StarEqual)
-                        || self.lexer.peek_n_type(2).contains(&TokenType::SlashEqual) =>
-                {
-                    match self.parse_assign() {
-                        Ok(ast) => ast,
-                        Err(e) => {
-                            all_errors.push(e);
-                            self.lexer.next();
-                            continue;
-                        }
-                    }
-                }
-                TokenType::IF => {
-                    self.lexer.next();
-                    assert_eq!(self.lexer.next().token_type, TokenType::LeftParen);
-                    let condition = self.parse_expression();
-                    assert_eq!(self.lexer.next().token_type, TokenType::RightParen);
-                    let then_branch = self.parse_block();
-                    let else_branch = if self.lexer.peek().token_type == TokenType::ELSE {
-                        self.lexer.next();
-                        Some(self.parse_block())
-                    } else {
-                        None
-                    };
-                    ASTNode::If(vec![condition], then_branch, else_branch)
-                }
-                TokenType::WHILE => {
-                    self.lexer.next();
-                    assert_eq!(self.lexer.next().token_type, TokenType::LeftParen);
-                    let condition = self.parse_expression();
-                    assert_eq!(self.lexer.next().token_type, TokenType::RightParen);
-                    let body = self.parse_block();
-                    ASTNode::While(vec![condition], body)
-                }
-                TokenType::SEMICOLON => {
-                    self.lexer.next();
-                    continue;
-                }
-                _ => self.parse_expression(),
-            };
-            statements.push(statement);
+            statements.push(self.parse_statement()?);
         }
 
-        statements
+        Ok(statements)
     }
 
-    fn parse_print(&mut self) -> ASTNode {
+    /// Parse a single statement
+    fn parse_statement(&mut self) -> ParseResult {
+        let statement = match self.lexer.peek().token_type {
+            TokenType::PRINT => self.parse_print(),
+            TokenType::LET => self.parse_let(),
+            TokenType::FN => self.parse_function(),
+            TokenType::LeftBrace => self.parse_block(),
+            TokenType::IF => self.parse_if(),
+            TokenType::WHILE => self.parse_while(),
+            TokenType::Identifier if self.is_assignment() => self.parse_assign(),
+            TokenType::SEMICOLON => {
+                self.lexer.next(); // Consume the semicolon
+                return Ok(ASTNode::Block(vec![])); // Return an empty block for lone semicolons
+            }
+            _ => self.parse_expression(),
+        }?;
+
+        // Consume the semicolon if present
+        if self.lexer.peek().token_type == TokenType::SEMICOLON {
+            self.lexer.next();
+        }
+
+        Ok(statement)
+    }
+    fn parse_print(&mut self) -> ParseResult {
         self.lexer.next();
-        assert_eq!(self.lexer.next().token_type, TokenType::LeftParen);
-        let expr = self.parse_expression();
-        assert_eq!(self.lexer.next().token_type, TokenType::RightParen);
-        ASTNode::Print(vec![expr])
+        if self.lexer.next().token_type != TokenType::LeftParen {
+            return Err(ParseError::SyntaxError(
+                "Expected '(' after print".to_string(),
+            ));
+        }
+        let expr = self.parse_expression()?;
+        if self.lexer.next().token_type != TokenType::RightParen {
+            return Err(ParseError::MissingToken(
+                TokenType::RightParen,
+                "to close print statement".to_string(),
+            ));
+        }
+        Ok(ASTNode::Print(vec![expr]))
     }
 
-    fn parse_let(&mut self) -> ASTNode {
+    fn parse_let(&mut self) -> ParseResult {
         self.lexer.next();
         let identifier = self.lexer.next().lexeme;
-        assert_eq!(self.lexer.next().token_type, TokenType::EQUAL);
-        let expr = self.parse_expression();
-        ASTNode::Let(identifier, vec![expr])
+        if self.lexer.next().token_type != TokenType::EQUAL {
+            return Err(ParseError::MissingToken(
+                TokenType::EQUAL,
+                "to assign value to variable".to_string(),
+            ));
+        }
+        let expr = self.parse_expression()?;
+        Ok(ASTNode::Let(identifier, vec![expr]))
+    }
+
+    // TODO: might need fixing
+    fn parse_block(&mut self) -> ParseResult {
+        self.lexer.next();
+        let mut statements = vec![];
+        while self.lexer.peek().token_type != TokenType::RightBrace {
+            statements.push(self.parse_statement()?);
+        }
+        self.lexer.next(); // consume RightBrace
+        Ok(ASTNode::Block(statements))
+    }
+
+    // fn parse_block(&mut self) -> ParseResult> {
+    //     // assert_eq!(self.lexer.next().token_type, TokenType::LeftBrace);
+    //     if self.lexer.next().token_type != TokenType::LeftBrace {
+    //         return Err(ParseError::MissingToken(
+    //             TokenType::LeftBrace,
+    //             "to start block".to_string(),
+    //         ));
+    //     }
+    //     let statements = self.parse();
+    //     // assert_eq!(self.lexer.next().token_type, TokenType::RightBrace);
+    //     if self.lexer.next().token_type != TokenType::RightBrace {
+    //         return Err(ParseError::MissingToken(
+    //             TokenType::RightBrace,
+    //             "to close block".to_string(),
+    //         ));
+    //     }
+    //     statements
+    // }
+
+    // TODO: might need fixing
+    fn parse_if(&mut self) -> ParseResult {
+        self.lexer.next();
+        if self.lexer.next().token_type != TokenType::LeftParen {
+            return Err(ParseError::MissingToken(
+                TokenType::LeftParen,
+                "to start if condition".to_string(),
+            ));
+        }
+        let condition = self.parse_expression()?;
+        if self.lexer.next().token_type != TokenType::RightParen {
+            return Err(ParseError::MissingToken(
+                TokenType::RightParen,
+                "to close if condition".to_string(),
+            ));
+        }
+        // let then_branch = self.parse_block()?;
+
+        // let else_branch = if self.lexer.peek().token_type == TokenType::ELSE {
+        //     self.lexer.next();
+        //     Some(self.parse_block()?)
+        // } else {
+        //     None
+        // };
+        let then_branch = vec![self.parse_statement()?];
+        let else_branch = if self.lexer.peek().token_type == TokenType::ELSE {
+            self.lexer.next();
+            Some(vec![self.parse_statement()?])
+        } else {
+            None
+        };
+        Ok(ASTNode::If(vec![condition], then_branch, else_branch))
+    }
+
+    // TODO: might need fixing
+    fn parse_while(&mut self) -> ParseResult {
+        self.lexer.next();
+        if self.lexer.next().token_type != TokenType::LeftParen {
+            return Err(ParseError::MissingToken(
+                TokenType::LeftParen,
+                "to start while condition".to_string(),
+            ));
+        }
+        let condition = self.parse_expression()?;
+        if self.lexer.next().token_type != TokenType::RightParen {
+            return Err(ParseError::MissingToken(
+                TokenType::RightParen,
+                "to close while condition".to_string(),
+            ));
+        }
+        // let body = self.parse_block()?;
+        let body = vec![self.parse_statement()?];
+        Ok(ASTNode::While(vec![condition], body))
     }
 
-    fn parse_function(&mut self) -> ASTNode {
+    fn parse_function(&mut self) -> ParseResult {
         self.lexer.next();
         let name = self.lexer.next().lexeme;
-        assert_eq!(self.lexer.next().token_type, TokenType::LeftParen);
+        if self.lexer.next().token_type != TokenType::LeftParen {
+            return Err(ParseError::MissingToken(
+                TokenType::LeftParen,
+                "to start function parameters".to_string(),
+            ));
+        }
         let mut params = vec![];
         while self.lexer.peek().token_type != TokenType::RightParen {
             params.push(self.lexer.next().lexeme);
@@ -168,222 +265,263 @@ impl<'a> Parser<'a> {
             }
         }
         self.lexer.next(); // consume RightParen
-        let body = self.parse_block();
-        ASTNode::Function(name, params, body)
+                           // let body = self.parse_block()?;
+        let body = vec![self.parse_statement()?];
+        Ok(ASTNode::Function(name, params, body))
     }
-
-    fn parse_assign(&mut self) -> Result {
-        let identifier = self.lexer.next().lexeme;
-        let op_lexer = self.lexer.next();
-        let op = op_lexer.token_type;
-        let expr = self.parse_expression();
-        let expr = if op == TokenType::EQUAL {
-            expr
-        } else {
-            let op = match op {
-                TokenType::PlusEqual => Ops::BinaryOp(BinaryOp::Add),
-                TokenType::MinusEqual => Ops::BinaryOp(BinaryOp::Sub),
-                TokenType::StarEqual => Ops::BinaryOp(BinaryOp::Mul),
-                TokenType::SlashEqual => Ops::BinaryOp(BinaryOp::Div),
-                _ => {
-                    return Err(vm::Result::CompileErr(format!(
-                        "Invalid operator: {:?} on line: {:?}",
-                        op, op_lexer.span
-                    )))
-                }
-            };
-            ASTNode::Op(op, vec![ASTNode::Identifier(identifier.clone()), expr])
+    fn parse_assign(&mut self) -> ParseResult {
+        let id = self.lexer.next().lexeme;
+        let op = self.lexer.next().token_type;
+        let expr = self.parse_expression()?;
+
+        let expr = match op {
+            TokenType::EQUAL => expr,
+            TokenType::PlusEqual
+            | TokenType::MinusEqual
+            | TokenType::StarEqual
+            | TokenType::SlashEqual => {
+                let bin_op = match op {
+                    TokenType::PlusEqual => BinaryOp::Add,
+                    TokenType::MinusEqual => BinaryOp::Sub,
+                    TokenType::StarEqual => BinaryOp::Mul,
+                    TokenType::SlashEqual => BinaryOp::Div,
+                    _ => {
+                        return Err(ParseError::InvalidOperator(format!(
+                            "Invalid assignment operator: {:?}",
+                            op
+                        )))
+                    }
+                };
+                ASTNode::Op(
+                    Ops::BinaryOp(bin_op),
+                    vec![ASTNode::Identifier(id.clone()), expr],
+                )
+            }
+            _ => {
+                return Err(ParseError::InvalidOperator(format!(
+                    "Invalid assignment operator: {:?}",
+                    op
+                )))
+            }
         };
-        Ok(ASTNode::Assign(identifier, vec![expr]))
-    }
 
-    fn parse_block(&mut self) -> Vec {
-        assert_eq!(self.lexer.next().token_type, TokenType::LeftBrace);
-        let statements = self.parse();
-        assert_eq!(self.lexer.next().token_type, TokenType::RightBrace);
-        statements
+        Ok(ASTNode::Assign(id, vec![expr]))
     }
 
-    fn parse_expression(&mut self) -> ASTNode {
+    /// Parse an expression using Pratt parsing
+    fn parse_expression(&mut self) -> ParseResult {
         expr_bp(self.lexer, 0)
     }
-}
-
-////////////////////////////// Pratt Parser //////////////////////////////
-/// Pratt parser for parsing expressions from https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
-//////////////////////////////////////////////////////////////////////////
-
-fn expr_bp(lexer: &mut Lexer, min_bp: u8) -> ASTNode {
-    let current_token = lexer.next();
-
-    let mut lhs = match current_token.token_type {
-        // TokenType::Number(it) => ASTNode::Number(it),
-        TokenType::IntNumber(it) => ASTNode::IntNumber(it),
-        TokenType::FloatNumber(it) => ASTNode::FloatNumber(it),
-        TokenType::Identifier => ASTNode::Identifier(current_token.lexeme),
-        TokenType::Boolean(it) => ASTNode::Boolean(it),
-        TokenType::String => ASTNode::String(current_token.lexeme),
-        TokenType::LeftParen => {
-            let lhs = expr_bp(lexer, 0);
-            assert_eq!(lexer.next().token_type, TokenType::RightParen);
-            lhs
-        }
-        TokenType::PLUS
-        | TokenType::MINUS
-        | TokenType::STAR
-        | TokenType::SLASH
-        | TokenType::AT
-        | TokenType::EqualEqual
-        | TokenType::BangEqual
-        | TokenType::LESS
-        | TokenType::LessEqual
-        | TokenType::GREATER
-        | TokenType::GreaterEqual
-        | TokenType::DOT
-        | TokenType::StarStar
-        | TokenType::BANG => {
-            let op = match current_token.token_type {
-                TokenType::PLUS => Ops::BinaryOp(BinaryOp::Add),
-                TokenType::MINUS => Ops::UnaryOp(UnaryOp::Negate),
-                TokenType::STAR => Ops::BinaryOp(BinaryOp::Mul),
-                TokenType::SLASH => Ops::BinaryOp(BinaryOp::Div),
-                TokenType::AT => Ops::BinaryOp(BinaryOp::At),
-
-                TokenType::EqualEqual => Ops::BinaryOp(BinaryOp::Eq),
-                TokenType::BangEqual => Ops::BinaryOp(BinaryOp::Ne),
-                TokenType::LESS => Ops::BinaryOp(BinaryOp::Lt),
-                TokenType::LessEqual => Ops::BinaryOp(BinaryOp::Le),
-                TokenType::GREATER => Ops::BinaryOp(BinaryOp::Gt),
-                TokenType::GreaterEqual => Ops::BinaryOp(BinaryOp::Ge),
-
-                TokenType::DOT => Ops::PostfixOp(PostfixOp::Call),
-                TokenType::StarStar => Ops::PostfixOp(PostfixOp::StarStar),
 
-                TokenType::BANG => Ops::UnaryOp(UnaryOp::Not),
-
-                _ => unreachable!(),
-            };
+    // Helper methods
+    fn is_assignment(&mut self) -> bool {
+        let peek_types = self.lexer.peek_n_type(2);
+        peek_types.contains(&TokenType::EQUAL)
+            || peek_types.contains(&TokenType::PlusEqual)
+            || peek_types.contains(&TokenType::MinusEqual)
+            || peek_types.contains(&TokenType::StarEqual)
+            || peek_types.contains(&TokenType::SlashEqual)
+    }
 
-            let ((), r_bp) = prefix_binding_power(op);
-            let rhs = expr_bp(lexer, r_bp);
-            ASTNode::Op(op, vec![rhs])
+    fn expect_token(&mut self, expected: TokenType, context: &str) -> ParseResult<()> {
+        let token = self.lexer.next();
+        if token.token_type != expected {
+            Err(ParseError::UnexpectedToken(
+                token.token_type,
+                format!("Expected {:?} {}", expected, context),
+            ))
+        } else {
+            Ok(())
         }
-        t => Err(vm::Result::CompileErr(format!("bad token: {:?}", t))).unwrap(),
-    };
+    }
+}
+
+/// Pratt parser for expressions
+fn expr_bp(lexer: &mut Lexer, min_bp: u8) -> ParseResult {
+    let mut lhs = parse_prefix(lexer)?;
 
     loop {
-        let op = match lexer.peek().token_type {
-            TokenType::EOF => break,
-            TokenType::PLUS => Ops::BinaryOp(BinaryOp::Add),
-            TokenType::MINUS => Ops::BinaryOp(BinaryOp::Sub),
-            TokenType::STAR => Ops::BinaryOp(BinaryOp::Mul),
-            TokenType::SLASH => Ops::BinaryOp(BinaryOp::Div),
-            TokenType::AT => Ops::BinaryOp(BinaryOp::At),
-
-            TokenType::EqualEqual => Ops::BinaryOp(BinaryOp::Eq),
-            TokenType::BangEqual => Ops::BinaryOp(BinaryOp::Ne),
-            TokenType::LESS => Ops::BinaryOp(BinaryOp::Lt),
-            TokenType::LessEqual => Ops::BinaryOp(BinaryOp::Le),
-            TokenType::GREATER => Ops::BinaryOp(BinaryOp::Gt),
-            TokenType::GreaterEqual => Ops::BinaryOp(BinaryOp::Ge),
-
-            TokenType::DOT => Ops::PostfixOp(PostfixOp::Call),
-            TokenType::LeftBracket => Ops::PostfixOp(PostfixOp::Index),
-            TokenType::StarStar => Ops::PostfixOp(PostfixOp::StarStar),
-
-            TokenType::BANG => Ops::UnaryOp(UnaryOp::Negate),
-
-            TokenType::LeftParen => break,
-            TokenType::RightParen => break,
-            TokenType::RightBracket => break,
-            TokenType::COMMA => break,
-            TokenType::SEMICOLON => break,
-            t => Err(vm::Result::CompileErr(format!("bad token: {:?}", t))).unwrap(),
+        let op = match infix_op(lexer.peek().token_type) {
+            Some(op) => op,
+            None => break,
         };
 
-        if let Some((l_bp, _)) = postfix_binding_power(op) {
+        if let Some((l_bp, r_bp)) = infix_binding_power(op) {
             if l_bp < min_bp {
                 break;
             }
             lexer.next();
 
-            lhs = if op == Ops::PostfixOp(PostfixOp::Index) {
-                let rhs = expr_bp(lexer, 0);
-                assert_eq!(lexer.next().token_type, TokenType::RightBracket);
-                ASTNode::Op(op, vec![lhs, rhs])
-            } else if op == Ops::PostfixOp(PostfixOp::Call) {
-                let callee_token = lexer.next();
-                assert_eq!(callee_token.token_type, TokenType::Identifier);
-
-                assert_eq!(lexer.next().token_type, TokenType::LeftParen);
-
-                let mut args = Vec::new();
-                while lexer.peek().token_type != TokenType::RightParen {
-                    args.push(expr_bp(lexer, 0));
-                    if lexer.peek().token_type == TokenType::COMMA {
-                        lexer.next();
-                    }
-                }
-
-                lexer.next();
-                ASTNode::Op(op, vec![lhs, ASTNode::Callee(callee_token.lexeme, args)])
-            } else if op == Ops::PostfixOp(PostfixOp::StarStar) {
-                let rhs = expr_bp(lexer, 0);
-                ASTNode::Op(op, vec![lhs, rhs])
-            } else {
-                ASTNode::Op(op, vec![lhs])
-            };
-            continue;
-        }
-
-        if let Some((l_bp, r_bp)) = infix_binding_power(op) {
+            let rhs = expr_bp(lexer, r_bp)?;
+            lhs = ASTNode::Op(op, vec![lhs, rhs]);
+        } else if let Some((l_bp, ())) = postfix_binding_power(op) {
             if l_bp < min_bp {
                 break;
             }
             lexer.next();
 
-            let rhs = expr_bp(lexer, r_bp);
-            lhs = ASTNode::Op(op, vec![lhs, rhs]);
-            continue;
+            lhs = parse_postfix(op, lhs, lexer)?;
+        } else {
+            break;
+        }
+    }
+
+    Ok(lhs)
+}
+
+/// Parse prefix expressions
+fn parse_prefix(lexer: &mut Lexer) -> ParseResult {
+    if lexer.peek().token_type == TokenType::EOF {
+        return Err(ParseError::UnexpectedToken(
+            TokenType::EOF,
+            "Unexpected end of input".to_string(),
+        ));
+    }
+
+    let token = lexer.next();
+    match token.token_type {
+        TokenType::IntNumber(n) => Ok(ASTNode::IntNumber(n)),
+        TokenType::FloatNumber(n) => Ok(ASTNode::FloatNumber(n)),
+        TokenType::Identifier => Ok(ASTNode::Identifier(token.lexeme)),
+        TokenType::Boolean(b) => Ok(ASTNode::Boolean(b)),
+        TokenType::String => Ok(ASTNode::String(token.lexeme)),
+        TokenType::LeftParen => {
+            let expr = expr_bp(lexer, 0)?;
+            if lexer.next().token_type != TokenType::RightParen {
+                return Err(ParseError::MissingToken(
+                    TokenType::RightParen,
+                    "to close parenthesized expression".to_string(),
+                ));
+            }
+            Ok(expr)
+        }
+        TokenType::PLUS | TokenType::MINUS | TokenType::BANG => {
+            let op = match token.token_type {
+                TokenType::MINUS => Ops::UnaryOp(UnaryOp::Negate),
+                TokenType::BANG => Ops::UnaryOp(UnaryOp::Not),
+                _ => {
+                    return Err(ParseError::InvalidOperator(format!(
+                        "Invalid prefix operator: {:?}",
+                        token.token_type
+                    )))
+                }
+            };
+            let ((), r_bp) = prefix_binding_power(op);
+            let rhs = expr_bp(lexer, r_bp)?;
+            Ok(ASTNode::Op(op, vec![rhs]))
+        }
+        _ => Err(ParseError::UnexpectedToken(
+            token.token_type,
+            "in prefix position".to_string(),
+        )),
+    }
+}
+
+/// Parse postfix expressions
+fn parse_postfix(op: Ops, lhs: ASTNode, lexer: &mut Lexer) -> ParseResult {
+    match op {
+        Ops::PostfixOp(PostfixOp::Index) => {
+            let rhs = expr_bp(lexer, 0)?;
+            if lexer.next().token_type != TokenType::RightBracket {
+                return Err(ParseError::MissingToken(
+                    TokenType::RightBracket,
+                    "to close index operation".to_string(),
+                ));
+            }
+            Ok(ASTNode::Op(op, vec![lhs, rhs]))
+        }
+        Ops::PostfixOp(PostfixOp::Call) => {
+            let callee = lexer.next().lexeme;
+            if lexer.next().token_type != TokenType::LeftParen {
+                return Err(ParseError::MissingToken(
+                    TokenType::LeftParen,
+                    "to start function call arguments".to_string(),
+                ));
+            }
+            let args = parse_args(lexer)?;
+            if lexer.next().token_type != TokenType::RightParen {
+                return Err(ParseError::MissingToken(
+                    TokenType::RightParen,
+                    "to close function call arguments".to_string(),
+                ));
+            }
+            Ok(ASTNode::Op(op, vec![lhs, ASTNode::Callee(callee, args)]))
         }
+        Ops::PostfixOp(PostfixOp::StarStar) => {
+            let rhs = expr_bp(lexer, 0)?;
+            Ok(ASTNode::Op(op, vec![lhs, rhs]))
+        }
+        _ => Err(ParseError::InvalidOperator(format!(
+            "Invalid postfix operator: {:?}",
+            op
+        ))),
+    }
+}
 
-        break;
+/// Parse function arguments
+fn parse_args(lexer: &mut Lexer) -> ParseResult> {
+    let mut args = Vec::new();
+    while lexer.peek().token_type != TokenType::RightParen {
+        args.push(expr_bp(lexer, 0)?);
+        if lexer.peek().token_type == TokenType::COMMA {
+            lexer.next();
+        }
     }
+    Ok(args)
+}
 
-    lhs
+/// Get the infix operator from a token type
+fn infix_op(token_type: TokenType) -> Option {
+    match token_type {
+        TokenType::PLUS => Some(Ops::BinaryOp(BinaryOp::Add)),
+        TokenType::MINUS => Some(Ops::BinaryOp(BinaryOp::Sub)),
+        TokenType::STAR => Some(Ops::BinaryOp(BinaryOp::Mul)),
+        TokenType::SLASH => Some(Ops::BinaryOp(BinaryOp::Div)),
+        TokenType::AT => Some(Ops::BinaryOp(BinaryOp::At)),
+        TokenType::EqualEqual => Some(Ops::BinaryOp(BinaryOp::Eq)),
+        TokenType::BangEqual => Some(Ops::BinaryOp(BinaryOp::Ne)),
+        TokenType::LESS => Some(Ops::BinaryOp(BinaryOp::Lt)),
+        TokenType::LessEqual => Some(Ops::BinaryOp(BinaryOp::Le)),
+        TokenType::GREATER => Some(Ops::BinaryOp(BinaryOp::Gt)),
+        TokenType::GreaterEqual => Some(Ops::BinaryOp(BinaryOp::Ge)),
+        TokenType::DOT => Some(Ops::PostfixOp(PostfixOp::Call)),
+        TokenType::LeftBracket => Some(Ops::PostfixOp(PostfixOp::Index)),
+        TokenType::StarStar => Some(Ops::PostfixOp(PostfixOp::StarStar)),
+        _ => None,
+    }
 }
 
+/// Get the binding power for prefix operators
 fn prefix_binding_power(op: Ops) -> ((), u8) {
     match op {
         Ops::UnaryOp(UnaryOp::Not) | Ops::UnaryOp(UnaryOp::Negate) => ((), 15),
-        _ => Err(vm::Result::CompileErr(format!("bad token: {:?}", op))).unwrap(),
+        _ => panic!("Invalid prefix operator: {:?}", op),
     }
 }
 
+/// Get the binding power for postfix operators
 fn postfix_binding_power(op: Ops) -> Option<(u8, ())> {
-    let res = match op {
-        Ops::PostfixOp(PostfixOp::Index) => (13, ()),
-        Ops::PostfixOp(PostfixOp::Call) => (14, ()),
-        Ops::PostfixOp(PostfixOp::StarStar) => (16, ()),
-        _ => return None,
-    };
-    Some(res)
+    match op {
+        Ops::PostfixOp(PostfixOp::Index) => Some((13, ())),
+        Ops::PostfixOp(PostfixOp::Call) => Some((14, ())),
+        Ops::PostfixOp(PostfixOp::StarStar) => Some((16, ())),
+        _ => None,
+    }
 }
 
+/// Get the binding power for infix operators
 fn infix_binding_power(op: Ops) -> Option<(u8, u8)> {
-    let res = match op {
-        Ops::BinaryOp(BinaryOp::Eq) | Ops::BinaryOp(BinaryOp::Ne) => (5, 6),
+    match op {
+        Ops::BinaryOp(BinaryOp::Eq) | Ops::BinaryOp(BinaryOp::Ne) => Some((5, 6)),
         Ops::BinaryOp(BinaryOp::Lt)
         | Ops::BinaryOp(BinaryOp::Le)
         | Ops::BinaryOp(BinaryOp::Gt)
-        | Ops::BinaryOp(BinaryOp::Ge) => (7, 8),
-        Ops::BinaryOp(BinaryOp::Add) | Ops::BinaryOp(BinaryOp::Sub) => (9, 10),
-        Ops::BinaryOp(BinaryOp::Mul) | Ops::BinaryOp(BinaryOp::Div) => (11, 12),
-        Ops::BinaryOp(BinaryOp::At) => (14, 13),
-
-        _ => return None,
-    };
-    Some(res)
+        | Ops::BinaryOp(BinaryOp::Ge) => Some((7, 8)),
+        Ops::BinaryOp(BinaryOp::Add) | Ops::BinaryOp(BinaryOp::Sub) => Some((9, 10)),
+        Ops::BinaryOp(BinaryOp::Mul) | Ops::BinaryOp(BinaryOp::Div) => Some((11, 12)),
+        Ops::BinaryOp(BinaryOp::At) => Some((14, 13)),
+        _ => None,
+    }
 }
 
 ////////////////////////////////////////
@@ -499,6 +637,95 @@ impl fmt::Display for ASTNode {
     }
 }
 
+use std::fmt::Write;
+
+pub fn ast_to_ascii(node: &ASTNode, indent: usize) -> String {
+    let mut result = String::new();
+    let indent_str = "  ".repeat(indent);
+
+    match node {
+        ASTNode::IntNumber(n) => writeln!(result, "{}IntNumber({})", indent_str, n).unwrap(),
+        ASTNode::FloatNumber(n) => writeln!(result, "{}FloatNumber({})", indent_str, n).unwrap(),
+        ASTNode::Identifier(s) => writeln!(result, "{}Identifier({})", indent_str, s).unwrap(),
+        ASTNode::Boolean(b) => writeln!(result, "{}Boolean({})", indent_str, b).unwrap(),
+        ASTNode::String(s) => writeln!(result, "{}String(\"{}\")", indent_str, s).unwrap(),
+        ASTNode::Op(op, args) => {
+            writeln!(result, "{}Op({:?})", indent_str, op).unwrap();
+            for arg in args {
+                result.push_str(&ast_to_ascii(arg, indent + 1));
+            }
+        }
+        ASTNode::Callee(name, args) => {
+            writeln!(result, "{}Callee({})", indent_str, name).unwrap();
+            for arg in args {
+                result.push_str(&ast_to_ascii(arg, indent + 1));
+            }
+        }
+        ASTNode::Let(name, value) => {
+            writeln!(result, "{}Let({})", indent_str, name).unwrap();
+            for v in value {
+                result.push_str(&ast_to_ascii(v, indent + 1));
+            }
+        }
+        ASTNode::Assign(name, value) => {
+            writeln!(result, "{}Assign({})", indent_str, name).unwrap();
+            for v in value {
+                result.push_str(&ast_to_ascii(v, indent + 1));
+            }
+        }
+        ASTNode::If(condition, then_branch, else_branch) => {
+            writeln!(result, "{}If", indent_str).unwrap();
+            writeln!(result, "{}  Condition:", indent_str).unwrap();
+            for cond in condition {
+                result.push_str(&ast_to_ascii(cond, indent + 2));
+            }
+            writeln!(result, "{}  Then:", indent_str).unwrap();
+            for stmt in then_branch {
+                result.push_str(&ast_to_ascii(stmt, indent + 2));
+            }
+            if let Some(else_branch) = else_branch {
+                writeln!(result, "{}  Else:", indent_str).unwrap();
+                for stmt in else_branch {
+                    result.push_str(&ast_to_ascii(stmt, indent + 2));
+                }
+            }
+        }
+        ASTNode::While(condition, body) => {
+            writeln!(result, "{}While", indent_str).unwrap();
+            writeln!(result, "{}  Condition:", indent_str).unwrap();
+            for cond in condition {
+                result.push_str(&ast_to_ascii(cond, indent + 2));
+            }
+            writeln!(result, "{}  Body:", indent_str).unwrap();
+            for stmt in body {
+                result.push_str(&ast_to_ascii(stmt, indent + 2));
+            }
+        }
+        ASTNode::Print(args) => {
+            writeln!(result, "{}Print", indent_str).unwrap();
+            for arg in args {
+                result.push_str(&ast_to_ascii(arg, indent + 1));
+            }
+        }
+        ASTNode::Function(name, params, body) => {
+            writeln!(result, "{}Function({})", indent_str, name).unwrap();
+            writeln!(result, "{}  Parameters: {:?}", indent_str, params).unwrap();
+            writeln!(result, "{}  Body:", indent_str).unwrap();
+            for stmt in body {
+                result.push_str(&ast_to_ascii(stmt, indent + 2));
+            }
+        }
+        ASTNode::Block(statements) => {
+            writeln!(result, "{}Block", indent_str).unwrap();
+            for stmt in statements {
+                result.push_str(&ast_to_ascii(stmt, indent + 1));
+            }
+        }
+    }
+
+    result
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -507,7 +734,7 @@ mod tests {
     fn test_expr() {
         fn expr(source: &str) -> String {
             let mut lexer = Lexer::new(source.to_string());
-            format!("{}", expr_bp(&mut lexer, 0))
+            format!("{}", expr_bp(&mut lexer, 0).unwrap())
         }
 
         let s = expr("1");
@@ -532,7 +759,7 @@ mod tests {
         assert_eq!(s, "(+ (+ 1 2) (* (* (@ f (@ g h)) 3) 4))");
 
         let s = expr("--1 * 2");
-        assert_eq!(s, "(* (- (- 1)) 2)");
+        assert_eq!(s, "(* (- -1) 2)");
 
         let s = expr("--f @ g");
         assert_eq!(s, "(@ (- (- f)) g)");
@@ -556,10 +783,7 @@ mod tests {
         assert_eq!(s, "(. (. x (relu 0 1)) (relu 2 3))");
 
         let s = expr("x.relu(a.b(0+2), 2-1).max(0)/2");
-        assert_eq!(
-            s,
-            "(/ (. (. x (relu (. a (b (+ 0 2))) (- 2 1))) (max 0)) 2)"
-        );
+        assert_eq!(s, "(/ (. (. x (relu (. a (b (+ 0 2))) 2 -1)) (max 0)) 2)");
 
         let s = expr("x.relu(a.sigmoid(0+2))");
         assert_eq!(s, "(. x (relu (. a (sigmoid (+ 0 2)))))");
@@ -568,14 +792,14 @@ mod tests {
         assert_eq!(s, "(== a b)");
 
         let s = expr("--1");
-        assert_eq!(s, "(- (- 1))");
+        assert_eq!(s, "(- -1)");
     }
 
     #[test]
     fn test_parser() {
         fn parse(source: &str) -> String {
             let mut lexer = Lexer::new(source.to_string());
-            let out = Parser::new(&mut lexer).parse();
+            let out = Parser::new(&mut lexer).parse().unwrap();
             assert!(out.len() == 1);
             format!("{}", out[0])
         }
diff --git a/src/chunk.rs b/src/chunk.rs
index ed83eda..fa3b994 100644
--- a/src/chunk.rs
+++ b/src/chunk.rs
@@ -1,8 +1,10 @@
+use serde::{Deserialize, Serialize};
+
 /// This module contains the implementation of the Chunk struct and its methods.
 /// The Chunk struct is used to store the bytecode and the constants.
 use crate::value::ValueType;
 
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
 #[repr(u8)]
 pub enum OpCode {
     OpConstant,
@@ -38,13 +40,13 @@ pub enum OpCode {
     // OpCall,
 }
 
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
 pub enum VectorType {
     Constant(usize),
     Code(OpCode),
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct Chunk {
     /// VectorType is either a index to the constants or an OpCode, see `VectorType` enum
     pub code: Vec,
diff --git a/src/debug.rs b/src/debug.rs
index bb25519..7ba3622 100644
--- a/src/debug.rs
+++ b/src/debug.rs
@@ -5,6 +5,7 @@ pub struct Debug {
     name: String,
     interner: Interner,
     chunk: chunk::Chunk,
+    use_colors: bool,
 }
 
 impl Debug {
@@ -13,124 +14,203 @@ impl Debug {
             name: name.to_string(),
             chunk,
             interner,
+            use_colors: true,
         }
     }
 
-    pub fn disassemble(&self) {
-        println!(
-            "{} {} {}",
-            "======== ".blue().bold(),
-            self.name.blue().bold(),
-            " ========".blue().bold()
-        );
+    pub fn set_color_usage(&mut self, use_colors: bool) {
+        self.use_colors = use_colors;
+    }
+
+    pub fn disassemble(&self) -> String {
+        let mut output = Vec::new();
+        output.push(self.format_header());
 
         let mut offset = 0;
         while offset < self.chunk.code.len() {
-            offset = self.disassemble_instruction(offset);
+            let (new_offset, instruction) = self.disassemble_instruction(offset);
+            output.push(instruction);
+            offset = new_offset;
         }
 
-        println!("{}", "====================".blue().bold());
+        output.push(self.format_footer());
+        output.join("\n")
     }
 
-    pub fn disassemble_instruction(&self, offset: usize) -> usize {
-        let instruction = self.chunk.code[offset];
+    pub fn disassemble_instruction(&self, offset: usize) -> (usize, String) {
+        let instruction = self.chunk.code.get(offset).ok_or_else(|| {
+            format!("Invalid offset {} in chunk of length {}", offset, self.chunk.code.len())
+        }).unwrap();
 
         match instruction {
-            chunk::VectorType::Code(chunk::OpCode::OpReturn)
-            | chunk::VectorType::Code(chunk::OpCode::OpNegate)
-            | chunk::VectorType::Code(chunk::OpCode::OpAdd)
-            | chunk::VectorType::Code(chunk::OpCode::OpSubtract)
-            | chunk::VectorType::Code(chunk::OpCode::OpMultiply)
-            | chunk::VectorType::Code(chunk::OpCode::OpDivide)
-            | chunk::VectorType::Code(chunk::OpCode::OpPower)
-            | chunk::VectorType::Code(chunk::OpCode::OpNil)
-            | chunk::VectorType::Code(chunk::OpCode::OpTrue)
-            | chunk::VectorType::Code(chunk::OpCode::OpFalse)
-            | chunk::VectorType::Code(chunk::OpCode::OpNot)
-            | chunk::VectorType::Code(chunk::OpCode::OpEqualEqual)
-            | chunk::VectorType::Code(chunk::OpCode::OpGreater)
-            | chunk::VectorType::Code(chunk::OpCode::OpLess)
-            | chunk::VectorType::Code(chunk::OpCode::OpPrint)
-            | chunk::VectorType::Code(chunk::OpCode::OpPop) => {
-                println!(
-                    "{:04} {}",
-                    offset.to_string().yellow(),
-                    instruction.to_string().red()
-                );
-
-                return offset + 1;
-            }
-            chunk::VectorType::Code(
-                chunk::OpCode::OpConstant
-                | chunk::OpCode::OpDefineGlobal
-                | chunk::OpCode::OpGetGlobal
-                | chunk::OpCode::OpSetGlobal
-                | chunk::OpCode::OpDefineLocal
-                | chunk::OpCode::OpGetLocal
-                | chunk::OpCode::OpSetLocal
-                // | chunk::OpCode::OpCall,
-            ) => {
-                let constant = self.chunk.code[offset + 1];
-                match constant {
-                    chunk::VectorType::Constant(idx) => match self.chunk.constants[idx] {
-                        ValueType::String(s) | ValueType::Identifier(s) => {
-                            println!(
-                                "{:04} {} {:20} | {}{}",
-                                offset.to_string().yellow(),
-                                instruction.to_string().red(),
-                                constant.to_string().green().italic(),
-                                "intr->".purple().magenta().italic(),
-                                self.interner.lookup(s).purple().magenta().italic()
-                            );
-                        }
-                        _ => {
-                            println!(
-                                "{:04} {} {:20} | {}",
-                                offset.to_string().yellow(),
-                                instruction.to_string().red(),
-                                constant.to_string().green().italic(),
-                                self.chunk.constants[idx]
-                                    .display(&self.interner)
-                                    .purple()
-                                    .magenta()
-                                    .italic()
-                            );
-                        }
-                    },
-                    _ => {
-                        unreachable!();
-                    }
-                }
-                return offset + 2;
-            }
-            chunk::VectorType::Code(chunk::OpCode::OpJump | chunk::OpCode::OpJumpIfFalse | chunk::OpCode::OpLoop) => {
-                let current_location = self.chunk.code[offset + 1];
-                let jump_offset = self.chunk.code[offset + 2];
-
-                if let chunk::VectorType::Constant(loc) = current_location {
-                    if let chunk::VectorType::Constant(jump) = jump_offset {
-                        println!(
-                            "{:04} {} | {}->{}",
-                            offset.to_string().yellow(),
-                            instruction.to_string().red(),
-                            self.chunk.constants[loc]
-                                .display(&self.interner)
-                                .purple()
-                                .magenta()
-                                .italic(),
-                            self.chunk.constants[jump]
-                                .display(&self.interner)
-                                .purple()
-                                .magenta()
-                                .italic(),
-                        );
-                    }
-                }
-                return offset + 2;
-            }
+            chunk::VectorType::Code(op) if op.is_simple() => {
+                (offset + 1, self.format_simple_instruction(offset, op))
+            },
+            chunk::VectorType::Code(op) if op.uses_constant() => {
+                self.format_constant_instruction(offset, op)
+            },
+            chunk::VectorType::Code(op) if op.is_jump() => {
+                self.format_jump_instruction(offset, op)
+            },
             chunk::VectorType::Constant(_) => {
-                return offset + 1;
-            }
+                (offset + 1, "Unexpected constant in code vector".to_string())
+            },
+            _ => (offset + 1, "Unknown instruction type".to_string()),
+        }
+    }
+
+    fn format_simple_instruction(&self, offset: usize, op: &chunk::OpCode) -> String {
+        format!("{} {}", self.colorize_offset(offset), self.colorize_op(op))
+    }
+
+    fn format_constant_instruction(&self, offset: usize, op: &chunk::OpCode) -> (usize, String) {
+        let constant_idx = self.chunk.code.get(offset + 1)
+            .and_then(|v| if let chunk::VectorType::Constant(idx) = v { Some(*idx) } else { None })
+            .ok_or_else(|| "Invalid constant index".to_string())
+            .unwrap();
+
+        let constant = &self.chunk.constants[constant_idx];
+        let constant_str = self.format_constant(constant_idx);
+        
+        (offset + 2, format!("{} {} {} | {}",
+            self.colorize_offset(offset),
+            self.colorize_op(op),
+            self.colorize_constant_idx(constant_idx),
+            self.colorize_constant_str(&constant_str)))
+    }
+
+    fn format_jump_instruction(&self, offset: usize, op: &chunk::OpCode) -> (usize, String) {
+        let current_loc = self.get_constant_value(offset + 1);
+        let jump_offset = self.get_constant_value(offset + 2);
+        
+        (offset + 3, format!("{} {} | {}->{}",
+            self.colorize_offset(offset),
+            self.colorize_op(op),
+            self.colorize_jump_loc(¤t_loc),
+            self.colorize_jump_offset(&jump_offset)))
+    }
+
+    pub fn format_constant(&self, idx: usize) -> String {
+        let constant = &self.chunk.constants[idx];
+        match constant {
+            ValueType::String(s) | ValueType::Identifier(s) => {
+                format!("intr->{}", self.interner.lookup(*s))
+            },
+            _ => constant.display(&self.interner),
+        }
+    }
+
+    fn get_constant_value(&self, offset: usize) -> String {
+        self.chunk.code.get(offset)
+            .and_then(|v| if let chunk::VectorType::Constant(idx) = v {
+                Some(self.chunk.constants[*idx].display(&self.interner))
+            } else { None })
+            .unwrap_or_else(|| "Invalid constant".to_string())
+    }
+
+    fn format_header(&self) -> String {
+        self.colorize_header(&format!("======== {} ========", self.name))
+    }
+
+    fn format_footer(&self) -> String {
+        self.colorize_footer("====================")
+    }
+
+    fn colorize_offset(&self, offset: usize) -> String {
+        if self.use_colors {
+            format!("{:04}", offset).yellow().to_string()
+        } else {
+            format!("{:04}", offset)
+        }
+    }
+
+    fn colorize_op(&self, op: &chunk::OpCode) -> String {
+        if self.use_colors {
+            op.to_string().red().to_string()
+        } else {
+            op.to_string()
+        }
+    }
+
+    fn colorize_constant_idx(&self, idx: usize) -> String {
+        if self.use_colors {
+            format!("{:20}", idx).green().italic().to_string()
+        } else {
+            format!("{:20}", idx)
         }
     }
+
+    fn colorize_constant_str(&self, s: &str) -> String {
+        if self.use_colors {
+            s.purple().magenta().italic().to_string()
+        } else {
+            s.to_string()
+        }
+    }
+
+    fn colorize_jump_loc(&self, loc: &str) -> String {
+        if self.use_colors {
+            loc.purple().magenta().italic().to_string()
+        } else {
+            loc.to_string()
+        }
+    }
+
+    fn colorize_jump_offset(&self, offset: &str) -> String {
+        if self.use_colors {
+            offset.purple().magenta().italic().to_string()
+        } else {
+            offset.to_string()
+        }
+    }
+
+    fn colorize_header(&self, s: &str) -> String {
+        if self.use_colors {
+            s.blue().bold().to_string()
+        } else {
+            s.to_string()
+        }
+    }
+
+    fn colorize_footer(&self, s: &str) -> String {
+        if self.use_colors {
+            s.blue().bold().to_string()
+        } else {
+            s.to_string()
+        }
+    }
+}
+
+trait OpCodeExt {
+    fn is_simple(&self) -> bool;
+    fn uses_constant(&self) -> bool;
+    fn is_jump(&self) -> bool;
 }
+
+impl OpCodeExt for chunk::OpCode {
+    fn is_simple(&self) -> bool {
+        matches!(self, 
+            chunk::OpCode::OpReturn | chunk::OpCode::OpNegate | chunk::OpCode::OpAdd |
+            chunk::OpCode::OpSubtract | chunk::OpCode::OpMultiply | chunk::OpCode::OpDivide |
+            chunk::OpCode::OpPower | chunk::OpCode::OpNil | chunk::OpCode::OpTrue |
+            chunk::OpCode::OpFalse | chunk::OpCode::OpNot | chunk::OpCode::OpEqualEqual |
+            chunk::OpCode::OpGreater | chunk::OpCode::OpLess | chunk::OpCode::OpPrint |
+            chunk::OpCode::OpPop
+        )
+    }
+
+    fn uses_constant(&self) -> bool {
+        matches!(self,
+            chunk::OpCode::OpConstant | chunk::OpCode::OpDefineGlobal |
+            chunk::OpCode::OpGetGlobal | chunk::OpCode::OpSetGlobal |
+            chunk::OpCode::OpDefineLocal | chunk::OpCode::OpGetLocal |
+            chunk::OpCode::OpSetLocal
+        )
+    }
+
+    fn is_jump(&self) -> bool {
+        matches!(self,
+            chunk::OpCode::OpJump | chunk::OpCode::OpJumpIfFalse | chunk::OpCode::OpLoop
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/interner.rs b/src/interner.rs
index 28ee32c..e30f30e 100644
--- a/src/interner.rs
+++ b/src/interner.rs
@@ -3,8 +3,10 @@
 
 use std::collections::HashMap;
 
+use serde::{Deserialize, Serialize};
+
 pub type StringObjIdx = usize;
-#[derive(Default, Debug, Clone)]
+#[derive(Default, Debug, Clone, Serialize, Deserialize)]
 pub struct Interner {
     pub map: HashMap,
     vec: Vec,
diff --git a/src/lib.rs b/src/lib.rs
index f19fea1..07801c5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,44 +1,68 @@
-mod ast;
-mod chunk;
-mod compiler;
-mod debug;
-mod interner;
-mod scanner;
-mod tensor;
-mod value;
-mod vm;
+pub mod ast;
+pub mod chunk;
+pub mod compiler;
+pub mod debug;
+pub mod interner;
+pub mod scanner;
+pub mod tensor;
+pub mod value;
+pub mod vm;
 
+use crate::vm::Result::{CompileErr, Ok, RuntimeErr};
 use crate::{ast::Parser, scanner::Lexer};
+
+use ast::{ast_to_ascii, ASTNode};
 use wasm_bindgen::prelude::*;
 
 
 /// `wasm-pack build -t web`
 #[wasm_bindgen]
-pub fn run_source(src: &str) -> String {
+pub fn run_source(src: &str) -> Vec {
     let mut lexer = Lexer::new(src.to_string());
 
-    let out = Parser::new(&mut lexer).parse();
-    for stmt in out.iter() {
-        println!("{:?}", stmt);
-    }
-    println!("-------------");
+    let out = Parser::new(&mut lexer).parse().unwrap();
+    // for stmt in out.iter() {
+    //     println!("{:?}", stmt);
+    // }
+    // println!("-------------");
 
     let mut compiler = compiler::Compiler::new();
-    let (bytecode, interner) = compiler.compile(out);
-    println!("{:?}", bytecode);
+    let (bytecode, interner) = compiler.compile(out.clone());
+    // println!("{:?}", bytecode);
 
     let debug = debug::Debug::new("test", bytecode.clone(), interner.clone());
-    debug.disassemble();
+    let disassemble_output = debug.disassemble();
 
     let mut vm = vm::VM::init(bytecode, interner);
     let result = vm.run();
 
-    format!("{:?}", result)
-}
+    match result {
+        Ok(v) => {
+            // join string to /n
+            let mut result = String::new();
+            for i in v.iter() {
+                result.push_str(&format!("{:?}\n", i));
+            }
+
+            // AST to ascii
+            let mut ast_output = String::new();
+            for stmt in out.iter() {
+                ast_output.push_str(&ast_to_ascii(stmt, 0));
+            }
 
-#[test]
-fn test_run_source() {
-    let src = "let a = 1 + 2 * 3;print(a);";
-    let result = run_source(src);
-    assert_eq!(result, "Ok([Tensor(7)])");
+            vec![result, disassemble_output, ast_output]
+        }
+        // CompileErr(e) => format!("CompileError({:?})", e),
+        CompileErr(e) => vec![format!("CompileError({:?})", e), disassemble_output],
+        // RuntimeErr(e) => format!("RuntimeError({:?})", e),
+        RuntimeErr(e) => vec![format!("RuntimeError({:?})", e), disassemble_output],
+    }
 }
+
+// #[test]
+// fn test_run_source() {
+//     let src = "let a = 1 + 2 * 3;print(a);";
+//     let result = run_source(src);
+//     assert_eq!(result, "Ok([Tensor(7)])");
+// }
+
diff --git a/src/main.rs b/src/main.rs
index 7a3e005..271a242 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,6 +9,7 @@ mod value;
 mod vm;
 
 use crate::{ast::Parser, scanner::Lexer};
+use ast::ast_to_ascii;
 use clap::Parser as ClapParser;
 use vm::Result;
 
@@ -44,90 +45,43 @@ fn main() {
     }
 }
 
-// fn run_repl() {
-//     loop {
-//         // print prompt
-//         print!("> ");
-//         let _ = std::io::stdout().flush();
-
-//         // read input
-//         let mut input = String::new();
-//         input = match std::io::stdin().read_line(&mut input) {
-//             Ok(_) => input,
-//             Err(e) => panic!("Error reading input: {}", e),
-//         };
-
-//         // exit if input is "exit"
-//         if input.trim() == "exit" || input.trim() == "" {
-//             break;
-//         }
-
-//         // ======================== REPL ========================
-//         let mut lexer = Lexer::new(input.to_string());
-
-//         let out = Parser::new(&mut lexer).parse();
-//         for stmt in out.iter() {
-//             println!("{};", stmt);
-//         }
-
-//         let mut compiler = compiler::Compiler::new();
-//         let (bytecode, interner) = compiler.compile(out);
-
-//         println!("{:?}", bytecode);
-
-//         let debug = debug::Debug::new("test", bytecode.clone());
-//         debug.disassemble();
-
-//         let mut vm = vm::VM::init(bytecode, interner);
-//         let result = vm.run();
-//         // println!("{:?}", result);
-
-//         // ======================== REPL ========================
-//     }
-// }
-
 pub fn run_source(src: &str, debug: bool) -> Result {
     let mut lexer = Lexer::new(src.to_string());
 
-    // print all tokens in single line separated by comma and space
     if debug {
-        println!(
-            "{:?}",
-            lexer
-                .tokens
-                .iter()
-                .map(|t| t.token_type)
-                .rev()
-                .collect::>()
-        );
+        println!("============= Tokens =============");
+        for token in lexer.tokens.iter().rev() {
+            println!("{:?}", token);
+        }
     };
 
-    let out = Parser::new(&mut lexer).parse();
+    let out = Parser::new(&mut lexer).parse().unwrap();
 
     if debug {
+        println!("============= AST =============");
+        let mut ast_output = String::new();
         for stmt in out.iter() {
-            println!("{:?}", stmt);
+            ast_output.push_str(&ast_to_ascii(stmt, 0));
         }
+        println!("{}", ast_output);
     }
 
     let mut compiler = compiler::Compiler::new();
     let (bytecode, interner) = compiler.compile(out);
 
     if debug {
+        println!("============= Bytecode =============");
         println!("{:?}", bytecode);
     }
 
     let debugger = debug::Debug::new("test", bytecode.clone(), interner.clone());
 
     if debug {
-        debugger.disassemble();
+        println!("{}", debugger.disassemble());
     }
 
     let mut vm = vm::VM::init(bytecode, interner);
-
-    // let time = std::time::Instant::now();
     let result = vm.run();
-    // println!("VM Elapsed: {:?}", time.elapsed());
 
     return result;
 }
diff --git a/src/value.rs b/src/value.rs
index 364332f..0da3149 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -1,6 +1,8 @@
+use serde::{Deserialize, Serialize};
+
 use crate::{interner::StringObjIdx, tensor::Tensor};
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub enum ValueType {
     // Tensor(Tensor), // TODO: Ideally, it should be seperate types for int and float (maybe?)
     String(StringObjIdx),
diff --git a/src/vm.rs b/src/vm.rs
index 2cc51d6..289dc03 100644
--- a/src/vm.rs
+++ b/src/vm.rs
@@ -15,7 +15,7 @@ struct CallFrame {
     stack_top: usize,
 }
 
-pub(crate) struct VM {
+pub struct VM {
     pub chunk: Chunk,
 
     // instruction pointer
@@ -37,7 +37,7 @@ pub(crate) struct VM {
 #[derive(Debug, PartialEq, Error)]
 pub enum Result {
     #[error("Ok")]
-    Ok(Vec),
+    Ok(Vec),
 
     #[error("Compile error : {0}")]
     CompileErr(String),
@@ -47,7 +47,7 @@ pub enum Result {
 }
 
 impl VM {
-    pub(crate) fn init(chunk: Chunk, interner: Interner) -> VM {
+    pub fn init(chunk: Chunk, interner: Interner) -> VM {
         // TODO: serialize and cache chunk and interner and save it as a file hash
         VM {
             chunk,
@@ -62,7 +62,7 @@ impl VM {
     }
 
     pub fn run(&mut self) -> Result {
-        let mut print_outputs = Vec::new();
+        let mut print_outputs: Vec = Vec::new();
 
         macro_rules! push {
             ($value:expr) => {
@@ -159,7 +159,7 @@ impl VM {
                 opcode!(OpPrint) => {
                     let value = pop!();
 
-                    print_outputs.push(value.clone());
+                    print_outputs.push(value.display(&self.interner));
                     println!("{}", value.display(&self.interner));
                 }
                 opcode!(OpPop) => {
diff --git a/ui/.cargo/config.toml b/ui/.cargo/config.toml
new file mode 100644
index 0000000..bac83fe
--- /dev/null
+++ b/ui/.cargo/config.toml
@@ -0,0 +1,6 @@
+# clipboard api is still unstable, so web-sys requires the below flag to be passed for copy (ctrl + c) to work
+# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
+# check status at https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility
+# we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93
+[target.wasm32-unknown-unknown]
+rustflags = ["--cfg=web_sys_unstable_apis"]
\ No newline at end of file
diff --git a/ui/.github/workflows/pages.yml b/ui/.github/workflows/pages.yml
new file mode 100644
index 0000000..0765d01
--- /dev/null
+++ b/ui/.github/workflows/pages.yml
@@ -0,0 +1,48 @@
+name: Github Pages
+
+# By default, runs if you push to main. keeps your deployed app in sync with main branch.
+on:
+  push:
+    branches:
+      - main
+# to only run when you do a new github release, comment out above part and uncomment the below trigger.
+# on:
+#   release:
+#     types:
+#       - published
+
+permissions:
+  contents: write # for committing to gh-pages branch.
+
+jobs:
+  build-github-pages:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2 # repo checkout
+      - uses: actions-rs/toolchain@v1 # get rust toolchain for wasm
+        with:
+          profile: minimal
+          toolchain: stable
+          target: wasm32-unknown-unknown
+          override: true
+      - name: Rust Cache # cache the rust build artefacts
+        uses: Swatinem/rust-cache@v1
+      - name: Download and install Trunk binary
+        run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
+      - name: Build # build
+        # Environment $public_url resolves to the github project page.
+        # If using a user/organization page, remove the `${{ github.event.repository.name }}` part.
+        # using --public-url something will allow trunk to modify all the href paths like from favicon.ico to repo_name/favicon.ico .
+        # this is necessary for github pages where the site is deployed to username.github.io/repo_name and all files must be requested
+        # relatively as eframe_template/favicon.ico. if we skip public-url option, the href paths will instead request username.github.io/favicon.ico which
+        # will obviously return error 404 not found.
+        run: ./trunk build --release --public-url $public_url
+        env:
+          public_url: "https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}"
+      - name: Deploy
+        uses: JamesIves/github-pages-deploy-action@v4
+        with:
+          folder: dist
+          # this option will not maintain any history of your previous pages deployment
+          # set to false if you want all page build to be committed to your gh-pages branch history
+          single-commit: true
diff --git a/ui/.github/workflows/rust.yml b/ui/.github/workflows/rust.yml
new file mode 100644
index 0000000..930f464
--- /dev/null
+++ b/ui/.github/workflows/rust.yml
@@ -0,0 +1,167 @@
+on: [push, pull_request, workflow_dispatch]
+
+name: CI
+
+env:
+  # --cfg=web_sys_unstable_apis is required to enable the web_sys clipboard API which egui_web uses
+  # https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html
+  # https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
+  RUSTFLAGS: -D warnings --cfg=web_sys_unstable_apis
+  RUSTDOCFLAGS: -D warnings
+
+jobs:
+  check:
+    name: Check
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: stable
+          override: true
+      - uses: actions-rs/cargo@v1
+        with:
+          command: check
+          args: --all-features
+
+  check_wasm:
+    name: Check wasm32
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: stable
+          target: wasm32-unknown-unknown
+          override: true
+      - uses: actions-rs/cargo@v1
+        with:
+          command: check
+          args: --all-features --lib --target wasm32-unknown-unknown
+
+  test:
+    name: Test Suite
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: stable
+          override: true
+      - run: sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
+      - uses: actions-rs/cargo@v1
+        with:
+          command: test
+          args: --lib
+
+  fmt:
+    name: Rustfmt
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: stable
+          override: true
+          components: rustfmt
+      - uses: actions-rs/cargo@v1
+        with:
+          command: fmt
+          args: --all -- --check
+
+  clippy:
+    name: Clippy
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: stable
+          override: true
+          components: clippy
+      - uses: actions-rs/cargo@v1
+        with:
+          command: clippy
+          args: -- -D warnings
+
+  trunk:
+    name: trunk
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: 1.76.0
+          target: wasm32-unknown-unknown
+          override: true
+      - name: Download and install Trunk binary
+        run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
+      - name: Build
+        run: ./trunk build
+
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+        - os: macos-latest
+          TARGET: aarch64-apple-darwin
+
+        - os: macos-latest
+          TARGET: x86_64-apple-darwin
+
+        - os: ubuntu-latest
+          TARGET: arm-unknown-linux-musleabihf
+
+        - os: ubuntu-latest
+          TARGET: armv7-unknown-linux-musleabihf
+
+        - os: ubuntu-latest
+          TARGET: x86_64-unknown-linux-musl
+
+        - os: windows-latest
+          TARGET: x86_64-pc-windows-msvc
+          EXTENSION: .exe
+
+    steps:
+    - name: Building ${{ matrix.TARGET }}
+      run: echo "${{ matrix.TARGET }}"
+
+    - uses: actions/checkout@master
+    - uses: actions-rs/toolchain@v1.0.1
+      with:
+        toolchain: stable
+        target: ${{ matrix.TARGET }}
+        override: true
+
+    - uses: actions-rs/cargo@v1
+      with:
+        use-cross: true
+        command: build
+        args: --verbose --release --target=${{ matrix.TARGET }}
+
+    - name: Rename
+      run: cp target/${{ matrix.TARGET }}/release/eframe_template${{ matrix.EXTENSION }} eframe_template-${{ matrix.TARGET }}${{ matrix.EXTENSION }}
+
+    - uses: actions/upload-artifact@master
+      with:
+        name: eframe_template-${{ matrix.TARGET }}${{ matrix.EXTENSION }}
+        path: eframe_template-${{ matrix.TARGET }}${{ matrix.EXTENSION }}
+
+    - uses: svenstaro/upload-release-action@v2
+      name: Upload binaries to release
+      if: ${{ github.event_name == 'push' }}
+      with:
+        repo_token: ${{ secrets.GITHUB_TOKEN }}
+        file: eframe_template-${{ matrix.TARGET }}${{ matrix.EXTENSION }}
+        asset_name: eframe_template-${{ matrix.TARGET }}${{ matrix.EXTENSION }}
+        tag: ${{ github.ref }}
+        prerelease: ${{ !startsWith(github.ref, 'refs/tags/') }}
+        overwrite: true
diff --git a/ui/.github/workflows/typos.yml b/ui/.github/workflows/typos.yml
new file mode 100644
index 0000000..3055f87
--- /dev/null
+++ b/ui/.github/workflows/typos.yml
@@ -0,0 +1,19 @@
+# Copied from https://github.com/rerun-io/rerun_template
+
+# https://github.com/crate-ci/typos
+# Add exceptions to `.typos.toml`
+# install and run locally: cargo install typos-cli && typos
+
+name: Spell Check
+on: [pull_request]
+
+jobs:
+  run:
+    name: Spell Check
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout Actions Repository
+        uses: actions/checkout@v4
+
+      - name: Check spelling of entire workspace
+        uses: crate-ci/typos@master
diff --git a/ui/.typos.toml b/ui/.typos.toml
new file mode 100644
index 0000000..0e66a4e
--- /dev/null
+++ b/ui/.typos.toml
@@ -0,0 +1,6 @@
+# https://github.com/crate-ci/typos
+# install:  cargo install typos-cli
+# run:      typos
+
+[default.extend-words]
+egui = "egui" # Example for how to ignore a false positive
diff --git a/ui/Cargo.lock b/ui/Cargo.lock
new file mode 100644
index 0000000..9e8b9d9
--- /dev/null
+++ b/ui/Cargo.lock
@@ -0,0 +1,4174 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ab_glyph"
+version = "0.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80179d7dd5d7e8c285d67c4a1e652972a92de7475beddfb92028c76463b13225"
+dependencies = [
+ "ab_glyph_rasterizer",
+ "owned_ttf_parser",
+]
+
+[[package]]
+name = "ab_glyph_rasterizer"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
+
+[[package]]
+name = "accesskit"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cb10ed32c63247e4e39a8f42e8e30fb9442fbf7878c8e4a9849e7e381619bea"
+dependencies = [
+ "enumn",
+ "serde",
+]
+
+[[package]]
+name = "accesskit_consumer"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c17cca53c09fbd7288667b22a201274b9becaa27f0b91bf52a526db95de45e6"
+dependencies = [
+ "accesskit",
+]
+
+[[package]]
+name = "accesskit_macos"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd3b6ae1eabbfbced10e840fd3fce8a93ae84f174b3e4ba892ab7bcb42e477a7"
+dependencies = [
+ "accesskit",
+ "accesskit_consumer",
+ "objc2 0.3.0-beta.3.patch-leaks.3",
+ "once_cell",
+]
+
+[[package]]
+name = "accesskit_unix"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09f46c18d99ba61ad7123dd13eeb0c104436ab6af1df6a1cd8c11054ed394a08"
+dependencies = [
+ "accesskit",
+ "accesskit_consumer",
+ "async-channel",
+ "async-once-cell",
+ "atspi",
+ "futures-lite 1.13.0",
+ "once_cell",
+ "serde",
+ "zbus",
+]
+
+[[package]]
+name = "accesskit_windows"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afcae27ec0974fc7c3b0b318783be89fd1b2e66dd702179fe600166a38ff4a0b"
+dependencies = [
+ "accesskit",
+ "accesskit_consumer",
+ "once_cell",
+ "paste",
+ "static_assertions",
+ "windows",
+]
+
+[[package]]
+name = "accesskit_winit"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5284218aca17d9e150164428a0ebc7b955f70e3a9a78b4c20894513aabf98a67"
+dependencies = [
+ "accesskit",
+ "accesskit_macos",
+ "accesskit_unix",
+ "accesskit_windows",
+ "winit",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ahash"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
+dependencies = [
+ "cfg-if",
+ "getrandom",
+ "once_cell",
+ "serde",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-activity"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39b801912a977c3fd52d80511fe1c0c8480c6f957f21ae2ce1b92ffe970cf4b9"
+dependencies = [
+ "android-properties",
+ "bitflags 2.4.1",
+ "cc",
+ "cesu8",
+ "jni",
+ "jni-sys",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-context",
+ "ndk-sys",
+ "num_enum",
+ "thiserror",
+]
+
+[[package]]
+name = "android-properties"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "arboard"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aafb29b107435aa276664c1db8954ac27a6e105cdad3c88287a199eb0e313c08"
+dependencies = [
+ "clipboard-win",
+ "log",
+ "objc",
+ "objc-foundation",
+ "objc_id",
+ "parking_lot 0.12.1",
+ "thiserror",
+ "winapi",
+ "x11rb 0.12.0",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
+[[package]]
+name = "as-raw-xcb-connection"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
+
+[[package]]
+name = "async-broadcast"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b"
+dependencies = [
+ "event-listener 2.5.3",
+ "futures-core",
+]
+
+[[package]]
+name = "async-channel"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c"
+dependencies = [
+ "concurrent-queue",
+ "event-listener 4.0.3",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c"
+dependencies = [
+ "async-lock 3.2.0",
+ "async-task",
+ "concurrent-queue",
+ "fastrand 2.0.1",
+ "futures-lite 2.2.0",
+ "slab",
+]
+
+[[package]]
+name = "async-fs"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
+dependencies = [
+ "async-lock 2.8.0",
+ "autocfg",
+ "blocking",
+ "futures-lite 1.13.0",
+]
+
+[[package]]
+name = "async-io"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
+dependencies = [
+ "async-lock 2.8.0",
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-lite 1.13.0",
+ "log",
+ "parking",
+ "polling 2.8.0",
+ "rustix 0.37.27",
+ "slab",
+ "socket2",
+ "waker-fn",
+]
+
+[[package]]
+name = "async-io"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7"
+dependencies = [
+ "async-lock 3.2.0",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite 2.2.0",
+ "parking",
+ "polling 3.3.1",
+ "rustix 0.38.28",
+ "slab",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
+dependencies = [
+ "event-listener 2.5.3",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c"
+dependencies = [
+ "event-listener 4.0.3",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-once-cell"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9338790e78aa95a416786ec8389546c4b6a1dfc3dc36071ed9518a9413a542eb"
+
+[[package]]
+name = "async-process"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88"
+dependencies = [
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
+ "async-signal",
+ "blocking",
+ "cfg-if",
+ "event-listener 3.1.0",
+ "futures-lite 1.13.0",
+ "rustix 0.38.28",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "async-recursion"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5"
+dependencies = [
+ "async-io 2.2.2",
+ "async-lock 2.8.0",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix 0.38.28",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
+
+[[package]]
+name = "async-trait"
+version = "0.1.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "atspi"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6059f350ab6f593ea00727b334265c4dfc7fd442ee32d264794bd9bdc68e87ca"
+dependencies = [
+ "atspi-common",
+ "atspi-connection",
+ "atspi-proxies",
+]
+
+[[package]]
+name = "atspi-common"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92af95f966d2431f962bc632c2e68eda7777330158bf640c4af4249349b2cdf5"
+dependencies = [
+ "enumflags2",
+ "serde",
+ "static_assertions",
+ "zbus",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "atspi-connection"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c65e7d70f86d4c0e3b2d585d9bf3f979f0b19d635a336725a88d279f76b939"
+dependencies = [
+ "atspi-common",
+ "atspi-proxies",
+ "futures-lite 1.13.0",
+ "zbus",
+]
+
+[[package]]
+name = "atspi-proxies"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52"
+dependencies = [
+ "atspi-common",
+ "serde",
+ "zbus",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.21.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
+
+[[package]]
+name = "beef"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block-sys"
+version = "0.1.0-beta.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146"
+dependencies = [
+ "objc-sys 0.2.0-beta.2",
+]
+
+[[package]]
+name = "block-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7"
+dependencies = [
+ "objc-sys 0.3.2",
+]
+
+[[package]]
+name = "block2"
+version = "0.2.0-alpha.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42"
+dependencies = [
+ "block-sys 0.1.0-beta.1",
+ "objc2-encode 2.0.0-pre.2",
+]
+
+[[package]]
+name = "block2"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68"
+dependencies = [
+ "block-sys 0.2.1",
+ "objc2 0.4.1",
+]
+
+[[package]]
+name = "blocking"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
+dependencies = [
+ "async-channel",
+ "async-lock 3.2.0",
+ "async-task",
+ "fastrand 2.0.1",
+ "futures-io",
+ "futures-lite 2.2.0",
+ "piper",
+ "tracing",
+]
+
+[[package]]
+name = "bstr"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "bytemuck"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+
+[[package]]
+name = "calloop"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b50b5a44d59a98c55a9eeb518f39bf7499ba19fd98ee7d22618687f3f10adbf"
+dependencies = [
+ "bitflags 2.4.1",
+ "log",
+ "polling 3.3.1",
+ "rustix 0.38.28",
+ "slab",
+ "thiserror",
+]
+
+[[package]]
+name = "calloop-wayland-source"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02"
+dependencies = [
+ "calloop",
+ "rustix 0.38.28",
+ "wayland-backend",
+ "wayland-client",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "jobserver",
+ "libc",
+]
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+
+[[package]]
+name = "cgl"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "chrono-tz"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
+dependencies = [
+ "chrono",
+ "chrono-tz-build",
+ "phf",
+]
+
+[[package]]
+name = "chrono-tz-build"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
+dependencies = [
+ "parse-zoneinfo",
+ "phf",
+ "phf_codegen",
+]
+
+[[package]]
+name = "clap"
+version = "2.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags 1.3.2",
+ "strsim 0.8.0",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim 0.11.1",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
+
+[[package]]
+name = "clipboard-win"
+version = "4.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
+dependencies = [
+ "error-code",
+ "str-buf",
+ "winapi",
+]
+
+[[package]]
+name = "cocoa"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c"
+dependencies = [
+ "bitflags 1.3.2",
+ "block",
+ "cocoa-foundation",
+ "core-foundation",
+ "core-graphics",
+ "foreign-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "cocoa-foundation"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
+dependencies = [
+ "bitflags 1.3.2",
+ "block",
+ "core-foundation",
+ "core-graphics-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
+
+[[package]]
+name = "colored"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
+dependencies = [
+ "lazy_static",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "core-graphics"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "cursor-icon"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
+
+[[package]]
+name = "dashmap"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
+dependencies = [
+ "cfg-if",
+ "num_cpus",
+ "serde",
+]
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "deunicode"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00"
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "directories-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "dispatch"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
+
+[[package]]
+name = "dlib"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
+dependencies = [
+ "libloading",
+]
+
+[[package]]
+name = "document-features"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
+
+[[package]]
+name = "ecolor"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c416b34b29e8f6b2492b5c80319cf9792d339550f0d75a54b53ed070045bbdeb"
+dependencies = [
+ "bytemuck",
+ "serde",
+]
+
+[[package]]
+name = "eframe"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86811b20df847cfa73637020ee78de780407110f49785df2dea6741146ea5aac"
+dependencies = [
+ "bytemuck",
+ "cocoa",
+ "directories-next",
+ "document-features",
+ "egui",
+ "egui-winit",
+ "egui_glow",
+ "glow",
+ "glutin",
+ "glutin-winit",
+ "image",
+ "js-sys",
+ "log",
+ "objc",
+ "parking_lot 0.12.1",
+ "percent-encoding",
+ "raw-window-handle 0.5.2",
+ "raw-window-handle 0.6.0",
+ "ron",
+ "serde",
+ "static_assertions",
+ "thiserror",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "web-time",
+ "winapi",
+ "winit",
+]
+
+[[package]]
+name = "egui"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8e20541e5d3da08cf817eb5c8a2bc4024f21aad7acf98de201442d6d76e0ecc"
+dependencies = [
+ "accesskit",
+ "ahash",
+ "epaint",
+ "log",
+ "nohash-hasher",
+ "ron",
+ "serde",
+]
+
+[[package]]
+name = "egui-winit"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff4fc01bc3617dba9280475e9ab2382015e298f401d2318c2a1d78c419b6dffe"
+dependencies = [
+ "accesskit_winit",
+ "arboard",
+ "egui",
+ "log",
+ "raw-window-handle 0.6.0",
+ "serde",
+ "smithay-clipboard",
+ "web-time",
+ "webbrowser",
+ "winit",
+]
+
+[[package]]
+name = "egui_glow"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743380a1c0f1dbb7bfe29663ce62b10785adda51105c9bb4e544e2f9955b4958"
+dependencies = [
+ "bytemuck",
+ "egui",
+ "glow",
+ "log",
+ "memoffset 0.9.0",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "either"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
+
+[[package]]
+name = "emath"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e87799f56edee11422267e1764dd0bf3fe1734888e8d2d0924a67b85f4998fbe"
+dependencies = [
+ "bytemuck",
+ "serde",
+]
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "encoding_rs_io"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83"
+dependencies = [
+ "encoding_rs",
+]
+
+[[package]]
+name = "enumflags2"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5998b4f30320c9d93aed72f63af821bfdac50465b75428fce77b48ec482c3939"
+dependencies = [
+ "enumflags2_derive",
+ "serde",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "enumn"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
+dependencies = [
+ "humantime",
+ "is-terminal",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "epaint"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "392eccd6d6b13fadccc268f4f61d9363775ec5711ffdece2a79006d676920bcf"
+dependencies = [
+ "ab_glyph",
+ "ahash",
+ "bytemuck",
+ "ecolor",
+ "emath",
+ "log",
+ "nohash-hasher",
+ "parking_lot 0.12.1",
+ "serde",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "error-code"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
+dependencies = [
+ "libc",
+ "str-buf",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "event-listener"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener"
+version = "4.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
+dependencies = [
+ "event-listener 4.0.3",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "fdeflate"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "209098dd6dfc4445aa6111f0e98653ac323eaa4dfd212c9ca3931bf9955c31bd"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-lite"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
+dependencies = [
+ "fastrand 1.9.0",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
+[[package]]
+name = "futures-lite"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba"
+dependencies = [
+ "fastrand 2.0.1",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "gethostname"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "gethostname"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
+dependencies = [
+ "libc",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gl_generator"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
+dependencies = [
+ "khronos_api",
+ "log",
+ "xml-rs",
+]
+
+[[package]]
+name = "globset"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
+dependencies = [
+ "aho-corasick 1.1.2",
+ "bstr",
+ "log",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "globwalk"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
+dependencies = [
+ "bitflags 2.4.1",
+ "ignore",
+ "walkdir",
+]
+
+[[package]]
+name = "glow"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "886c2a30b160c4c6fec8f987430c26b526b7988ca71f664e6a699ddf6f9601e4"
+dependencies = [
+ "js-sys",
+ "slotmap",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "glutin"
+version = "0.31.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "005459a22af86adc706522d78d360101118e2638ec21df3852fcc626e0dbb212"
+dependencies = [
+ "bitflags 2.4.1",
+ "cfg_aliases",
+ "cgl",
+ "core-foundation",
+ "dispatch",
+ "glutin_egl_sys",
+ "glutin_glx_sys",
+ "glutin_wgl_sys",
+ "icrate",
+ "libloading",
+ "objc2 0.4.1",
+ "once_cell",
+ "raw-window-handle 0.5.2",
+ "wayland-sys",
+ "windows-sys 0.48.0",
+ "x11-dl",
+]
+
+[[package]]
+name = "glutin-winit"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735"
+dependencies = [
+ "cfg_aliases",
+ "glutin",
+ "raw-window-handle 0.5.2",
+ "winit",
+]
+
+[[package]]
+name = "glutin_egl_sys"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77cc5623f5309ef433c3dd4ca1223195347fe62c413da8e2fdd0eb76db2d9bcd"
+dependencies = [
+ "gl_generator",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "glutin_glx_sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a165fd686c10dcc2d45380b35796e577eacfd43d4660ee741ec8ebe2201b3b4f"
+dependencies = [
+ "gl_generator",
+ "x11-dl",
+]
+
+[[package]]
+name = "glutin_wgl_sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead"
+dependencies = [
+ "gl_generator",
+]
+
+[[package]]
+name = "grad"
+version = "0.1.0"
+dependencies = [
+ "clap 4.5.7",
+ "colored",
+ "logos",
+ "serde",
+ "thiserror",
+ "tokei",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "grep-matcher"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "grep-searcher"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54"
+dependencies = [
+ "bstr",
+ "encoding_rs",
+ "encoding_rs_io",
+ "grep-matcher",
+ "log",
+ "memchr",
+ "memmap2",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "humansize"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
+dependencies = [
+ "libm",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "icrate"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319"
+dependencies = [
+ "block2 0.3.0",
+ "dispatch",
+ "objc2 0.4.1",
+]
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "ignore"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
+dependencies = [
+ "crossbeam-deque",
+ "globset",
+ "log",
+ "memchr",
+ "regex-automata",
+ "same-file",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "image"
+version = "0.24.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "num-rational",
+ "num-traits",
+ "png",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi 0.3.3",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
+dependencies = [
+ "hermit-abi 0.3.3",
+ "rustix 0.38.28",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "jobserver"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "khronos_api"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+
+[[package]]
+name = "libloading"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "libm"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+
+[[package]]
+name = "libredox"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
+dependencies = [
+ "bitflags 2.4.1",
+ "libc",
+ "redox_syscall 0.4.1",
+]
+
+[[package]]
+name = "libredox"
+version = "0.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607"
+dependencies = [
+ "bitflags 2.4.1",
+ "libc",
+ "redox_syscall 0.4.1",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
+
+[[package]]
+name = "litrs"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "logos"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "161971eb88a0da7ae0c333e1063467c5b5727e7fb6b710b8db4814eade3a42e8"
+dependencies = [
+ "logos-derive",
+]
+
+[[package]]
+name = "logos-codegen"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e31badd9de5131fdf4921f6473d457e3dd85b11b7f091ceb50e4df7c3eeb12a"
+dependencies = [
+ "beef",
+ "fnv",
+ "lazy_static",
+ "proc-macro2",
+ "quote",
+ "regex-syntax",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "logos-derive"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c2a69b3eb68d5bd595107c9ee58d7e07fe2bb5e360cc85b0f084dedac80de0a"
+dependencies = [
+ "logos-codegen",
+]
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "memmap2"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+ "simd-adler32",
+]
+
+[[package]]
+name = "ndk"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
+dependencies = [
+ "bitflags 2.4.1",
+ "jni-sys",
+ "log",
+ "ndk-sys",
+ "num_enum",
+ "raw-window-handle 0.5.2",
+ "raw-window-handle 0.6.0",
+ "thiserror",
+]
+
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
+[[package]]
+name = "ndk-sys"
+version = "0.5.0+25.2.9519653"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
+dependencies = [
+ "jni-sys",
+]
+
+[[package]]
+name = "nix"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+ "memoffset 0.7.1",
+]
+
+[[package]]
+name = "nohash-hasher"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
+
+[[package]]
+name = "num-format"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
+dependencies = [
+ "arrayvec",
+ "itoa",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi 0.3.3",
+ "libc",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
+dependencies = [
+ "proc-macro-crate 3.0.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
+
+[[package]]
+name = "objc-foundation"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
+dependencies = [
+ "block",
+ "objc",
+ "objc_id",
+]
+
+[[package]]
+name = "objc-sys"
+version = "0.2.0-beta.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7"
+
+[[package]]
+name = "objc-sys"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459"
+
+[[package]]
+name = "objc2"
+version = "0.3.0-beta.3.patch-leaks.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468"
+dependencies = [
+ "block2 0.2.0-alpha.6",
+ "objc-sys 0.2.0-beta.2",
+ "objc2-encode 2.0.0-pre.2",
+]
+
+[[package]]
+name = "objc2"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d"
+dependencies = [
+ "objc-sys 0.3.2",
+ "objc2-encode 3.0.0",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "2.0.0-pre.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512"
+dependencies = [
+ "objc-sys 0.2.0-beta.2",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666"
+
+[[package]]
+name = "objc_id"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
+dependencies = [
+ "objc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "orbclient"
+version = "0.3.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166"
+dependencies = [
+ "libredox 0.0.2",
+]
+
+[[package]]
+name = "ordered-stream"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "owned_ttf_parser"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7"
+dependencies = [
+ "ttf-parser",
+]
+
+[[package]]
+name = "parking"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core 0.8.6",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core 0.9.9",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall 0.2.16",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.4.1",
+ "smallvec",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "parse-zoneinfo"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
+dependencies = [
+ "regex",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pest"
+version = "2.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8"
+dependencies = [
+ "memchr",
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd"
+dependencies = [
+ "once_cell",
+ "pest",
+ "sha2",
+]
+
+[[package]]
+name = "phf"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "piper"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
+dependencies = [
+ "atomic-waker",
+ "fastrand 2.0.1",
+ "futures-io",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
+
+[[package]]
+name = "png"
+version = "0.17.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "polling"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
+dependencies = [
+ "autocfg",
+ "bitflags 1.3.2",
+ "cfg-if",
+ "concurrent-queue",
+ "libc",
+ "log",
+ "pin-project-lite",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "polling"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "pin-project-lite",
+ "rustix 0.38.28",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
+dependencies = [
+ "once_cell",
+ "toml_edit 0.19.15",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b2685dd208a3771337d8d386a89840f0f43cd68be8dae90a5f8c2384effc9cd"
+dependencies = [
+ "toml_edit 0.21.0",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "raw-window-handle"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
+
+[[package]]
+name = "raw-window-handle"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544"
+
+[[package]]
+name = "rayon"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
+dependencies = [
+ "getrandom",
+ "libredox 0.0.1",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+dependencies = [
+ "aho-corasick 1.1.2",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+dependencies = [
+ "aho-corasick 1.1.2",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "ron"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
+dependencies = [
+ "base64",
+ "bitflags 2.4.1",
+ "serde",
+ "serde_derive",
+]
+
+[[package]]
+name = "rustix"
+version = "0.37.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
+dependencies = [
+ "bitflags 1.3.2",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys 0.3.8",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
+dependencies = [
+ "bitflags 2.4.1",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.12",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.203"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.203"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+
+[[package]]
+name = "siphasher"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "slotmap"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "slug"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4"
+dependencies = [
+ "deunicode",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+
+[[package]]
+name = "smithay-client-toolkit"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60e3d9941fa3bacf7c2bf4b065304faa14164151254cd16ce1b1bc8fc381600f"
+dependencies = [
+ "bitflags 2.4.1",
+ "calloop",
+ "calloop-wayland-source",
+ "cursor-icon",
+ "libc",
+ "log",
+ "memmap2",
+ "rustix 0.38.28",
+ "thiserror",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-csd-frame",
+ "wayland-cursor",
+ "wayland-protocols",
+ "wayland-protocols-wlr",
+ "wayland-scanner",
+ "xkeysym",
+]
+
+[[package]]
+name = "smithay-clipboard"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bb62b280ce5a5cba847669933a0948d00904cf83845c944eae96a4738cea1a6"
+dependencies = [
+ "libc",
+ "smithay-client-toolkit",
+ "wayland-backend",
+]
+
+[[package]]
+name = "smol_str"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "socket2"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "str-buf"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
+dependencies = [
+ "cfg-if",
+ "fastrand 2.0.1",
+ "redox_syscall 0.4.1",
+ "rustix 0.38.28",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tera"
+version = "1.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee"
+dependencies = [
+ "chrono",
+ "chrono-tz",
+ "globwalk",
+ "humansize",
+ "lazy_static",
+ "percent-encoding",
+ "pest",
+ "pest_derive",
+ "rand",
+ "regex",
+ "serde",
+ "serde_json",
+ "slug",
+ "unic-segment",
+]
+
+[[package]]
+name = "term_size"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokei"
+version = "12.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a41f915e075a8a98ad64a5f7be6b7cc1710fc835c5f07e4a3efcaeb013291c00"
+dependencies = [
+ "aho-corasick 0.7.20",
+ "clap 2.34.0",
+ "crossbeam-channel",
+ "dashmap",
+ "dirs",
+ "encoding_rs_io",
+ "env_logger 0.8.4",
+ "grep-searcher",
+ "ignore",
+ "log",
+ "num-format",
+ "once_cell",
+ "parking_lot 0.11.2",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_json",
+ "tera",
+ "term_size",
+ "toml",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "ttf-parser"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
+
+[[package]]
+name = "uds_windows"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
+dependencies = [
+ "memoffset 0.9.0",
+ "tempfile",
+ "winapi",
+]
+
+[[package]]
+name = "ui"
+version = "0.1.0"
+dependencies = [
+ "colored",
+ "eframe",
+ "egui",
+ "env_logger 0.10.1",
+ "grad",
+ "log",
+ "serde",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
+dependencies = [
+ "unic-ucd-segment",
+]
+
+[[package]]
+name = "unic-ucd-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
+
+[[package]]
+name = "url"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "waker-fn"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
+
+[[package]]
+name = "walkdir"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "wayland-backend"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4"
+dependencies = [
+ "cc",
+ "downcast-rs",
+ "nix",
+ "scoped-tls",
+ "smallvec",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-client"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3"
+dependencies = [
+ "bitflags 2.4.1",
+ "nix",
+ "wayland-backend",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-csd-frame"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
+dependencies = [
+ "bitflags 2.4.1",
+ "cursor-icon",
+ "wayland-backend",
+]
+
+[[package]]
+name = "wayland-cursor"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44aa20ae986659d6c77d64d808a046996a932aa763913864dc40c359ef7ad5b"
+dependencies = [
+ "nix",
+ "wayland-client",
+ "xcursor",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c"
+dependencies = [
+ "bitflags 2.4.1",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-plasma"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479"
+dependencies = [
+ "bitflags 2.4.1",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-wlr"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6"
+dependencies = [
+ "bitflags 2.4.1",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c"
+dependencies = [
+ "proc-macro2",
+ "quick-xml",
+ "quote",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af"
+dependencies = [
+ "dlib",
+ "log",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webbrowser"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82b2391658b02c27719fc5a0a73d6e696285138e8b12fba9d4baa70451023c71"
+dependencies = [
+ "core-foundation",
+ "home",
+ "jni",
+ "log",
+ "ndk-context",
+ "objc",
+ "raw-window-handle 0.5.2",
+ "url",
+ "web-sys",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-wsapoll"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
+[[package]]
+name = "winit"
+version = "0.29.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2376dab13e09c01ad8b679f0dbc7038af4ec43d9a91344338e37bd686481550"
+dependencies = [
+ "ahash",
+ "android-activity",
+ "atomic-waker",
+ "bitflags 2.4.1",
+ "bytemuck",
+ "calloop",
+ "cfg_aliases",
+ "core-foundation",
+ "core-graphics",
+ "cursor-icon",
+ "icrate",
+ "js-sys",
+ "libc",
+ "log",
+ "memmap2",
+ "ndk",
+ "ndk-sys",
+ "objc2 0.4.1",
+ "once_cell",
+ "orbclient",
+ "percent-encoding",
+ "raw-window-handle 0.5.2",
+ "raw-window-handle 0.6.0",
+ "redox_syscall 0.3.5",
+ "rustix 0.38.28",
+ "smithay-client-toolkit",
+ "smol_str",
+ "unicode-segmentation",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-protocols-plasma",
+ "web-sys",
+ "web-time",
+ "windows-sys 0.48.0",
+ "x11-dl",
+ "x11rb 0.13.0",
+ "xkbcommon-dl",
+]
+
+[[package]]
+name = "winnow"
+version = "0.5.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7520bbdec7211caa7c4e682eb1fbe07abe20cee6756b6e00f537c82c11816aa"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "x11-dl"
+version = "2.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
+dependencies = [
+ "libc",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "x11rb"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a"
+dependencies = [
+ "gethostname 0.3.0",
+ "nix",
+ "winapi",
+ "winapi-wsapoll",
+ "x11rb-protocol 0.12.0",
+]
+
+[[package]]
+name = "x11rb"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a"
+dependencies = [
+ "as-raw-xcb-connection",
+ "gethostname 0.4.3",
+ "libc",
+ "libloading",
+ "once_cell",
+ "rustix 0.38.28",
+ "x11rb-protocol 0.13.0",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc"
+dependencies = [
+ "nix",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34"
+
+[[package]]
+name = "xcursor"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911"
+
+[[package]]
+name = "xdg-home"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd"
+dependencies = [
+ "nix",
+ "winapi",
+]
+
+[[package]]
+name = "xkbcommon-dl"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6924668544c48c0133152e7eec86d644a056ca3d09275eb8d5cdb9855f9d8699"
+dependencies = [
+ "bitflags 2.4.1",
+ "dlib",
+ "log",
+ "once_cell",
+ "xkeysym",
+]
+
+[[package]]
+name = "xkeysym"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
+
+[[package]]
+name = "zbus"
+version = "3.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948"
+dependencies = [
+ "async-broadcast",
+ "async-executor",
+ "async-fs",
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
+ "async-process",
+ "async-recursion",
+ "async-task",
+ "async-trait",
+ "blocking",
+ "byteorder",
+ "derivative",
+ "enumflags2",
+ "event-listener 2.5.3",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "hex",
+ "nix",
+ "once_cell",
+ "ordered-stream",
+ "rand",
+ "serde",
+ "serde_repr",
+ "sha1",
+ "static_assertions",
+ "tracing",
+ "uds_windows",
+ "winapi",
+ "xdg-home",
+ "zbus_macros",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "3.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d"
+dependencies = [
+ "proc-macro-crate 1.3.1",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn 1.0.109",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zbus_names"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9"
+dependencies = [
+ "serde",
+ "static_assertions",
+ "zvariant",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "zvariant"
+version = "3.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c"
+dependencies = [
+ "byteorder",
+ "enumflags2",
+ "libc",
+ "serde",
+ "static_assertions",
+ "zvariant_derive",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "3.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd"
+dependencies = [
+ "proc-macro-crate 1.3.1",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_utils"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
diff --git a/ui/Cargo.toml b/ui/Cargo.toml
new file mode 100644
index 0000000..409b858
--- /dev/null
+++ b/ui/Cargo.toml
@@ -0,0 +1,53 @@
+[package]
+name = "ui"
+version = "0.1.0"
+authors = ["Shubhamai"]
+edition = "2021"
+include = ["LICENSE-APACHE", "LICENSE-MIT", "**/*.rs", "Cargo.toml"]
+rust-version = "1.76"
+
+[package.metadata.docs.rs]
+all-features = true
+targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
+
+[dependencies]
+egui = "0.27.0"
+eframe = { version = "0.27.0", default-features = false, features = [
+    "accesskit",     # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies.
+    "default_fonts", # Embed the default egui fonts.
+    "glow",          # Use the glow rendering backend. Alternative: "wgpu".
+    "persistence",   # Enable restoring app state when restarting the app.
+] }
+log = "0.4"
+grad = { path = ".." }
+
+# You only need serde if you want app persistence:
+serde = { version = "1", features = ["derive"] }
+colored = "2.1.0"
+
+# native:
+[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+env_logger = "0.10"
+
+# web:
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+wasm-bindgen-futures = "0.4"
+
+
+[profile.release]
+opt-level = 2 # fast and small wasm
+
+# Optimize all dependencies even in debug builds:
+[profile.dev.package."*"]
+opt-level = 2
+
+
+[patch.crates-io]
+
+# If you want to use the bleeding edge version of egui and eframe:
+# egui = { git = "https://github.com/emilk/egui", branch = "master" }
+# eframe = { git = "https://github.com/emilk/egui", branch = "master" }
+
+# If you fork https://github.com/emilk/egui you can test with:
+# egui = { path = "../egui/crates/egui" }
+# eframe = { path = "../egui/crates/eframe" }
diff --git a/ui/LICENSE-APACHE b/ui/LICENSE-APACHE
new file mode 100644
index 0000000..11069ed
--- /dev/null
+++ b/ui/LICENSE-APACHE
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/ui/LICENSE-MIT b/ui/LICENSE-MIT
new file mode 100644
index 0000000..31aa793
--- /dev/null
+++ b/ui/LICENSE-MIT
@@ -0,0 +1,23 @@
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/ui/README.md b/ui/README.md
new file mode 100644
index 0000000..d9ea195
--- /dev/null
+++ b/ui/README.md
@@ -0,0 +1,84 @@
+# eframe template
+
+[![dependency status](https://deps.rs/repo/github/emilk/eframe_template/status.svg)](https://deps.rs/repo/github/emilk/eframe_template)
+[![Build Status](https://github.com/emilk/eframe_template/workflows/CI/badge.svg)](https://github.com/emilk/eframe_template/actions?workflow=CI)
+
+This is a template repo for [eframe](https://github.com/emilk/egui/tree/master/crates/eframe), a framework for writing apps using [egui](https://github.com/emilk/egui/).
+
+The goal is for this to be the simplest way to get started writing a GUI app in Rust.
+
+You can compile your app natively or for the web, and share it using Github Pages.
+
+## Getting started
+
+Start by clicking "Use this template" at https://github.com/emilk/eframe_template/ or follow [these instructions](https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template).
+
+Change the name of the crate: Chose a good name for your project, and change the name to it in:
+* `Cargo.toml`
+    * Change the `package.name` from `eframe_template` to `your_crate`.
+    * Change the `package.authors`
+* `main.rs`
+    * Change `eframe_template::TemplateApp` to `your_crate::TemplateApp`
+* `index.html`
+    * Change the `eframe template` to `your_crate`. optional.
+* `assets/sw.js`
+  * Change the `'./eframe_template.js'` to `./your_crate.js` (in `filesToCache` array)
+  * Change the `'./eframe_template_bg.wasm'` to `./your_crate_bg.wasm` (in `filesToCache` array)
+
+### Learning about egui
+
+`src/app.rs` contains a simple example app. This is just to give some inspiration - most of it can be removed if you like.
+
+The official egui docs are at . If you prefer watching a video introduction, check out . For inspiration, check out the [the egui web demo](https://emilk.github.io/egui/index.html) and follow the links in it to its source code.
+
+### Testing locally
+
+Make sure you are using the latest version of stable rust by running `rustup update`.
+
+`cargo run --release`
+
+On Linux you need to first run:
+
+`sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev`
+
+On Fedora Rawhide you need to run:
+
+`dnf install clang clang-devel clang-tools-extra libxkbcommon-devel pkg-config openssl-devel libxcb-devel gtk3-devel atk fontconfig-devel`
+
+### Web Locally
+
+You can compile your app to [WASM](https://en.wikipedia.org/wiki/WebAssembly) and publish it as a web page.
+
+We use [Trunk](https://trunkrs.dev/) to build for web target.
+1. Install the required target with `rustup target add wasm32-unknown-unknown`.
+2. Install Trunk with `cargo install --locked trunk`.
+3. Run `trunk serve` to build and serve on `http://127.0.0.1:8080`. Trunk will rebuild automatically if you edit the project.
+4. Open `http://127.0.0.1:8080/index.html#dev` in a browser. See the warning below.
+
+> `assets/sw.js` script will try to cache our app, and loads the cached version when it cannot connect to server allowing your app to work offline (like PWA).
+> appending `#dev` to `index.html` will skip this caching, allowing us to load the latest builds during development.
+
+### Web Deploy
+1. Just run `trunk build --release`.
+2. It will generate a `dist` directory as a "static html" website
+3. Upload the `dist` directory to any of the numerous free hosting websites including [GitHub Pages](https://docs.github.com/en/free-pro-team@latest/github/working-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site).
+4. we already provide a workflow that auto-deploys our app to GitHub pages if you enable it.
+> To enable Github Pages, you need to go to Repository -> Settings -> Pages -> Source -> set to `gh-pages` branch and `/` (root).
+>
+> If `gh-pages` is not available in `Source`, just create and push a branch called `gh-pages` and it should be available.
+>
+> If you renamed the `main` branch to something else (say you re-initialized the repository with `master` as the initial branch), be sure to edit the github workflows `.github/workflows/pages.yml` file to reflect the change
+> ```yml
+> on:
+>   push:
+>     branches:
+>       - 
+> ```
+
+You can test the template app at .
+
+## Updating egui
+
+As of 2023, egui is in active development with frequent releases with breaking changes. [eframe_template](https://github.com/emilk/eframe_template/) will be updated in lock-step to always use the latest version of egui.
+
+When updating `egui` and `eframe` it is recommended you do so one version at the time, and read about the changes in [the egui changelog](https://github.com/emilk/egui/blob/master/CHANGELOG.md) and [eframe changelog](https://github.com/emilk/egui/blob/master/crates/eframe/CHANGELOG.md).
diff --git a/ui/Trunk.toml b/ui/Trunk.toml
new file mode 100644
index 0000000..a7b19c9
--- /dev/null
+++ b/ui/Trunk.toml
@@ -0,0 +1 @@
+[build]
diff --git a/ui/assets/SpaceMono-Regular.ttf b/ui/assets/SpaceMono-Regular.ttf
new file mode 100644
index 0000000..04e56b9
Binary files /dev/null and b/ui/assets/SpaceMono-Regular.ttf differ
diff --git a/ui/assets/favicon.ico b/ui/assets/favicon.ico
new file mode 100755
index 0000000..61ad031
Binary files /dev/null and b/ui/assets/favicon.ico differ
diff --git a/ui/assets/icon-1024.png b/ui/assets/icon-1024.png
new file mode 100644
index 0000000..1b5868a
Binary files /dev/null and b/ui/assets/icon-1024.png differ
diff --git a/ui/assets/icon-256.png b/ui/assets/icon-256.png
new file mode 100644
index 0000000..ae72287
Binary files /dev/null and b/ui/assets/icon-256.png differ
diff --git a/ui/assets/icon_ios_touch_192.png b/ui/assets/icon_ios_touch_192.png
new file mode 100644
index 0000000..8472802
Binary files /dev/null and b/ui/assets/icon_ios_touch_192.png differ
diff --git a/ui/assets/manifest.json b/ui/assets/manifest.json
new file mode 100644
index 0000000..2a137fb
--- /dev/null
+++ b/ui/assets/manifest.json
@@ -0,0 +1,28 @@
+{
+  "name": "egui Template PWA",
+  "short_name": "egui-template-pwa",
+  "icons": [
+    {
+      "src": "./icon-256.png",
+      "sizes": "256x256",
+      "type": "image/png"
+    },
+    {
+      "src": "./maskable_icon_x512.png",
+      "sizes": "512x512",
+      "type": "image/png",
+      "purpose": "any maskable"
+    },
+    {
+      "src": "./icon-1024.png",
+      "sizes": "1024x1024",
+      "type": "image/png"
+    }
+  ],
+  "lang": "en-US",
+  "id": "/index.html",
+  "start_url": "./index.html",
+  "display": "standalone",
+  "background_color": "white",
+  "theme_color": "white"
+}
diff --git a/ui/assets/maskable_icon_x512.png b/ui/assets/maskable_icon_x512.png
new file mode 100644
index 0000000..db8df3e
Binary files /dev/null and b/ui/assets/maskable_icon_x512.png differ
diff --git a/ui/assets/sw.js b/ui/assets/sw.js
new file mode 100644
index 0000000..7e546d6
--- /dev/null
+++ b/ui/assets/sw.js
@@ -0,0 +1,25 @@
+var cacheName = 'egui-template-pwa';
+var filesToCache = [
+  './',
+  './index.html',
+  './ui.js',
+  './ui_bg.wasm',
+];
+
+/* Start the service worker and cache all of the app's content */
+self.addEventListener('install', function (e) {
+  e.waitUntil(
+    caches.open(cacheName).then(function (cache) {
+      return cache.addAll(filesToCache);
+    })
+  );
+});
+
+/* Serve cached content when offline */
+self.addEventListener('fetch', function (e) {
+  e.respondWith(
+    caches.match(e.request).then(function (response) {
+      return response || fetch(e.request);
+    })
+  );
+});
diff --git a/ui/check.sh b/ui/check.sh
new file mode 100755
index 0000000..522dab1
--- /dev/null
+++ b/ui/check.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+# This scripts runs various CI-like checks in a convenient way.
+set -eux
+
+cargo check --quiet --workspace --all-targets
+cargo check --quiet --workspace --all-features --lib --target wasm32-unknown-unknown
+cargo fmt --all -- --check
+cargo clippy --quiet --workspace --all-targets --all-features --  -D warnings -W clippy::all
+cargo test --quiet --workspace --all-targets --all-features
+cargo test --quiet --workspace --doc
+trunk build
diff --git a/ui/index.html b/ui/index.html
new file mode 100644
index 0000000..dcdf3aa
--- /dev/null
+++ b/ui/index.html
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+    
+    ui
+
+    
+    
+    
+    
+
+    
+
+
+    
+    
+    
+    
+    
+    
+
+
+    
+    
+    
+    
+
+    
+
+
+
+    
+    
+    
+
+    
+    
+    
+
+
+
+
+
diff --git a/ui/rust-toolchain b/ui/rust-toolchain
new file mode 100644
index 0000000..031b4cc
--- /dev/null
+++ b/ui/rust-toolchain
@@ -0,0 +1,10 @@
+# If you see this, run "rustup self update" to get rustup 1.23 or newer.
+
+# NOTE: above comment is for older `rustup` (before TOML support was added),
+# which will treat the first line as the toolchain name, and therefore show it
+# to the user in the error, instead of "error: invalid channel name '[toolchain]'".
+
+[toolchain]
+channel = "1.76.0"
+components = [ "rustfmt", "clippy" ]
+targets = [ "wasm32-unknown-unknown" ]
diff --git a/ui/src/app.rs b/ui/src/app.rs
new file mode 100644
index 0000000..23125b7
--- /dev/null
+++ b/ui/src/app.rs
@@ -0,0 +1,277 @@
+use grad::{
+    ast::{ast_to_ascii, ASTNode, Parser},
+    chunk::Chunk,
+    compiler, debug,
+    interner::Interner,
+    run_source,
+    scanner::Lexer,
+    vm::{
+        self,
+        Result::{self as CompilerResult, CompileErr, Ok as CompilerOk, RuntimeErr},
+    },
+};
+
+use eframe::egui;
+use std::collections::HashMap;
+
+#[derive(serde::Deserialize, serde::Serialize)]
+struct CustomLanguage;
+
+
+#[derive(serde::Deserialize, serde::Serialize, Clone)]
+struct DisassembledOutput {
+    bytecode: Chunk,
+    interner: Interner,
+}
+
+impl CustomLanguage {
+    fn parse(&self, code: &str) -> Result, String> {
+        let mut lexer = Lexer::new(code.to_string());
+        let ast_out = match Parser::new(&mut lexer).parse() {
+            Ok(ast) => ast,
+            Err(e) => return Err(format!("{:?}", e)),
+        };
+
+        Ok(ast_out)
+    }
+
+    fn compile(&self, ast: &Vec) -> DisassembledOutput {
+        let mut compiler = compiler::Compiler::new();
+        let (bytecode, interner) = compiler.compile(ast.clone());
+
+        DisassembledOutput { bytecode, interner }
+    }
+
+    fn execute(&self, compiled: DisassembledOutput) -> String {
+        let mut vm = vm::VM::init(compiled.bytecode, compiled.interner);
+        let result: CompilerResult = vm.run();
+
+        match result {
+            CompilerOk(v) => {
+                let mut result = String::new();
+                for i in v.iter() {
+                    result.push_str(&format!("{:?}\n", i));
+                }
+
+                result
+            }
+            CompileErr(e) => format!("CompileError({:?})", e),
+            RuntimeErr(e) => format!("RuntimeError({:?})", e),
+        }
+    }
+}
+
+#[derive(serde::Deserialize, serde::Serialize)]
+#[serde(default)] // if we add new fields, give them default values when deserializing old state
+pub struct CustomLanguageDemo {
+    custom_lang: CustomLanguage,
+    code: String,
+    examples: Vec<(String, String)>,
+    selected_example: usize,
+    ast: Option>,
+    disassembled: Option,
+    result: String,
+    constants: HashMap,
+}
+
+impl Default for CustomLanguageDemo {
+    fn default() -> Self {
+        Self {
+            custom_lang: CustomLanguage,
+            code: String::new(),
+            examples: vec![
+                ("Hello World".to_string(), "print(1)".to_string()),
+                ("dfds".to_string(), "45354345".to_string()),
+            ],
+            selected_example: 0,
+            ast: None,
+            disassembled: None,
+            result: String::new(),
+            constants: HashMap::new(),
+        }
+    }
+}
+
+impl eframe::App for CustomLanguageDemo {
+    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
+        let left_panel_width = 400.0;
+        let top_panel_height = 300.0;
+
+        // Configure fonts (call this once, typically in the first frame)
+        self.configure_fonts(ctx);
+
+        // Left panel (Code editor and Examples)
+        egui::SidePanel::left("left_panel")
+            .resizable(true)
+            .default_width(left_panel_width)
+            .show(ctx, |ui| {
+                ui.heading("Code Editor");
+                ui.add_space(10.0);
+
+                ui.horizontal(|ui| {
+                    ui.label("Select an example:");
+                    egui::ComboBox::from_label("")
+                        .selected_text(&self.examples[self.selected_example].0)
+                        .show_ui(ui, |ui| {
+                            for (idx, (title, example)) in self.examples.iter().enumerate() {
+                                if ui
+                                    .selectable_value(&mut self.selected_example, idx, title)
+                                    .clicked()
+                                {
+                                    self.code = example.clone()
+                                }
+                            }
+                        });
+                });
+
+                ui.add_space(10.0);
+
+                let response = ui.add(
+                    egui::TextEdit::multiline(&mut self.code)
+                        .desired_width(f32::INFINITY)
+                        .desired_rows(25),
+                );
+
+                // if response.changed() {
+                //     self.update_output();
+                // }
+
+                ui.add_space(10.0);
+
+                if ui.button("Run").clicked() {
+                    self.update_output();
+                }
+
+                // if ctrl+enter is pressed, run the code
+                // if ui.input(|i| i.key_down(egui::Key::Enter).ctrl()).clicked() {
+                //     self.update_output();
+                // }
+
+                ui.heading("Execution Result");
+                ui.add_space(10.0);
+                ui.label(&self.result);
+            });
+
+        // Right panel (AST and Disassembled Output)
+        egui::CentralPanel::default().show(ctx, |ui| {
+            // Top right (AST)
+            egui::TopBottomPanel::top("ast_panel")
+                .resizable(true)
+                .default_height(top_panel_height)
+                .show_inside(ui, |ui| {
+                    ui.heading("Abstract Syntax Tree");
+                    ui.add_space(10.0);
+                    if let Some(_ast) = &self.ast {
+                        let mut ast_ascii = String::new();
+                        for stmt in _ast.iter() {
+                            ast_ascii.push_str(&ast_to_ascii(stmt, 0));
+                        }
+
+                        ui.label(ast_ascii);
+                    } else {
+                        ui.label("No AST available");
+                    }
+                });
+
+            // Bottom right (Disassembled Output)
+            ui.heading("Disassembled Output");
+            ui.add_space(10.0);
+            if let Some(_disassembled) = &self.disassembled {
+                let debug = debug::Debug::new(
+                    "test",
+                    _disassembled.bytecode.clone(),
+                    _disassembled.interner.clone(),
+                );
+                let disassemble_output = debug.disassemble();
+                ui.label(disassemble_output);
+
+                // Add hoverable constants
+                for (name, value) in &self.constants {
+                    ui.add(egui::Label::new(name).sense(egui::Sense::hover()))
+                        .on_hover_text(value);
+                }
+            } else {
+                ui.label("No disassembled output available");
+            }
+        });
+
+        // Bottom panel (Execution Result)
+        // egui::TopBottomPanel::bottom("result_panel")
+        //     .resizable(true)
+        //     .min_height(100.0)
+        //     .show(ctx, |ui| {
+
+        //     });
+    }
+}
+
+impl CustomLanguageDemo {
+    fn update_output(&mut self) {
+        match self.custom_lang.parse(&self.code) {
+            Ok(ast) => {
+                self.ast = Some(ast.clone());
+
+                let disassembled_output = self.custom_lang.compile(&ast);
+                self.disassembled = Some(disassembled_output.clone());
+
+                self.result = self.custom_lang.execute(disassembled_output);
+
+                // Populate constants (replace this with actual constant extraction)
+                self.constants.clear();
+                self.constants
+                    .insert("CONST_1".to_string(), "42".to_string());
+                self.constants
+                    .insert("CONSTs_2".to_string(), "Hello, World!".to_string());
+            }
+            Err(e) => {
+                self.ast = None;
+                self.disassembled = None;
+                self.result = format!("Error: {}", e);
+                self.constants.clear();
+            }
+        }
+    }
+
+    fn configure_fonts(&self, ctx: &egui::Context) {
+        let mut fonts = egui::FontDefinitions::default();
+
+        // Load your custom font
+        // Note: You need to include your font file in your project's assets
+        fonts.font_data.insert(
+            "my_font".to_owned(),
+            egui::FontData::from_static(include_bytes!("../assets/SpaceMono-Regular.ttf")),
+        );
+
+        // Set the custom font as the default for various text styles
+        fonts
+            .families
+            .entry(egui::FontFamily::Proportional)
+            .or_default()
+            .insert(0, "my_font".to_owned());
+
+        fonts
+            .families
+            .entry(egui::FontFamily::Monospace)
+            .or_default()
+            .insert(0, "my_font".to_owned());
+
+        // Set the configured fonts
+        ctx.set_fonts(fonts);
+    }
+}
+
+impl CustomLanguageDemo {
+    /// Called once before the first frame.
+    pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
+        // This is also where you can customize the look and feel of egui using
+        // `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.
+
+        // Load previous app state (if any).
+        // Note that you must enable the `persistence` feature for this to work.
+        if let Some(storage) = cc.storage {
+            return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default();
+        }
+
+        Default::default()
+    }
+}
\ No newline at end of file
diff --git a/ui/src/lib.rs b/ui/src/lib.rs
new file mode 100644
index 0000000..2506b79
--- /dev/null
+++ b/ui/src/lib.rs
@@ -0,0 +1,4 @@
+#![warn(clippy::all, rust_2018_idioms)]
+
+mod app;
+pub use app::CustomLanguageDemo;
diff --git a/ui/src/main.rs b/ui/src/main.rs
new file mode 100644
index 0000000..24dcb23
--- /dev/null
+++ b/ui/src/main.rs
@@ -0,0 +1,47 @@
+#![warn(clippy::all, rust_2018_idioms)]
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
+
+// When compiling natively:
+#[cfg(not(target_arch = "wasm32"))]
+fn main() -> eframe::Result<()> {
+    env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
+
+    let native_options = eframe::NativeOptions {
+        viewport: egui::ViewportBuilder::default()
+            .with_inner_size([400.0, 300.0])
+            .with_min_inner_size([300.0, 220.0])
+            .with_icon(
+                // NOTE: Adding an icon is optional
+                eframe::icon_data::from_png_bytes(&include_bytes!("../assets/icon-256.png")[..])
+                    .expect("Failed to load icon"),
+            ),
+        ..Default::default()
+    };
+    eframe::run_native(
+        "eframe template",
+        native_options,
+        Box::new(|cc| Box::new(ui::CustomLanguageDemo::new(cc))),
+    )
+}
+
+// When compiling to web using trunk:
+#[cfg(target_arch = "wasm32")]
+fn main() {
+    // Redirect `log` message to `console.log` and friends:
+    eframe::WebLogger::init(log::LevelFilter::Debug).ok();
+
+    let web_options = eframe::WebOptions::default();
+
+    wasm_bindgen_futures::spawn_local(async {
+        eframe::WebRunner::new()
+            .start(
+                "the_canvas_id", // hardcode it
+                web_options,
+                Box::new(|cc| {
+                    Box::new(ui::CustomLanguageDemo::new(cc))
+                }),
+            )
+            .await
+            .expect("failed to start eframe");
+    });
+}