├── .gitignore ├── README.md ├── lsp-monkey ├── Cargo.lock ├── Cargo.toml ├── README.md ├── scripts │ └── test_message.txt └── src │ ├── lib.rs │ ├── main.rs │ ├── server │ ├── error.rs │ └── mod.rs │ └── types │ ├── annotations.rs │ ├── base.rs │ ├── capabilities.rs │ ├── error_codes.rs │ ├── filesystem.rs │ ├── initialize.rs │ ├── kinds.rs │ ├── lsp.rs │ ├── message.rs │ ├── mod.rs │ ├── progress.rs │ ├── results.rs │ ├── symbols.rs │ ├── uri │ ├── error.rs │ └── mod.rs │ └── workspace.rs ├── monkey ├── Cargo.lock ├── Cargo.toml ├── examples │ ├── fibonacci.monkey │ ├── macro.monkey │ └── test.monkey ├── src │ ├── code │ │ ├── error.rs │ │ └── mod.rs │ ├── compiler │ │ ├── error.rs │ │ ├── mod.rs │ │ └── symbol_table.rs │ ├── evaluator │ │ ├── error.rs │ │ └── mod.rs │ ├── lexer.rs │ ├── lib.rs │ ├── main.rs │ ├── monkey │ │ └── mod.rs │ ├── object │ │ ├── builtin.rs │ │ ├── environment.rs │ │ ├── error.rs │ │ └── mod.rs │ ├── parser │ │ ├── ast.rs │ │ ├── errors.rs │ │ ├── mod.rs │ │ └── precedence.rs │ ├── token.rs │ ├── utils.rs │ ├── vm │ │ ├── error.rs │ │ ├── frame.rs │ │ └── mod.rs │ └── wasm.rs └── web │ ├── .gitignore │ ├── .npmrc │ ├── README.md │ ├── bun.lockb │ ├── declarations.d.ts │ ├── package.json │ ├── postcss.config.js │ ├── src │ ├── app.css │ ├── app.d.ts │ ├── app.html │ ├── lib │ │ ├── index.ts │ │ └── pkg │ │ │ ├── README.md │ │ │ ├── interpreter.d.ts │ │ │ ├── interpreter.js │ │ │ ├── interpreter_bg.wasm │ │ │ ├── interpreter_bg.wasm.d.ts │ │ │ └── package.json │ └── routes │ │ └── +page.svelte │ ├── svelte.config.js │ ├── tailwind.config.js │ ├── tsconfig.json │ └── vite.config.ts └── tree-sitter-monkey ├── .editorconfig ├── .gitattributes ├── .gitignore ├── CMakeLists.txt ├── Cargo.toml ├── Makefile ├── Package.swift ├── README.md ├── binding.gyp ├── bindings ├── c │ ├── tree-sitter-monkey.h │ └── tree-sitter-monkey.pc.in ├── go │ ├── binding.go │ └── binding_test.go ├── node │ ├── binding.cc │ ├── binding_test.js │ ├── index.d.ts │ └── index.js ├── python │ ├── tests │ │ └── test_binding.py │ └── tree_sitter_monkey │ │ ├── __init__.py │ │ ├── __init__.pyi │ │ ├── binding.c │ │ └── py.typed ├── rust │ ├── build.rs │ └── lib.rs └── swift │ ├── TreeSitterMonkey │ └── monkey.h │ └── TreeSitterMonkeyTests │ └── TreeSitterMonkeyTests.swift ├── go.mod ├── grammar.js ├── package-lock.json ├── package.json ├── pyproject.toml ├── queries ├── highlights.scm └── locals.scm ├── setup.py ├── simple.monkey ├── src ├── grammar.json ├── node-types.json ├── parser.c └── tree_sitter │ ├── alloc.h │ ├── array.h │ └── parser.h ├── test.monkey └── tree-sitter.json /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/web/node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## MonkeyLang Interpreter in Rust 2 | 3 | This repo contains an implementation of Thorsten Ball's Monkey Language in Rust. 4 | Initially, I only implemented the interpreter as defined in his Interpreter Book. 5 | As I really enjoyed the book, I decided to give the Compiler Book a spin. Now, the 6 | implementation includes both the original naively interpreted implementation, and the 7 | bytecode compiled + vm implementation. As a treat, you can also run this project 8 | in the browser using wasm. 9 | 10 | I used this project to learn rust, and I imagine it isn't the most idiomatic rust code. 11 | Please feel free to rip my implementation to shreds. 12 | 13 | // Sidebar 14 | 15 | This is much later, oh and I'm still bad at rust, but I wanted to learn more 16 | about syntax highlighting and lsps, so I went ahead and implemented a tree-sitter 17 | grammar for monkey. I'm currently in the process of adding an lsp for monkey, but 18 | I'm trying to implement the LSP from "scratch" so it's taking a bit. 19 | 20 | Okay, building the underlying language server protocol is going to take a 21 | while. However, I'm learning a lot. 22 | 23 | Actually, I don't know if I'm learning anything. It's all just types. 24 | I'm going to do a wild rush towards getting just the initialize command 25 | implemented. It's all just writing types, so it's super painful, but 26 | once I get to the actual writing of the lsp, I think it will be more fun 27 | and educational. 28 | 29 | That said, I'm going to evenutally finish this but I may have to return to it in a bit. 30 | 31 | ### Usage 32 | 33 | I designed the interpreter binary to work much like the lua binary. 34 | You can run the interpreter (in naive-interactive mode) by simply using the binary name. 35 | 36 | ``` 37 | monkey 38 | ``` 39 | 40 | Additionally, you can supply a script to run or library to load before opening the repl 41 | with the -i flag. 42 | 43 | ``` 44 | monkey -i ./lib.monkey 45 | ``` 46 | 47 | To simply execute a file, without using the repl, provide the path to the file as an argument. 48 | 49 | ``` 50 | monkey ./main.monkey 51 | ``` 52 | 53 | Finally, you can run any of the above commands using the more performant, 54 | bytecode-interpreted (rather than sourcecode interpreted) and vm executed version of monkey, by supplying the --mode vm 55 | 56 | ``` 57 | monkey --mode vm 58 | monkey --mode vm -i ./lib.monkey 59 | monkey --mode vm ./main.monkey 60 | 61 | ``` 62 | 63 | ### Acknowledgements 64 | 65 | Some of the code here was influenced by [ ThePrimeagen's ](https://github.com/ThePrimeagen) version which he abandoned 66 | two chapters into the book. Much of the code is influenced by [monkey-wasm](https://github.com/shioyama18/monkey-wasm/tree/master). 67 | Additionally, for the compiler implementation, I occasionally referenced [cymbal](https://github.com/shuhei/cymbal) 68 | 69 | 70 | -------------------------------------------------------------------------------- /lsp-monkey/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lsp-monkey" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | anyhow = "1.0.97" 8 | jsonrpc-core = "18.0.0" 9 | jsonrpc-stdio-server = "18.0.0" 10 | lazy_static = "1.5.0" 11 | regex = "1.11.1" 12 | serde = { version = "1.0.219", features = ["derive"] } 13 | serde_json = "1.0.140" 14 | thiserror = "2.0.12" 15 | tokio = { version = "1", features = ["full"] } 16 | 17 | -------------------------------------------------------------------------------- /lsp-monkey/README.md: -------------------------------------------------------------------------------- 1 | ### lsp-monkey 2 | 3 | lsp-monkey is an implementation of the LSP protocol both in general 4 | and for the monkey programming language. In this project, I define 5 | all the types and functions needed to implement an LPS server, and 6 | I do so for monkey. 7 | 8 | ### TODO 9 | 10 | - [x] Go through all types, using #[serder(rename="")] where needed 11 | - [x] Go through all types, using #[serde(default, skip_serializing_if)] as needed 12 | - [ ] Implement InitializeParams 13 | - [ ] Implement ClientCapabilities 14 | - [ ] Implement TraceValues 15 | - [ ] Implement WorkspaceFolders 16 | 17 | On pause until HTTP1.1 is implemented. I have too many side projects and this 18 | one is way too big. 19 | 20 | -------------------------------------------------------------------------------- /lsp-monkey/scripts/test_message.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 57 2 | Content-Type: application/vscode-jsonrpc; charset=utf-7 3 | 4 | {"jsonrpc":"2.0","id":1,"method":"say_hello","params":[]} 5 | -------------------------------------------------------------------------------- /lsp-monkey/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod server; 2 | pub mod types; 3 | -------------------------------------------------------------------------------- /lsp-monkey/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use lsp_monkey::server::LspServer; 3 | 4 | fn main() -> Result<()> { 5 | let stdin = std::io::stdin(); 6 | let stdout = std::io::stdout(); 7 | let mut server = LspServer::new(stdin.lock(), stdout.lock()); 8 | server.setup_handlers(); 9 | server.run().context("LSP server failed to run") 10 | } 11 | -------------------------------------------------------------------------------- /lsp-monkey/src/server/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum LspError { 5 | #[error("I/O error: {0}")] 6 | Io(#[from] std::io::Error), 7 | 8 | #[error("Failed to parse UTF-8: {0}")] 9 | Utf8(#[from] std::string::FromUtf8Error), 10 | 11 | #[error("Failed to parse content length: {0}")] 12 | Parse(#[from] std::num::ParseIntError), 13 | 14 | #[error("LSP protocol error: {0}")] 15 | Protocol(String), 16 | } 17 | 18 | impl LspError { 19 | pub fn new(msg: String) -> Self { 20 | LspError::Protocol(msg) 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /lsp-monkey/src/server/mod.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | 3 | use crate::types::{InitializeParams, RequestId}; 4 | use error::LspError; 5 | use jsonrpc_core::{IoHandler, Value}; 6 | use std::collections::HashMap; 7 | use std::io::{BufRead, BufReader, BufWriter, Read, Write}; 8 | 9 | pub struct LspServer 10 | where 11 | R: Read, 12 | W: Write, 13 | { 14 | reader: BufReader, 15 | writer: BufWriter, 16 | request_cancellations: HashMap, 17 | io: IoHandler, 18 | initialized: bool, 19 | } 20 | 21 | impl LspServer 22 | where 23 | R: Read, 24 | W: Write, 25 | { 26 | pub fn new(reader: R, writer: W) -> Self { 27 | LspServer { 28 | reader: BufReader::new(reader), 29 | writer: BufWriter::new(writer), 30 | request_cancellations: HashMap::new(), 31 | io: IoHandler::new(), 32 | initialized: false, 33 | } 34 | } 35 | 36 | pub fn setup_handlers(&mut self) { 37 | let io = &mut self.io; 38 | io.add_sync_method("say_hello", |_params| Ok(Value::String("hello".to_owned()))); 39 | } 40 | 41 | pub fn initialize(&mut self, params: InitializeParams) -> Result<(), LspError> { 42 | Ok(()) 43 | } 44 | 45 | pub fn run(&mut self) -> Result<(), LspError> { 46 | let mut buffer = String::new(); 47 | loop { 48 | let mut content_length: Option = None; 49 | loop { 50 | buffer.clear(); 51 | let bytes_read = self.reader.read_line(&mut buffer)?; 52 | 53 | if bytes_read == 0 { 54 | return Ok(()); 55 | } 56 | 57 | let length_prefix = "Content-Length: "; 58 | let line = buffer.trim_end(); 59 | if line.is_empty() { 60 | break; 61 | } 62 | 63 | if line.starts_with("Content-Length: ") { 64 | let content_length_str = buffer[length_prefix.len()..].trim_end(); 65 | match content_length_str.parse::() { 66 | Ok(len) => { 67 | content_length = Some(len); 68 | } 69 | Err(e) => { 70 | eprintln!("Failed to parse content length: {}", e); 71 | } 72 | } 73 | } 74 | if line.starts_with("Content-Type: ") 75 | && !line.contains("charset=utf-8") 76 | && !line.contains("utf8") 77 | { 78 | let error_response = r#"{"jsonrpc":"2.0","error":{"code":-32701,"message":"Unsupported encoding, only utf-8 is supported"},"id":null}"#; 79 | let response_bytes = error_response.as_bytes(); 80 | write!( 81 | self.writer, 82 | "Content-Length: {}\r\n\r\n", 83 | response_bytes.len() 84 | )?; 85 | 86 | self.writer.write_all(response_bytes)?; 87 | self.writer.flush()?; 88 | continue; 89 | } 90 | } 91 | 92 | if let Some(length) = content_length { 93 | let mut content = vec![0; length]; 94 | self.reader.read_exact(&mut content)?; 95 | let request_str = String::from_utf8(content)?; 96 | eprintln!("Request: {}", request_str); 97 | 98 | let response = self.io.handle_request_sync(&request_str); 99 | 100 | if let Some(response_str) = response { 101 | let response_bytes = response_str.as_bytes(); 102 | write!( 103 | self.writer, 104 | "Content-Length: {}\r\n\r\n", 105 | response_bytes.len() 106 | )?; 107 | self.writer.write_all(response_bytes)?; 108 | self.writer.flush()?; 109 | eprintln!("\nSent response"); 110 | } 111 | } else { 112 | eprintln!("No Content-Length header found, skipping") 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/annotations.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// ChangeAnnotationSupport defines capabilities specific to change annotations. 4 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 5 | #[serde(rename_all = "camelCase")] 6 | pub struct ChangeAnnotationSupport { 7 | /// Whether the client groups edits with equal labels into tree nodes, 8 | /// for instance all edits labelled with "Changes in Strings" would 9 | /// be a tree node. 10 | #[serde(skip_serializing_if = "Option::is_none")] 11 | pub groups_on_label: Option, 12 | } 13 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/base.rs: -------------------------------------------------------------------------------- 1 | use serde::de::{self, Visitor}; 2 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 3 | use std::fmt; 4 | use std::marker::PhantomData; 5 | 6 | /// Defines an integer number in the range of -2^31 to 2^31 - 1. 7 | pub type Integer = i32; 8 | 9 | /// Defines an unsigned integer number in the range of 0 to 2^31 - 1. 10 | pub type UInteger = u32; 11 | 12 | /// Represents a decimal number, typically in the range [0, 1]. 13 | /// 14 | /// This type enforces range constraints during deserialization to 15 | /// ensure valid decimal values according to the LSP specification. 16 | pub struct Decimal(f32); 17 | 18 | /// Represents any valid LSP value. 19 | /// 20 | /// This can be an object, array, string, number, boolean, or null, 21 | /// following the JSON data model used by the Language Server Protocol. 22 | pub type LSPAny = serde_json::Value; 23 | 24 | /// Represents a JSON object with string keys and arbitrary LSP values. 25 | pub type LSPObject = serde_json::Map; 26 | 27 | /// Represents a JSON array containing arbitrary LSP values. 28 | pub type LSPArray = Vec; 29 | 30 | impl Decimal { 31 | /// Creates a new Decimal if the value is within the valid range [0, 1]. 32 | /// 33 | /// Returns None if the value is outside the valid range. 34 | pub fn new(value: f32) -> Option { 35 | if value >= 0.0 && value <= 1.0 { 36 | Some(Decimal(value)) 37 | } else { 38 | None 39 | } 40 | } 41 | 42 | /// Returns the underlying f32 value. 43 | pub fn value(&self) -> f32 { 44 | self.0 45 | } 46 | } 47 | 48 | impl Serialize for Decimal { 49 | fn serialize(&self, serializer: S) -> Result 50 | where 51 | S: Serializer, 52 | { 53 | self.0.serialize(serializer) 54 | } 55 | } 56 | 57 | impl<'de> Deserialize<'de> for Decimal { 58 | fn deserialize(deserializer: D) -> Result 59 | where 60 | D: Deserializer<'de>, 61 | { 62 | struct DecimalVisitor(PhantomData Decimal>); 63 | impl<'de> Visitor<'de> for DecimalVisitor { 64 | type Value = Decimal; 65 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 66 | formatter.write_str("a decimal number between 0 and 1") 67 | } 68 | fn visit_f32(self, value: f32) -> Result 69 | where 70 | E: de::Error, 71 | { 72 | if value >= 0.0 && value <= 1.0 { 73 | Ok(Decimal(value)) 74 | } else { 75 | Err(E::custom(format!("decimal out of range: {}", value))) 76 | } 77 | } 78 | } 79 | deserializer.deserialize_f64(DecimalVisitor(PhantomData)) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/capabilities.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{base::LSPAny, workspace::WorkspaceClientCapabilities}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Represents the level of verbosity with which the server systematically 6 | /// reports its execution trace using $/logTrace notifications. 7 | /// The initial trace value is set by the client at initialization and can be 8 | /// modified later using the $/setTrace notification. 9 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 10 | #[serde(rename_all = "lowercase")] 11 | pub enum TraceValue { 12 | /// No tracing 13 | Off, 14 | /// Tracing is enabled 15 | Messages, 16 | /// Tracing is enabled and verbose 17 | Verbose, 18 | } 19 | 20 | /// ClientCapabilities define capabilities for dynamic registration, 21 | /// workspace and text document features the client supports. 22 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 23 | #[serde(rename_all = "camelCase")] 24 | pub struct ClientCapabilities { 25 | /// workspace specific client capabilities TODO: WorkspaceClientCapabilities 26 | workspace: Option, 27 | 28 | /// Text document specific client capabilities TODO: TextDocumentClientCapabilities 29 | #[serde(skip_serializing_if = "Option::is_none")] 30 | text_document: Option, 31 | } 32 | 33 | /// TextDocumentClientCapabilities define capabilities the 34 | /// editor / tool provides on text documents. 35 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 36 | #[serde(rename_all = "camelCase")] 37 | pub struct TextDocumentClientCapabilities { 38 | /// Synchronization capabilities 39 | #[serde(skip_serializing_if = "Option::is_none")] 40 | pub synchronization: Option, 41 | 42 | /// Capabilities specific to the `textDocument/completion` request 43 | #[serde(skip_serializing_if = "Option::is_none")] 44 | pub completion: Option, 45 | 46 | /// Capabilities specific to the `textDocument/hover` request 47 | #[serde(skip_serializing_if = "Option::is_none")] 48 | pub hover: Option, 49 | 50 | /// Capabilities specific to the `textDocument/signatureHelp` request 51 | #[serde(skip_serializing_if = "Option::is_none")] 52 | pub signature_help: Option, 53 | 54 | /// Capabilities specific to the `textDocument/declaration` request 55 | #[serde(skip_serializing_if = "Option::is_none")] 56 | pub declaration: Option, 57 | 58 | /// Capabilities specific to the `textDocument/definition` request 59 | #[serde(skip_serializing_if = "Option::is_none")] 60 | pub definition: Option, 61 | 62 | /// Capabilities specific to the `textDocument/typeDefinition` request 63 | #[serde(skip_serializing_if = "Option::is_none")] 64 | pub type_definition: Option, 65 | 66 | /// Capabilities specific to the `textDocument/implementation` request 67 | #[serde(skip_serializing_if = "Option::is_none")] 68 | pub implementation: Option, 69 | 70 | /// Capabilities specific to the `textDocument/references` request 71 | #[serde(skip_serializing_if = "Option::is_none")] 72 | pub references: Option, 73 | 74 | /// Capabilities specific to the `textDocument/documentHighlight` request 75 | #[serde(skip_serializing_if = "Option::is_none")] 76 | pub document_highlight: Option, 77 | 78 | /// Capabilities specific to the `textDocument/documentSymbol` request 79 | #[serde(skip_serializing_if = "Option::is_none")] 80 | pub document_symbol: Option, 81 | 82 | /// Capabilities specific to the `textDocument/codeAction` request 83 | #[serde(skip_serializing_if = "Option::is_none")] 84 | pub code_action: Option, 85 | 86 | /// Capabilities specific to the `textDocument/codeLens` request 87 | #[serde(skip_serializing_if = "Option::is_none")] 88 | pub code_lens: Option, 89 | 90 | /// Capabilities specific to the `textDocument/documentLink` request 91 | #[serde(skip_serializing_if = "Option::is_none")] 92 | pub document_link: Option, 93 | 94 | /// Capabilities specific to the `textDocument/documentColor` request 95 | #[serde(skip_serializing_if = "Option::is_none")] 96 | pub color_provider: Option, 97 | 98 | /// Capabilities specific to the `textDocument/formatting` request 99 | #[serde(skip_serializing_if = "Option::is_none")] 100 | pub formatting: Option, 101 | 102 | /// Capabilities specific to the `textDocument/rangeFormatting` request 103 | #[serde(skip_serializing_if = "Option::is_none")] 104 | pub range_formatting: Option, 105 | 106 | /// Capabilities specific to the `textDocument/onTypeFormatting` request 107 | #[serde(skip_serializing_if = "Option::is_none")] 108 | pub on_type_formatting: Option, 109 | 110 | /// Capabilities specific to the `textDocument/rename` request 111 | #[serde(skip_serializing_if = "Option::is_none")] 112 | pub rename: Option, 113 | 114 | /// Capabilities specific to the `textDocument/publishDiagnostics` notification 115 | #[serde(skip_serializing_if = "Option::is_none")] 116 | pub publish_diagnostics: Option, 117 | 118 | /// Capabilities specific to the `textDocument/foldingRange` request 119 | #[serde(skip_serializing_if = "Option::is_none")] 120 | pub folding_range: Option, 121 | 122 | /// Capabilities specific to the `textDocument/selectionRange` request 123 | #[serde(skip_serializing_if = "Option::is_none")] 124 | pub selection_range: Option, 125 | 126 | /// Capabilities specific to the `textDocument/linkedEditingRange` request 127 | #[serde(skip_serializing_if = "Option::is_none")] 128 | pub linked_editing_range: Option, 129 | 130 | /// Capabilitiesspecific to the various call hierarchy requests 131 | #[serde(skip_serializing_if = "Option::is_none")] 132 | pub call_hierarchy: Option, 133 | 134 | /// Capabilities specific to the various semantic token requests 135 | #[serde(skip_serializing_if = "Option::is_none")] 136 | pub semantic_tokens: Option, 137 | 138 | /// Capabilities specific to the `textDocument/moniker` request 139 | #[serde(skip_serializing_if = "Option::is_none")] 140 | pub moniker: Option, 141 | 142 | /// Capabilities specific to the various type hierarchy requests 143 | #[serde(skip_serializing_if = "Option::is_none")] 144 | pub type_hierarchy: Option, 145 | 146 | /// Capabilities specific to the `textDocument/inlineValue` request 147 | #[serde(skip_serializing_if = "Option::is_none")] 148 | pub inline_value: Option, 149 | 150 | /// Capabilities specific to the `textDocument/inlayHint` request 151 | #[serde(skip_serializing_if = "Option::is_none")] 152 | pub inlay_hint: Option, 153 | 154 | /// Capabilities specific to the diagnostic pull model 155 | #[serde(skip_serializing_if = "Option::is_none")] 156 | pub diagnostic: Option, 157 | } 158 | 159 | /// The client has support for file requests/notifications. 160 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 161 | #[serde(rename_all = "camelCase")] 162 | pub struct FileOperationsClientCapabilities { 163 | /// Whether the client supports dynamic registration for file 164 | /// requests/notifications. 165 | #[serde(skip_serializing_if = "Option::is_none")] 166 | pub watchers: Option, 167 | 168 | /// Capabilities specific to the `FileOperationRegistrationOptions` 169 | #[serde(skip_serializing_if = "Option::is_none")] 170 | pub rename: Option, 171 | } 172 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/error_codes.rs: -------------------------------------------------------------------------------- 1 | /// Error code indicating an issue occurred while parsing a JSON-RPC message. 2 | /// This is defined by the JSON-RPC specification. 3 | pub const PARSE_ERROR: i32 = -32700; 4 | 5 | /// Error code indicating the JSON-RPC request was malformed. 6 | pub const INVALID_REQUEST: i32 = -32600; 7 | 8 | /// Error code indicating the method specified in the request doesn't exist. 9 | pub const METHOD_NOT_FOUND: i32 = -32601; 10 | 11 | /// Error code indicating the method parameters are invalid. 12 | pub const INVALID_PARAMS: i32 = -32602; 13 | 14 | /// Error code indicating an internal error occurred in the server. 15 | pub const INTERNAL_ERROR: i32 = -32603; 16 | 17 | /// This is the start range of JSON-RPC reserved error codes. 18 | /// It doesn't denote a real error code. 19 | pub const JSON_RPC_RESERVED_ERROR_RANGE_START: i32 = -32099; 20 | 21 | /// @deprecated Use JSON_RPC_RESERVED_ERROR_RANGE_START instead. 22 | #[deprecated(note = "use JSON_RPC_RESERVED_ERROR_RANGE_START")] 23 | pub const SERVER_ERROR_START: i32 = JSON_RPC_RESERVED_ERROR_RANGE_START; 24 | 25 | /// Error code indicating that a server received a notification or 26 | /// request before the server has received the `initialize` request. 27 | pub const SERVER_NOT_INITIALIZED: i32 = -32002; 28 | 29 | /// Generic error code for unexpected server errors. 30 | pub const UNKNOWN_ERROR_CODE: i32 = -32001; 31 | 32 | /// This is the end range of JSON-RPC reserved error codes. 33 | /// It doesn't denote a real error code. 34 | pub const JSON_RPC_RESERVED_ERROR_RANGE_END: i32 = -32000; 35 | 36 | /// @deprecated Use JSON_RPC_RESERVED_ERROR_RANGE_END instead. 37 | #[deprecated(note = "use JSON_RPC_RESERVED_ERROR_RANGE_END")] 38 | pub const SERVER_ERROR_END: i32 = JSON_RPC_RESERVED_ERROR_RANGE_END; 39 | 40 | /// This is the start range of LSP reserved error codes. 41 | /// It doesn't denote a real error code. 42 | pub const LSP_RESERVED_ERROR_RANGE_START: i32 = -32899; 43 | 44 | /// A request failed but it was syntactically correct, e.g the 45 | /// method name was known and the parameters were valid. The error 46 | /// message should contain human readable information about why 47 | /// the request failed. 48 | pub const REQUEST_FAILED: i32 = -32803; 49 | 50 | /// The server cancelled the request. This error code should 51 | /// only be used for requests that explicitly support being 52 | /// server cancellable. 53 | pub const SERVER_CANCELLED: i32 = -32802; 54 | 55 | /// The server detected that the content of a document got 56 | /// modified outside normal conditions. A server should 57 | /// NOT send this error code if it detects a content change 58 | /// in it unprocessed messages. 59 | pub const CONTENT_MODIFIED: i32 = -32801; 60 | 61 | /// The client has canceled a request and a server has detected 62 | /// the cancel. 63 | pub const REQUEST_CANCELLED: i32 = -32800; 64 | 65 | /// This is the end range of LSP reserved error codes. 66 | /// It doesn't denote a real error code. 67 | pub const LSP_RESERVED_ERROR_RANGE_END: i32 = -32800; 68 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/filesystem.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{UInteger, uri::DocumentUri, workspace::WorkspaceFolder}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// A glob pattern is either a raw string or a relative‐pattern object. 5 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 6 | #[serde(untagged)] 7 | pub enum GlobPattern { 8 | /// e.g. `"**/*.rs"` 9 | Pattern(String), 10 | 11 | /// e.g. `{ baseUri: {...}, pattern: "**/*.rs" }` 12 | Relative(RelativePattern), 13 | } 14 | 15 | /// A helper to construct glob patterns matched relative to a base URI or workspace folder. 16 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 17 | #[serde(rename_all = "camelCase")] 18 | pub struct RelativePattern { 19 | /// Either a workspace folder or a raw URI. 20 | #[serde(rename = "baseUri")] 21 | pub base: WorkspaceFolderOrUri, 22 | 23 | /// The glob pattern itself. 24 | pub pattern: String, 25 | } 26 | 27 | /// Union of WorkspaceFolder or plain URI 28 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 29 | #[serde(untagged)] 30 | pub enum WorkspaceFolderOrUri { 31 | Folder(WorkspaceFolder), 32 | Uri(DocumentUri), 33 | } 34 | 35 | /// The kind of file‐system events to watch. Defaults (1|2|4 = 7) if you omit it. 36 | pub type WatchKind = UInteger; 37 | pub mod watch_kind { 38 | use super::WatchKind; 39 | pub const CREATE: WatchKind = 1; 40 | pub const CHANGE: WatchKind = 2; 41 | pub const DELETE: WatchKind = 4; 42 | } 43 | 44 | /// One individual watcher 45 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 46 | #[serde(rename_all = "camelCase")] 47 | pub struct FileSystemWatcher { 48 | /// GlobPattern = String | RelativePattern 49 | pub glob_pattern: GlobPattern, 50 | 51 | /// Watch kinds (create=1, change=2, delete=4). 52 | /// If you omit this field, you should treat it as `1|2|4` on the server. 53 | #[serde(skip_serializing_if = "Option::is_none")] 54 | pub kind: Option, 55 | } 56 | 57 | /// An event describing a file change. 58 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 59 | #[serde(rename_all = "camelCase")] 60 | pub struct FileEvent { 61 | /// The file’s URI 62 | pub uri: DocumentUri, 63 | 64 | /// The change type (1 = created, 2 = changed, 3 = deleted) 65 | #[serde(rename = "type")] 66 | pub r#type: FileChangeType, 67 | } 68 | 69 | /// File‐change type is 1, 2 or 3 70 | pub type FileChangeType = UInteger; 71 | pub mod file_change_type { 72 | use super::FileChangeType; 73 | pub const CREATED: FileChangeType = 1; 74 | pub const CHANGED: FileChangeType = 2; 75 | pub const DELETED: FileChangeType = 3; 76 | } 77 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/initialize.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{ 2 | base::{Integer, LSPAny}, 3 | capabilities::{ClientCapabilities, TraceValue}, 4 | progress::WorkDoneProgressParams, 5 | uri::DocumentUri, 6 | workspace::WorkspaceFolders, 7 | }; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | /// Payload for the very first request sent from the client to the server 11 | #[derive(Debug, Clone, Serialize, Deserialize)] 12 | pub struct InitializeParams { 13 | /// From `WorkDoneProgressParams`: optional token to report progress. 14 | #[serde(flatten)] 15 | pub progress: WorkDoneProgressParams, 16 | 17 | /// The process Id of the parent process that started the server. Is null if 18 | /// the process has not been started by another process. If the parent 19 | /// process is not alive then the server should exit (see exit notification) 20 | /// its process. 21 | #[serde(rename = "processId")] 22 | pub process_id: Option, 23 | 24 | /// Information about the client 25 | #[serde(rename = "clientInfo", skip_serializing_if = "Option::is_none")] 26 | pub client_info: Option, 27 | 28 | /// The locale the client is currently showing the user interface 29 | /// in. This must not necessarily be the locale of the operating 30 | /// system. 31 | #[serde(skip_serializing_if = "Option::is_none")] 32 | pub locale: Option, 33 | 34 | /// @deprecated in favour of `rootUri` 35 | #[serde(rename = "rootPath", skip_serializing_if = "Option::is_none")] 36 | pub root_path: Option, 37 | 38 | /// The rootUri of the workspace. Is null if no 39 | /// folder is open. If both `rootPath` and `rootUri` are set 40 | /// `rootUri` wins. 41 | /// 42 | /// @deprecated in favour of `workspaceFolders` 43 | #[serde(rename = "rootUri")] 44 | #[deprecated(note = "Use `workspace_folders` instead")] 45 | pub root_uri: Option, 46 | 47 | /// User provided initialization options 48 | #[serde( 49 | rename = "initializationOptions", 50 | skip_serializing_if = "Option::is_none" 51 | )] 52 | pub initialization_options: Option, 53 | 54 | /// The capabilities provided by the client (editor or tool) 55 | pub capabilities: ClientCapabilities, 56 | 57 | /// The initial trace setting. If omitted trace is disabled ('off'). 58 | #[serde(skip_serializing_if = "Option::is_none")] 59 | pub trace: Option, 60 | 61 | /// The workspace folders configured in the client when the server starts. 62 | /// This property is only available if the client supports workspace folders. 63 | /// It can be `null` if the client supports workspace folders but none are 64 | /// configured. 65 | #[serde(rename = "workspaceFolders", skip_serializing_if = "Option::is_none")] 66 | pub workspace_folders: Option, 67 | } 68 | 69 | /// Subobject for clientInfo in InitializeParams 70 | #[derive(Debug, Clone, Serialize, Deserialize)] 71 | pub struct ClientInfo { 72 | /// The name of the client as defined by the client 73 | pub name: String, 74 | 75 | /// The client's version as defined by the client 76 | #[serde(skip_serializing_if = "Option::is_none")] 77 | pub version: Option, 78 | } 79 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/kinds.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// How a client wants the server to behave if applying a WorkspaceEdit fails. 4 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 5 | #[serde(rename_all = "camelCase")] 6 | pub enum FailureHandlingKind { 7 | /// Applying the workspace change is simply aborted if one of the changes 8 | /// provided fails. All operations executed before the failing operation 9 | /// stay executed. 10 | Abort, 11 | 12 | /// All operations are executed transactionally. That means they either all 13 | /// succeed or no changes at all are applied to the workspace. 14 | Transactional, 15 | 16 | /// If the workspace edit contains only textual file changes they are 17 | /// executed transactionally. If resource changes (create, rename or delete 18 | /// file) are part of the change the failure handling strategy is abort. 19 | TextOnlyTransactional, 20 | 21 | /// The client tries to undo the operations already executed. But there is no 22 | /// guarantee this will succeed. 23 | Undo, 24 | } 25 | 26 | /// The kind of resource operations supported by the client 27 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 28 | #[serde(rename_all = "camelCase")] 29 | pub enum ResourceOperationKind { 30 | /// Supports create operations for files and folders 31 | Create, 32 | 33 | /// Supports rename operations for files and folders 34 | Rename, 35 | 36 | /// Supports delete operations for files and folders 37 | Delete, 38 | } 39 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/lsp.rs: -------------------------------------------------------------------------------- 1 | use crate::types::base::UInteger; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// Position in a text document expressed as a tuple of line and character 5 | #[derive(Debug, Clone, Serialize, Deserialize)] 6 | pub struct Position { 7 | /// The zero-based line number 8 | pub line: UInteger, 9 | 10 | /// The zero-based character offset 11 | pub character: UInteger, 12 | } 13 | 14 | /// Parameters for progress notifications 15 | #[derive(Debug, Clone, Serialize, Deserialize)] 16 | pub struct HoverParams { 17 | /// The text documenst URI in string form 18 | #[serde(rename = "textDocument")] 19 | pub text_document: String, 20 | 21 | /// The position in the text document 22 | pub position: Position, 23 | } 24 | 25 | /// Result of a hover request 26 | pub struct HoverResult { 27 | /// the string to be displayed 28 | pub value: String, 29 | } 30 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/message.rs: -------------------------------------------------------------------------------- 1 | use crate::types::base::{Integer, LSPAny}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// Base message type as defined by JSON-RPC. 5 | /// The Language Server Protocol always uses "2.0" as the jsonrpc version. 6 | #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] 7 | pub struct Message { 8 | /// The JSON-RPC protocol version, always "2.0". 9 | pub jsonrpc: String, 10 | } 11 | 12 | /// A unique identifier for a request, can be int or string 13 | #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] 14 | #[serde(untagged)] 15 | pub enum RequestId { 16 | Integer(Integer), 17 | String(String), 18 | } 19 | 20 | /// A request message to describe a request between the client and the server. 21 | /// Every processed request must send a response back to the sender of the request. 22 | #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] 23 | pub struct RequestMessage { 24 | /// The JSON-RPC protocol version, must be set to "2.0". 25 | pub jsonrpc: String, 26 | 27 | /// The request id. 28 | pub id: RequestId, 29 | 30 | /// The method to be invoked. 31 | pub method: String, 32 | 33 | /// The method's params. 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | pub params: Option, 36 | } 37 | 38 | /// The error object in case a request fails. 39 | #[derive(Debug, Clone, Serialize, Deserialize)] 40 | pub struct ResponseError { 41 | /// A number indicating the error type that occurred. 42 | pub code: Integer, 43 | 44 | /// A string providing a short description of the error. 45 | pub message: String, 46 | 47 | /// A Primitive or Structured value that contains additional information about the error. 48 | #[serde(skip_serializing_if = "Option::is_none")] 49 | pub data: Option, 50 | } 51 | 52 | /// A Response Message sent as a result of a request. 53 | /// If a request doesn't provide a result value the receiver still needs to 54 | /// return a response message to conform to the JSON-RPC specification. 55 | #[derive(Debug, Clone, Serialize, Deserialize)] 56 | pub struct ResponseMessage { 57 | /// The JSON-RPC protocol version, must be set to "2.0". 58 | pub jsonrpc: String, 59 | 60 | /// The request id. 61 | pub id: Option, 62 | 63 | /// The result of a request. This member is REQUIRED on success. 64 | /// This member MUST NOT exist if there was an error invoking the method. 65 | #[serde(skip_serializing_if = "Option::is_none")] 66 | pub result: Option, 67 | 68 | /// The error object in case a request fails. 69 | #[serde(skip_serializing_if = "Option::is_none")] 70 | pub error: Option, 71 | } 72 | 73 | /// A notification message. A processed notification message must not send a 74 | /// response back. They work like events. 75 | #[derive(Debug, Clone, Serialize, Deserialize)] 76 | pub struct NotificationMessage { 77 | /// The JSON-RPC protocol version, always "2.0". 78 | pub jsonrpc: String, 79 | 80 | /// The method to be invoked. 81 | pub method: String, 82 | 83 | /// The notification's params. 84 | #[serde(skip_serializing_if = "Option::is_none")] 85 | pub params: Option, 86 | } 87 | 88 | /// Parameters for the cancel request 89 | #[derive(Debug, Clone, Serialize, Deserialize)] 90 | pub struct CancelParams { 91 | /// The request id to cancel. 92 | pub id: RequestId, 93 | } 94 | 95 | /// A unique token to identify progress operations 96 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 97 | #[serde(untagged)] 98 | pub enum ProgressToken { 99 | Integer(Integer), 100 | String(String), 101 | } 102 | 103 | /// Parameters for progress notifications 104 | #[derive(Debug, Clone, Serialize, Deserialize)] 105 | pub struct ProgressParams { 106 | /// The progress token provided by the client or server. 107 | pub token: ProgressToken, 108 | 109 | /// The progress data. 110 | pub value: T, 111 | } 112 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | mod annotations; 2 | mod base; 3 | mod capabilities; 4 | mod error_codes; 5 | mod filesystem; 6 | mod initialize; 7 | mod kinds; 8 | mod lsp; 9 | mod message; 10 | mod progress; 11 | mod results; 12 | mod symbols; 13 | mod workspace; 14 | 15 | pub mod uri; 16 | 17 | pub use annotations::*; 18 | pub use base::*; 19 | pub use capabilities::*; 20 | pub use error_codes::*; 21 | pub use filesystem::*; 22 | pub use initialize::*; 23 | pub use kinds::*; 24 | pub use lsp::*; 25 | pub use message::*; 26 | pub use progress::*; 27 | pub use results::*; 28 | pub use symbols::*; 29 | pub use uri::*; 30 | pub use workspace::*; 31 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/progress.rs: -------------------------------------------------------------------------------- 1 | use crate::types::base::UInteger; 2 | use crate::types::message::ProgressToken; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// A work‐done progress payload, tagged by `"kind"`. 6 | #[derive(Debug, Serialize, Deserialize)] 7 | #[serde(tag = "kind")] 8 | pub enum WorkDoneProgress { 9 | /// Payload type to start a progress report. 10 | #[serde(rename = "begin")] 11 | Begin { 12 | /// Mandatory title of the progress operation, used to briefly inform about the type of 13 | /// operation being performed. 14 | /// 15 | /// Examples: "Indexing" or "Linking dependencies". 16 | title: String, 17 | 18 | /// Controls if a cancel button should show to allow the user to cancel the 19 | /// long running operation. Clients that don't support cancellation are 20 | /// allowed to ignore the setting. 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | cancellable: Option, 23 | 24 | /// Optional, more detailed associated progress message. Contains 25 | /// complementary information to the `title`. 26 | /// 27 | /// Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". 28 | /// If unset, the previous progress message (if any) is still valid. 29 | #[serde(skip_serializing_if = "Option::is_none")] 30 | message: Option, 31 | 32 | /// Optional progress percentage to display (value 100 is considered 100%). 33 | /// If not provided infinite progress is assumed and clients are allowed 34 | /// to ignore the `percentage` value in subsequent report notifications. 35 | /// 36 | /// The value should be steadily rising. Clients are free to ignore values 37 | /// that are not following this rule. The value range is [0, 100]. 38 | #[serde(skip_serializing_if = "Option::is_none")] 39 | percentage: Option, 40 | }, 41 | 42 | /// Payload type to report intermediate progress. 43 | #[serde(rename = "report")] 44 | Report { 45 | /// Updated cancel-button state. 46 | /// 47 | 48 | /// Controls enablement state of a cancel button. This property is only valid 49 | /// if a cancel button got requested in the `WorkDoneProgressBegin` payload. 50 | /// 51 | /// Clients that don't support cancellation or don't support control the 52 | /// button's enablement state are allowed to ignore the setting. 53 | #[serde(skip_serializing_if = "Option::is_none")] 54 | cancellable: Option, 55 | 56 | /// Optional, more detailed associated progress message. Contains 57 | /// complementary information to the `title`. 58 | /// 59 | /// Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". 60 | /// If unset, the previous progress message (if any) is still valid. 61 | #[serde(skip_serializing_if = "Option::is_none")] 62 | message: Option, 63 | 64 | /// Optional progress percentage to display (value 100 is considered 100%). 65 | /// If not provided infinite progress is assumed and clients are allowed 66 | /// to ignore the `percentage` value in subsequent report notifications. 67 | /// 68 | /// The value should be steadily rising. Clients are free to ignore values 69 | /// that are not following this rule. The value range is [0, 100]. 70 | #[serde(skip_serializing_if = "Option::is_none")] 71 | percentage: Option, 72 | }, 73 | 74 | /// Payload type to end a progress report. 75 | #[serde(rename = "end")] 76 | End { 77 | /// Optional, a final message indicating to for example indicate the outcome 78 | /// of the operation. 79 | #[serde(skip_serializing_if = "Option::is_none")] 80 | message: Option, 81 | }, 82 | } 83 | 84 | /// Mixin you embed in *request* parameter structs when the client wants 85 | /// to receive progress updates for that request. 86 | /// 87 | /// If `work_done_token` is `Some`, the client is telling you: 88 | /// “Here’s a token—feel free to send `$\/progress` notifications 89 | /// tagged with this value while you work on my request.” 90 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 91 | pub struct WorkDoneProgressParams { 92 | /// An optional token that a server can use to report work done progress. 93 | #[serde(rename = "workDoneToken", skip_serializing_if = "Option::is_none")] 94 | token: Option, 95 | } 96 | 97 | /// Server capability option signalling that the server supports 98 | /// sending `$\/progress` notifications for this feature. 99 | /// 100 | /// When you set this to `Some(true)` in your `InitializeResult.capabilities` 101 | /// (e.g. under `referencesProvider`), clients will know they can 102 | /// attach a `workDoneToken` and expect progress updates. 103 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 104 | pub struct WorkDoneProgressOptions { 105 | /// If `true`, the server will send `$\/progress` notifications 106 | /// for client-initiated tokens on this feature. 107 | #[serde(rename = "workDoneProgress", skip_serializing_if = "Option::is_none")] 108 | pub work_done_progress: Option, 109 | } 110 | 111 | /// Parameters for the `window/workDoneProgress/create` request. 112 | /// 113 | /// Servers call this to get a fresh token they can use *outside* of 114 | /// any particular request (e.g. for background re‐indexing). 115 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 116 | pub struct WorkDoneProgressCreateParams { 117 | /// A new token to be used exactly once: one `Begin`, zero or more `Report`, 118 | /// and one `End` notification against it. 119 | pub token: ProgressToken, 120 | } 121 | 122 | /// Mixin for *streaming partial results* in requests like `textDocument/references`. 123 | /// 124 | /// If `partial_result_token` is `Some`, the server may send 125 | /// `$\/progress` notifications whose `value` payloads are chunks of 126 | /// the final result array. 127 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 128 | pub struct PartialResultParams { 129 | /// An optional token that a server can use to report partial results (e.g. 130 | /// streaming) to the client. 131 | #[serde(rename = "partialResultToken", skip_serializing_if = "Option::is_none")] 132 | pub partial_result_token: Option, 133 | } 134 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/results.rs: -------------------------------------------------------------------------------- 1 | use crate::types::uri::DocumentUri; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// A previous result id in a workspace pull request 5 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 6 | #[serde(rename_all = "camelCase")] 7 | pub struct PreviousResultId { 8 | /// The URI for which the client knows a result ID 9 | pub uri: DocumentUri, 10 | /// The value of the previous result Id 11 | pub value: String, 12 | } 13 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/symbols.rs: -------------------------------------------------------------------------------- 1 | use crate::types::UInteger; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | pub type SymbolKind = UInteger; 5 | pub mod symbol_kind { 6 | use super::SymbolKind; 7 | pub const FILE: SymbolKind = 1; 8 | pub const MODULE: SymbolKind = 2; 9 | pub const NAMESPACE: SymbolKind = 3; 10 | pub const PACKAGE: SymbolKind = 4; 11 | pub const CLASS: SymbolKind = 5; 12 | pub const METHOD: SymbolKind = 6; 13 | pub const PROPERTY: SymbolKind = 7; 14 | pub const FIELD: SymbolKind = 8; 15 | pub const CONSTRUCTOR: SymbolKind = 9; 16 | pub const ENUM: SymbolKind = 10; 17 | pub const INTERFACE: SymbolKind = 11; 18 | pub const FUNCTION: SymbolKind = 12; 19 | pub const VARIABLE: SymbolKind = 13; 20 | pub const CONSTANT: SymbolKind = 14; 21 | pub const STRING: SymbolKind = 15; 22 | pub const NUMBER: SymbolKind = 16; 23 | pub const BOOLEAN: SymbolKind = 17; 24 | pub const ARRAY: SymbolKind = 18; 25 | pub const OBJECT: SymbolKind = 19; 26 | pub const KEY: SymbolKind = 20; 27 | pub const NULL: SymbolKind = 21; 28 | pub const ENUM_MEMBER: SymbolKind = 22; 29 | pub const STRUCT: SymbolKind = 23; 30 | pub const EVENT: SymbolKind = 24; 31 | pub const OPERATOR: SymbolKind = 25; 32 | pub const TYPE_PARAMETER: SymbolKind = 26; 33 | } 34 | 35 | pub type SymbolTag = UInteger; 36 | pub mod symbol_tag { 37 | use super::SymbolTag; 38 | /// Render a symbol as obsolete, usually using a strike-out. 39 | pub const DEPRECATED: SymbolTag = 1; 40 | } 41 | 42 | /// Struct for represeting `SymbolKind` within other structs 43 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 44 | #[serde(rename_all = "camelCase")] 45 | pub struct SymbolKindCapabilities { 46 | /// The symbol kind values the client supports. When this 47 | /// property exists the client also guarantees that it will 48 | /// handle values outside its set gracefully and falls back 49 | /// to a default value when unknown. 50 | /// 51 | /// If this property is not present the client only supports 52 | /// the symbol kinds from `File` to `Array` as defined in 53 | /// the initial version of the protocol. 54 | #[serde(skip_serializing_if = "Option::is_none")] 55 | pub value_set: Option>, 56 | } 57 | 58 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 59 | #[serde(rename_all = "camelCase")] 60 | pub struct TagSupport { 61 | /// The tags supported by the client. 62 | pub value_set: Vec, 63 | } 64 | 65 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 66 | #[serde(rename_all = "camelCase")] 67 | pub struct ResolveSupport { 68 | /// The properties the client can resolve lazily. Usually location.range 69 | pub properties: Vec, 70 | } 71 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/uri/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Debug, Clone, Error)] 4 | #[error("{msg}")] 5 | pub struct UriError { 6 | pub msg: String, 7 | } 8 | 9 | impl UriError { 10 | pub fn new(msg: String) -> Self { 11 | UriError { msg } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lsp-monkey/src/types/workspace.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{ 2 | annotations::ChangeAnnotationSupport, 3 | base::LSPAny, 4 | filesystem::FileSystemWatcher, 5 | kinds::{FailureHandlingKind, ResourceOperationKind}, 6 | progress::{PartialResultParams, WorkDoneProgressParams}, 7 | results::PreviousResultId, 8 | symbols::{ResolveSupport, SymbolKindCapabilities, TagSupport}, 9 | uri::DocumentUri, 10 | }; 11 | use serde::{Deserialize, Serialize}; 12 | 13 | /// A workspace folder as returned in the initialize request 14 | /// or workspace/didChangeWorkspaceFolders notification. 15 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 16 | pub struct WorkspaceFolder { 17 | /// The associated URI for this workspace folder. 18 | pub uri: DocumentUri, 19 | 20 | /// The name of the workspace folder. 21 | /// Used to refer to this workspace folder in the user interface. 22 | pub name: String, 23 | } 24 | /// A list of `WorkspaceFolder`s, as used in InitializeParams.workspace_folders` 25 | pub type WorkspaceFolders = Vec; 26 | 27 | /// The parameters of the workspace/didChangeClientCapabilities notification 28 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 29 | #[serde(rename_all = "camelCase")] 30 | pub struct DidChangeConfigurationClientCapabilities { 31 | /// Did change configuration notification supports dynamic registration. 32 | dynamic_registration: Option, 33 | } 34 | 35 | /// Notification for workspace/didChangeConfiguration 36 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 37 | #[serde(rename_all = "camelCase")] 38 | pub struct DidChangeConfigurationParams { 39 | /// The actual changed settings 40 | pub settings: LSPAny, 41 | } 42 | 43 | /// Did change watched files notification 44 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 45 | #[serde(rename_all = "camelCase")] 46 | pub struct DidChangeWatchedFilesRegistrationOptions { 47 | /// The watchers to register 48 | pub watchers: Vec, 49 | } 50 | 51 | /// Workspace‐specific client capabilities as defined by the Language Server Protocol. 52 | /// 53 | /// This struct corresponds to the optional `workspace` property on the top‐level 54 | /// [`ClientCapabilities`](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_capabilities) 55 | /// object. It allows a client to declare which workspace features it supports: 56 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 57 | #[serde(rename_all = "camelCase")] 58 | pub struct WorkspaceClientCapabilities { 59 | /// The client supports applying batch edits to 60 | /// the workspace by supporting the request 'workspace/applyEdit' 61 | #[serde(skip_serializing_if = "Option::is_none")] 62 | pub apply_edit: Option, 63 | 64 | /// Capabilities specific to `WorkspaceEdit`s 65 | #[serde(skip_serializing_if = "Option::is_none")] 66 | pub workspace_edit: Option, 67 | 68 | /// Capabilities specific to the `workspace/didChangeConfiguration` notification 69 | #[serde(skip_serializing_if = "Option::is_none")] 70 | pub did_change_configuration: Option, 71 | 72 | // Capabilities specific to the `workspace/didChangeWatchedFiles` notification 73 | #[serde(skip_serializing_if = "Option::is_none")] 74 | pub did_change_watched_files: Option, 75 | 76 | /// Capabilities specific to the `workspace/symbol` request 77 | #[serde(skip_serializing_if = "Option::is_none")] 78 | pub symbol: Option, 79 | 80 | /// Capabilities specific to the `workspace/executeCommand` request 81 | #[serde(skip_serializing_if = "Option::is_none")] 82 | pub execute_command: Option, 83 | 84 | /// The client has support for workspace folders 85 | #[serde(skip_serializing_if = "Option::is_none")] 86 | pub workspace_folders: Option, 87 | 88 | /// the client supports workspace/configuration requests 89 | #[serde(skip_serializing_if = "Option::is_none")] 90 | pub configuration: Option, 91 | 92 | /// Capabilities specific to the semantic token requests scoped to the workspace 93 | #[serde(skip_serializing_if = "Option::is_none")] 94 | pub semantic_tokens: Option, 95 | 96 | /// Capabilities specific to the code lens requests scoped to the workspace 97 | #[serde(skip_serializing_if = "Option::is_none")] 98 | pub code_lens: Option, 99 | } 100 | 101 | /// WorkspaceEditClientCapabilities defines capabilities specific to workspace edits. 102 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 103 | pub struct WorkspaceEditClientCapabilities { 104 | /// The client supports versioned document changes in `WorkspaceEdit`s 105 | 106 | #[serde(rename = "documentChanges", skip_serializing_if = "Option::is_none")] 107 | pub document_changes: Option, 108 | 109 | /// The resource operations the client supports. Clients should at least 110 | /// support 'create', 'rename' and 'delete' files and folders. 111 | #[serde(rename = "resourceOperations", skip_serializing_if = "Option::is_none")] 112 | pub resource_operations: Option>, 113 | 114 | /// The failure handling strategy of a client if applying the workspace edit 115 | /// fails. 116 | #[serde(rename = "failureHandling", skip_serializing_if = "Option::is_none")] 117 | pub failure_handling: Option, 118 | 119 | /// Whether the client normalizes line endings to the client specific setting. 120 | /// If set to `true` the client will normalize line ending characters 121 | /// in a workspace edit to the client specific new line character(s). 122 | #[serde( 123 | rename = "normalizesLineEndings", 124 | skip_serializing_if = "Option::is_none" 125 | )] 126 | pub normalizes_line_endings: Option, 127 | 128 | /// Whether the client in general supports change annotations on text edits, 129 | /// create file, rename file and delete file changes. 130 | #[serde( 131 | rename = "changeAnnotationSupport", 132 | skip_serializing_if = "Option::is_none" 133 | )] 134 | pub change_annotation_support: Option, 135 | } 136 | 137 | /// Parameters of the workspace diagnostic request 138 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 139 | #[serde(rename_all = "camelCase")] 140 | pub struct WorkspaceDiagnosticParams { 141 | /// WorkDoneProgress token 142 | #[serde(flatten)] 143 | pub work_done: WorkDoneProgressParams, 144 | 145 | /// Partial result token 146 | #[serde(flatten)] 147 | pub partial_result: PartialResultParams, 148 | 149 | /// The additional identifier provided during registration 150 | #[serde(skip_serializing_if = "Option::is_none")] 151 | pub identifier: Option, 152 | 153 | /// The currently known diagnostic reports with their prevous result ids 154 | prevous_result_ids: Vec, 155 | } 156 | 157 | /// Parameters of the workspace symbols request 158 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 159 | #[serde(rename_all = "camelCase")] 160 | pub struct WorkspaceSymbolClientCapabilities { 161 | /// Symbol request supports dynamic registration. 162 | #[serde(skip_serializing_if = "Option::is_none")] 163 | pub dynamic_registration: Option, 164 | 165 | /// Specific capabilities for the `SymbolKind` in the `workspace/symbol` request 166 | #[serde(skip_serializing_if = "Option::is_none")] 167 | pub symbol_kind: Option, 168 | 169 | /// The client supports tags on `SymbolInformation` and `WorkspaceSymbol`. 170 | /// Clients supporting tags have to handle unknown tags gracefully. 171 | #[serde(skip_serializing_if = "Option::is_none")] 172 | pub tag_support: Option, 173 | 174 | /// The client support partial workspace symbols. The client will send the 175 | /// request `workspaceSymbol/resolve` to the server to resolve additional 176 | /// properties. 177 | #[serde(skip_serializing_if = "Option::is_none")] 178 | pub resolve_support: Option, 179 | } 180 | 181 | /// Capabilities specific to the `workspace/executeCommand` request. 182 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 183 | #[serde(rename_all = "camelCase")] 184 | pub struct ExecuteCommandClientCapabilities { 185 | /// Execute command supports dynamic registration 186 | #[serde(skip_serializing_if = "Option::is_none")] 187 | pub dynamic_registration: Option, 188 | } 189 | 190 | /// Capabilities specific to the semantic token requests scoped to the workspace 191 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 192 | #[serde(rename_all = "camelCase")] 193 | pub struct SemanticTokensWorkspaceClientCapabilities { 194 | /// Whether the client implementation supports a refresh request sent from 195 | /// the server to the client. 196 | /// 197 | /// Note that this event is global and will force the client to refresh all 198 | /// semantic tokens currently shown. It should be used with absolute care 199 | /// and is useful for situation where a server for example detect a project 200 | /// wide change that requires such a calculation. 201 | #[serde(skip_serializing_if = "Option::is_none")] 202 | pub refresh_support: Option, 203 | } 204 | 205 | /// Capabilities specific to the code lens requests scoped to the workspace. 206 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 207 | #[serde(rename_all = "camelCase")] 208 | pub struct CodeLensWorkspaceClientCapabilities { 209 | /// Whether the client implementation supports a refresh request sent from 210 | /// the server to the client. 211 | /// 212 | /// Note that this event is global and will force the client to refresh all 213 | /// code lenses currently shown. It should be used with absolute care and is 214 | /// useful for situation where a server for example detect a project wide 215 | /// change that requires such a calculation. 216 | #[serde(skip_serializing_if = "Option::is_none")] 217 | pub refresh_support: Option, 218 | } 219 | -------------------------------------------------------------------------------- /monkey/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "utf8parse", 17 | ] 18 | 19 | [[package]] 20 | name = "anstyle" 21 | version = "1.0.4" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" 24 | 25 | [[package]] 26 | name = "anstyle-parse" 27 | version = "0.2.2" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" 30 | dependencies = [ 31 | "utf8parse", 32 | ] 33 | 34 | [[package]] 35 | name = "anstyle-query" 36 | version = "1.0.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 39 | dependencies = [ 40 | "windows-sys", 41 | ] 42 | 43 | [[package]] 44 | name = "anstyle-wincon" 45 | version = "3.0.1" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" 48 | dependencies = [ 49 | "anstyle", 50 | "windows-sys", 51 | ] 52 | 53 | [[package]] 54 | name = "anyhow" 55 | version = "1.0.71" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" 58 | 59 | [[package]] 60 | name = "bumpalo" 61 | version = "3.13.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" 64 | 65 | [[package]] 66 | name = "byteorder" 67 | version = "1.5.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 70 | 71 | [[package]] 72 | name = "cfg-if" 73 | version = "1.0.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 76 | 77 | [[package]] 78 | name = "clap" 79 | version = "4.4.8" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" 82 | dependencies = [ 83 | "clap_builder", 84 | "clap_derive", 85 | ] 86 | 87 | [[package]] 88 | name = "clap_builder" 89 | version = "4.4.8" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" 92 | dependencies = [ 93 | "anstream", 94 | "anstyle", 95 | "clap_lex", 96 | "strsim", 97 | ] 98 | 99 | [[package]] 100 | name = "clap_derive" 101 | version = "4.4.7" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" 104 | dependencies = [ 105 | "heck", 106 | "proc-macro2", 107 | "quote", 108 | "syn", 109 | ] 110 | 111 | [[package]] 112 | name = "clap_lex" 113 | version = "0.6.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" 116 | 117 | [[package]] 118 | name = "colorchoice" 119 | version = "1.0.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 122 | 123 | [[package]] 124 | name = "heck" 125 | version = "0.4.1" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 128 | 129 | [[package]] 130 | name = "libc" 131 | version = "0.2.153" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 134 | 135 | [[package]] 136 | name = "log" 137 | version = "0.4.19" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" 140 | 141 | [[package]] 142 | name = "monkey" 143 | version = "0.1.0" 144 | dependencies = [ 145 | "anyhow", 146 | "byteorder", 147 | "clap", 148 | "signal-hook", 149 | "strum", 150 | "strum_macros", 151 | "thiserror", 152 | "wasm-bindgen", 153 | ] 154 | 155 | [[package]] 156 | name = "once_cell" 157 | version = "1.18.0" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 160 | 161 | [[package]] 162 | name = "proc-macro2" 163 | version = "1.0.66" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 166 | dependencies = [ 167 | "unicode-ident", 168 | ] 169 | 170 | [[package]] 171 | name = "quote" 172 | version = "1.0.32" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" 175 | dependencies = [ 176 | "proc-macro2", 177 | ] 178 | 179 | [[package]] 180 | name = "rustversion" 181 | version = "1.0.14" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 184 | 185 | [[package]] 186 | name = "signal-hook" 187 | version = "0.3.17" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 190 | dependencies = [ 191 | "libc", 192 | "signal-hook-registry", 193 | ] 194 | 195 | [[package]] 196 | name = "signal-hook-registry" 197 | version = "1.4.1" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 200 | dependencies = [ 201 | "libc", 202 | ] 203 | 204 | [[package]] 205 | name = "strsim" 206 | version = "0.10.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 209 | 210 | [[package]] 211 | name = "strum" 212 | version = "0.26.2" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" 215 | 216 | [[package]] 217 | name = "strum_macros" 218 | version = "0.26.2" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" 221 | dependencies = [ 222 | "heck", 223 | "proc-macro2", 224 | "quote", 225 | "rustversion", 226 | "syn", 227 | ] 228 | 229 | [[package]] 230 | name = "syn" 231 | version = "2.0.28" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" 234 | dependencies = [ 235 | "proc-macro2", 236 | "quote", 237 | "unicode-ident", 238 | ] 239 | 240 | [[package]] 241 | name = "thiserror" 242 | version = "1.0.46" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "d9207952ae1a003f42d3d5e892dac3c6ba42aa6ac0c79a6a91a2b5cb4253e75c" 245 | dependencies = [ 246 | "thiserror-impl", 247 | ] 248 | 249 | [[package]] 250 | name = "thiserror-impl" 251 | version = "1.0.46" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "f1728216d3244de4f14f14f8c15c79be1a7c67867d28d69b719690e2a19fb445" 254 | dependencies = [ 255 | "proc-macro2", 256 | "quote", 257 | "syn", 258 | ] 259 | 260 | [[package]] 261 | name = "unicode-ident" 262 | version = "1.0.11" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 265 | 266 | [[package]] 267 | name = "utf8parse" 268 | version = "0.2.1" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 271 | 272 | [[package]] 273 | name = "wasm-bindgen" 274 | version = "0.2.100" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 277 | dependencies = [ 278 | "cfg-if", 279 | "once_cell", 280 | "rustversion", 281 | "wasm-bindgen-macro", 282 | ] 283 | 284 | [[package]] 285 | name = "wasm-bindgen-backend" 286 | version = "0.2.100" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 289 | dependencies = [ 290 | "bumpalo", 291 | "log", 292 | "proc-macro2", 293 | "quote", 294 | "syn", 295 | "wasm-bindgen-shared", 296 | ] 297 | 298 | [[package]] 299 | name = "wasm-bindgen-macro" 300 | version = "0.2.100" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 303 | dependencies = [ 304 | "quote", 305 | "wasm-bindgen-macro-support", 306 | ] 307 | 308 | [[package]] 309 | name = "wasm-bindgen-macro-support" 310 | version = "0.2.100" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 313 | dependencies = [ 314 | "proc-macro2", 315 | "quote", 316 | "syn", 317 | "wasm-bindgen-backend", 318 | "wasm-bindgen-shared", 319 | ] 320 | 321 | [[package]] 322 | name = "wasm-bindgen-shared" 323 | version = "0.2.100" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 326 | dependencies = [ 327 | "unicode-ident", 328 | ] 329 | 330 | [[package]] 331 | name = "windows-sys" 332 | version = "0.48.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 335 | dependencies = [ 336 | "windows-targets", 337 | ] 338 | 339 | [[package]] 340 | name = "windows-targets" 341 | version = "0.48.5" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 344 | dependencies = [ 345 | "windows_aarch64_gnullvm", 346 | "windows_aarch64_msvc", 347 | "windows_i686_gnu", 348 | "windows_i686_msvc", 349 | "windows_x86_64_gnu", 350 | "windows_x86_64_gnullvm", 351 | "windows_x86_64_msvc", 352 | ] 353 | 354 | [[package]] 355 | name = "windows_aarch64_gnullvm" 356 | version = "0.48.5" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 359 | 360 | [[package]] 361 | name = "windows_aarch64_msvc" 362 | version = "0.48.5" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 365 | 366 | [[package]] 367 | name = "windows_i686_gnu" 368 | version = "0.48.5" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 371 | 372 | [[package]] 373 | name = "windows_i686_msvc" 374 | version = "0.48.5" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 377 | 378 | [[package]] 379 | name = "windows_x86_64_gnu" 380 | version = "0.48.5" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 383 | 384 | [[package]] 385 | name = "windows_x86_64_gnullvm" 386 | version = "0.48.5" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 389 | 390 | [[package]] 391 | name = "windows_x86_64_msvc" 392 | version = "0.48.5" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 395 | -------------------------------------------------------------------------------- /monkey/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "monkey" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.71" 10 | thiserror = "1.0.46" 11 | wasm-bindgen = "0.2.87" 12 | clap = { version = "4.3.1", features = ["derive", "cargo"] } 13 | byteorder = "1.5.0" 14 | strum_macros = "0.26.2" 15 | strum = "0.26.2" 16 | signal-hook = "0.3.17" 17 | 18 | 19 | [lib] 20 | path = "src/lib.rs" 21 | crate-type = ["cdylib", "rlib"] 22 | 23 | -------------------------------------------------------------------------------- /monkey/examples/fibonacci.monkey: -------------------------------------------------------------------------------- 1 | let fibonacci = fn(x) { 2 | if (x == 0) { 3 | return 0; 4 | } else { 5 | if (x == 1) { 6 | return 1; 7 | } else { 8 | fibonacci(x - 1) + fibonacci(x - 2); 9 | } 10 | 11 | } 12 | }; 13 | 14 | echoln(fibonacci(25)); 15 | -------------------------------------------------------------------------------- /monkey/examples/macro.monkey: -------------------------------------------------------------------------------- 1 | let unless = macro(condition, consequence, alternative) { 2 | quote( 3 | if (!(unquote(condition))) { 4 | unquote(consequence); 5 | } else { 6 | unquote(alternative); 7 | } 8 | ); 9 | }; 10 | unless(10 > 5, echoln("not greater"), echoln("greater")); 11 | -------------------------------------------------------------------------------- /monkey/examples/test.monkey: -------------------------------------------------------------------------------- 1 | let x = 5; 2 | -------------------------------------------------------------------------------- /monkey/src/code/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Debug, Clone, Error)] 4 | #[error("{msg}")] 5 | pub struct CodeError { 6 | pub msg: String, 7 | } 8 | 9 | impl CodeError { 10 | pub fn new(msg: String) -> Self { 11 | CodeError { msg } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /monkey/src/code/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | use std::ops::{Index, IndexMut}; 3 | 4 | use std::fmt::{Debug, Display}; 5 | 6 | #[derive(Debug, Clone, Copy, PartialEq)] 7 | #[repr(u8)] 8 | pub enum Opcode { 9 | Constant, 10 | Add, 11 | Pop, 12 | Sub, 13 | Mul, 14 | Div, 15 | True, 16 | False, 17 | Equal, 18 | NotEqual, 19 | GreaterThan, 20 | Minus, 21 | Bang, 22 | JumpNotTruthy, 23 | Jump, 24 | Null, 25 | GetGlobal, 26 | SetGlobal, 27 | Array, 28 | Hash, 29 | Index, 30 | Call, 31 | ReturnValue, 32 | Return, 33 | GetLocal, 34 | SetLocal, 35 | GetBuiltin, 36 | Closure, 37 | GetFree, 38 | CurrentClosure, 39 | } 40 | impl From for Opcode { 41 | fn from(op: u8) -> Opcode { 42 | match op { 43 | 0 => Opcode::Constant, 44 | 1 => Opcode::Add, 45 | 2 => Opcode::Pop, 46 | 3 => Opcode::Sub, 47 | 4 => Opcode::Mul, 48 | 5 => Opcode::Div, 49 | 6 => Opcode::True, 50 | 7 => Opcode::False, 51 | 8 => Opcode::Equal, 52 | 9 => Opcode::NotEqual, 53 | 10 => Opcode::GreaterThan, 54 | 11 => Opcode::Minus, 55 | 12 => Opcode::Bang, 56 | 13 => Opcode::JumpNotTruthy, 57 | 14 => Opcode::Jump, 58 | 15 => Opcode::Null, 59 | 16 => Opcode::GetGlobal, 60 | 17 => Opcode::SetGlobal, 61 | 18 => Opcode::Array, 62 | 19 => Opcode::Hash, 63 | 20 => Opcode::Index, 64 | 21 => Opcode::Call, 65 | 22 => Opcode::ReturnValue, 66 | 23 => Opcode::Return, 67 | 24 => Opcode::GetLocal, 68 | 25 => Opcode::SetLocal, 69 | 26 => Opcode::GetBuiltin, 70 | 27 => Opcode::Closure, 71 | 28 => Opcode::GetFree, 72 | 29 => Opcode::CurrentClosure, 73 | _ => panic!("unknown opcode"), 74 | } 75 | } 76 | } 77 | 78 | impl Into for Vec { 79 | fn into(self) -> Instructions { 80 | Instructions::new(self) 81 | } 82 | } 83 | 84 | pub struct Definition { 85 | name: &'static str, 86 | operand_widths: Vec, 87 | } 88 | 89 | #[derive(Clone, PartialEq, Eq)] 90 | pub struct Instructions(pub Vec); 91 | 92 | impl Index for Instructions { 93 | type Output = u8; 94 | 95 | fn index(&self, index: usize) -> &Self::Output { 96 | &self.0[index] 97 | } 98 | } 99 | 100 | impl IndexMut for Instructions { 101 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 102 | &mut self.0[index] 103 | } 104 | } 105 | 106 | impl FromIterator for Instructions { 107 | fn from_iter>(iter: I) -> Self { 108 | Instructions(iter.into_iter().collect()) 109 | } 110 | } 111 | 112 | impl Instructions { 113 | pub fn new(bytes: Vec) -> Self { 114 | Instructions(bytes) 115 | } 116 | 117 | pub fn len(&self) -> usize { 118 | self.0.len() 119 | } 120 | 121 | pub fn iter(&self) -> std::slice::Iter { 122 | self.0.iter() 123 | } 124 | 125 | pub fn write(&mut self, bytes: Vec) { 126 | self.0.extend(bytes); 127 | } 128 | 129 | pub fn extend(&mut self, instructions: Instructions) { 130 | self.0.extend(instructions.0); 131 | } 132 | 133 | pub fn as_slice(&self) -> &[u8] { 134 | &self.0 135 | } 136 | 137 | pub fn slice(&self, start: usize, end: usize) -> Vec { 138 | self.0[start..end].to_vec() 139 | } 140 | 141 | pub fn slice_from(&self, start: usize, end: usize) -> Vec { 142 | self.0[start..end].to_vec() 143 | } 144 | } 145 | 146 | impl Opcode { 147 | pub fn name(&self) -> &str { 148 | match self { 149 | Opcode::Constant => "OpConstant", 150 | Opcode::Add => "OpAdd", 151 | Opcode::Pop => "OpPop", 152 | Opcode::Sub => "OpSub", 153 | Opcode::Mul => "OpMul", 154 | Opcode::Div => "OpDiv", 155 | Opcode::True => "OpTrue", 156 | Opcode::False => "OpFalse", 157 | Opcode::Equal => "OpEqual", 158 | Opcode::NotEqual => "OpNotEqual", 159 | Opcode::GreaterThan => "OpGreaterThan", 160 | Opcode::Minus => "OpMinus", 161 | Opcode::Bang => "OpBang", 162 | Opcode::JumpNotTruthy => "OpJumpNotTruthy", 163 | Opcode::Jump => "OpJump", 164 | Opcode::Null => "OpNull", 165 | Opcode::GetGlobal => "OpGetGlobal", 166 | Opcode::SetGlobal => "OpSetGlobal", 167 | Opcode::Array => "OpArray", 168 | Opcode::Hash => "OpHash", 169 | Opcode::Index => "OpIndex", 170 | Opcode::Call => "OpCall", 171 | Opcode::ReturnValue => "OpReturnValue", 172 | Opcode::Return => "OpReturn", 173 | Opcode::GetLocal => "OpGetLocal", 174 | Opcode::SetLocal => "OpSetLocal", 175 | Opcode::GetBuiltin => "OpGetBuiltin", 176 | Opcode::Closure => "OpClosure", 177 | Opcode::GetFree => "OpGetFree", 178 | Opcode::CurrentClosure => "OpCurrentClosure", 179 | } 180 | } 181 | 182 | pub fn operand_widths(&self) -> Vec { 183 | match self { 184 | Opcode::Constant => vec![2], 185 | Opcode::Add => vec![], 186 | Opcode::Pop => vec![], 187 | Opcode::Sub => vec![], 188 | Opcode::Mul => vec![], 189 | Opcode::Div => vec![], 190 | Opcode::True => vec![], 191 | Opcode::False => vec![], 192 | Opcode::Equal => vec![], 193 | Opcode::NotEqual => vec![], 194 | Opcode::GreaterThan => vec![], 195 | Opcode::Minus => vec![], 196 | Opcode::Bang => vec![], 197 | Opcode::JumpNotTruthy => vec![2], 198 | Opcode::Jump => vec![2], 199 | Opcode::Null => vec![], 200 | Opcode::GetGlobal => vec![2], 201 | Opcode::SetGlobal => vec![2], 202 | Opcode::Array => vec![2], 203 | Opcode::Hash => vec![2], 204 | Opcode::Index => vec![], 205 | Opcode::Call => vec![1], 206 | Opcode::ReturnValue => vec![], 207 | Opcode::Return => vec![], 208 | Opcode::GetLocal => vec![1], 209 | Opcode::SetLocal => vec![1], 210 | Opcode::GetBuiltin => vec![1], 211 | Opcode::Closure => vec![2, 1], 212 | Opcode::GetFree => vec![1], 213 | Opcode::CurrentClosure => vec![], 214 | } 215 | } 216 | } 217 | 218 | pub fn lookup(op: u8) -> Option { 219 | match op { 220 | 0 => Some(Definition { 221 | name: "OpConstant", 222 | operand_widths: vec![2], 223 | }), 224 | 225 | 1 => Some(Definition { 226 | name: "OpAdd", 227 | operand_widths: vec![], 228 | }), 229 | 230 | 2 => Some(Definition { 231 | name: "OpPop", 232 | operand_widths: vec![], 233 | }), 234 | 235 | 3 => Some(Definition { 236 | name: "OpSub", 237 | operand_widths: vec![], 238 | }), 239 | 240 | 4 => Some(Definition { 241 | name: "OpMul", 242 | operand_widths: vec![], 243 | }), 244 | 245 | 5 => Some(Definition { 246 | name: "OpDiv", 247 | operand_widths: vec![], 248 | }), 249 | 250 | 6 => Some(Definition { 251 | name: "OpTrue", 252 | operand_widths: vec![], 253 | }), 254 | 255 | 7 => Some(Definition { 256 | name: "OpFalse", 257 | operand_widths: vec![], 258 | }), 259 | 260 | 8 => Some(Definition { 261 | name: "OpEqual", 262 | operand_widths: vec![], 263 | }), 264 | 265 | 9 => Some(Definition { 266 | name: "OpNotEqual", 267 | operand_widths: vec![], 268 | }), 269 | 270 | 10 => Some(Definition { 271 | name: "OpGreaterThan", 272 | operand_widths: vec![], 273 | }), 274 | 275 | 11 => Some(Definition { 276 | name: "OpMinus", 277 | operand_widths: vec![], 278 | }), 279 | 280 | 12 => Some(Definition { 281 | name: "OpBang", 282 | operand_widths: vec![], 283 | }), 284 | 285 | 13 => Some(Definition { 286 | name: "OpJumpNotTruthy", 287 | operand_widths: vec![2], 288 | }), 289 | 290 | 14 => Some(Definition { 291 | name: "OpJump", 292 | operand_widths: vec![2], 293 | }), 294 | 295 | 15 => Some(Definition { 296 | name: "OpNull", 297 | operand_widths: vec![], 298 | }), 299 | 300 | 16 => Some(Definition { 301 | name: "OpGetGlobal", 302 | operand_widths: vec![2], 303 | }), 304 | 305 | 17 => Some(Definition { 306 | name: "OpSetGlobal", 307 | operand_widths: vec![2], 308 | }), 309 | 310 | 18 => Some(Definition { 311 | name: "OpArray", 312 | operand_widths: vec![2], 313 | }), 314 | 315 | 19 => Some(Definition { 316 | name: "OpHash", 317 | operand_widths: vec![2], 318 | }), 319 | 320 | 20 => Some(Definition { 321 | name: "OpIndex", 322 | operand_widths: vec![], 323 | }), 324 | 325 | 21 => Some(Definition { 326 | name: "OpCall", 327 | operand_widths: vec![1], 328 | }), 329 | 330 | 22 => Some(Definition { 331 | name: "OpReturnValue", 332 | operand_widths: vec![], 333 | }), 334 | 335 | 23 => Some(Definition { 336 | name: "OpReturn", 337 | operand_widths: vec![], 338 | }), 339 | 340 | 24 => Some(Definition { 341 | name: "OpGetLocal", 342 | operand_widths: vec![1], 343 | }), 344 | 345 | 25 => Some(Definition { 346 | name: "OpSetLocal", 347 | operand_widths: vec![1], 348 | }), 349 | 350 | 26 => Some(Definition { 351 | name: "OpGetBuiltin", 352 | operand_widths: vec![1], 353 | }), 354 | 355 | 27 => Some(Definition { 356 | name: "OpClosure", 357 | operand_widths: vec![2, 1], 358 | }), 359 | 360 | 28 => Some(Definition { 361 | name: "OpGetFree", 362 | operand_widths: vec![1], 363 | }), 364 | 365 | _ => None, 366 | } 367 | } 368 | 369 | pub fn format_instruction(def: &Definition, operands: &Vec) -> String { 370 | let operand_count = def.operand_widths.len(); 371 | if operands.len() != operand_count { 372 | return format!( 373 | "ERROR: operand len {} does not match defined {}\n", 374 | operands.len(), 375 | operand_count 376 | ) 377 | .to_string(); 378 | } 379 | match operand_count { 380 | 0 => return def.name.to_string(), 381 | 1 => return format!("{} {}", def.name, operands[0]).to_string(), 382 | 2 => return format!("{} {} {}", def.name, operands[0], operands[1]).to_string(), 383 | _ => return format!("ERROR: unhandled operand_count for {}\n", def.name), 384 | } 385 | } 386 | 387 | impl Display for Instructions { 388 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 389 | let mut i = 0; 390 | while i < self.0.len() { 391 | let definition = lookup(self.0[i]); 392 | if definition.is_none() { 393 | write!(f, "ERROR: undefined opcode {}", self.0[i])?; 394 | continue; 395 | } 396 | let def = definition.unwrap(); 397 | let (operands, n) = read_operands(&def, &self.0[i + 1..]); 398 | let _ = write!(f, "{:04} {}\n", i, format_instruction(&def, &operands)); 399 | i += n + 1; 400 | } 401 | Ok(()) 402 | } 403 | } 404 | 405 | impl Debug for Instructions { 406 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 407 | let mut i = 0; 408 | write!(f, "\n")?; 409 | while i < self.0.len() { 410 | let definition = lookup(self.0[i]); 411 | if definition.is_none() { 412 | write!(f, "ERROR: undefined opcode {}", self.0[i])?; 413 | continue; 414 | } 415 | let def = definition.unwrap(); 416 | let (operands, n) = read_operands(&def, &self.0[i + 1..]); 417 | let _ = write!(f, "{:04} {}\n", i, format_instruction(&def, &operands)); 418 | i += n + 1; 419 | } 420 | Ok(()) 421 | } 422 | } 423 | 424 | pub fn make(op: Opcode, operands: Vec) -> Vec { 425 | let length: usize = (op.operand_widths().iter().sum::()) + 1; 426 | let mut instructions = vec![0; length]; 427 | instructions[0] = op as u8; 428 | 429 | let mut offset = 1; 430 | for (i, &o) in operands.iter().enumerate() { 431 | let width = op.operand_widths()[i]; 432 | match width { 433 | 1 => instructions[offset] = o as u8, 434 | 2 => { 435 | let bytes = (o as u16).to_be_bytes(); 436 | instructions[offset] = bytes[0]; 437 | instructions[offset + 1] = bytes[1]; 438 | } 439 | _ => panic!("invalid operand width"), 440 | } 441 | offset += width; 442 | } 443 | instructions 444 | } 445 | 446 | pub fn read_operands(def: &Definition, instructions: &[u8]) -> (Vec, usize) { 447 | let mut operands: Vec = Vec::with_capacity(def.operand_widths.len()); 448 | let mut offset = 0; 449 | 450 | for width in def.operand_widths.iter() { 451 | match width { 452 | 1 => operands.push(instructions[offset] as usize), 453 | 2 => { 454 | let bytes = instructions[offset..offset + 2].to_vec(); 455 | operands.push(u16::from_be_bytes([bytes[0], bytes[1]]) as usize); 456 | } 457 | _ => panic!("invalid operand width"), 458 | } 459 | 460 | offset = offset + width 461 | } 462 | return (operands, offset); 463 | } 464 | 465 | pub fn read_u16(instructions: &Instructions, start: usize) -> u16 { 466 | u16::from_be_bytes([instructions[start], instructions[start + 1]]) 467 | } 468 | 469 | pub fn read_u8(instructions: &Instructions, start: usize) -> u8 { 470 | instructions[start] 471 | } 472 | 473 | #[cfg(test)] 474 | mod test { 475 | use super::*; 476 | 477 | fn check(opcode: Opcode, operands: Vec, expected: Vec) { 478 | let instruction = make(opcode, operands); 479 | assert_eq!(instruction.len(), expected.len()); 480 | for (i, b) in expected.iter().enumerate() { 481 | assert_eq!(instruction[i], *b); 482 | } 483 | } 484 | 485 | #[test] 486 | fn it_makes_correctly() { 487 | let tests = vec![ 488 | (Opcode::Constant, vec![65534], vec![0, 255, 254]), 489 | (Opcode::Add, vec![], vec![Opcode::Add as u8]), 490 | ( 491 | Opcode::GetLocal, 492 | vec![255], 493 | vec![Opcode::GetLocal as u8, 255], 494 | ), 495 | ( 496 | Opcode::Closure, 497 | vec![65534, 255], 498 | vec![Opcode::Closure as u8, 255, 254, 255], 499 | ), 500 | ]; 501 | for (opcode, operands, expected) in tests { 502 | check(opcode, operands, expected); 503 | } 504 | } 505 | 506 | #[test] 507 | fn it_reads_operands_correctly() { 508 | struct OperandTest { 509 | opcode: Opcode, 510 | operands: Vec, 511 | bytes_read: usize, 512 | } 513 | let tests = vec![ 514 | OperandTest { 515 | opcode: Opcode::Constant, 516 | operands: vec![65535], 517 | bytes_read: 2, 518 | }, 519 | OperandTest { 520 | opcode: Opcode::GetLocal, 521 | operands: vec![255], 522 | bytes_read: 1, 523 | }, 524 | OperandTest { 525 | opcode: Opcode::Closure, 526 | operands: vec![65535, 255], 527 | bytes_read: 3, 528 | }, 529 | ]; 530 | 531 | for test in tests { 532 | let instruction = make(test.opcode, test.operands.clone()); 533 | let definition = lookup(test.opcode as u8).unwrap(); 534 | let (operands_read, n) = read_operands(&definition, &instruction[1..]); 535 | 536 | if n != test.bytes_read { 537 | panic!("n wrong"); 538 | } 539 | for (i, want) in test.operands.iter().enumerate() { 540 | if operands_read[i] != *want { 541 | panic!("operand wrong. Want {}, got {}", want, operands_read[i]); 542 | } 543 | } 544 | } 545 | } 546 | 547 | #[test] 548 | fn it_prints_correctly() { 549 | let instructions = vec![ 550 | make(Opcode::Add, vec![]), 551 | make(Opcode::GetLocal, vec![1]), 552 | make(Opcode::Constant, vec![2]), 553 | make(Opcode::Constant, vec![65535]), 554 | make(Opcode::Closure, vec![65535, 255]), 555 | ]; 556 | 557 | let expected = r#"0000 OpAdd 558 | 0001 OpGetLocal 1 559 | 0003 OpConstant 2 560 | 0006 OpConstant 65535 561 | 0009 OpClosure 65535 255 562 | "#; 563 | 564 | let concattenated = instructions.into_iter().flatten().collect::(); 565 | println!("{}", concattenated); 566 | 567 | println!("{}", expected); 568 | 569 | if concattenated.to_string() != expected { 570 | panic!( 571 | "wrong length: expected {}, got {}", 572 | expected.len(), 573 | concattenated.to_string().len() 574 | ); 575 | } 576 | } 577 | } 578 | -------------------------------------------------------------------------------- /monkey/src/compiler/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Debug, Clone, Error)] 4 | #[error("{msg}")] 5 | pub struct CompileError { 6 | pub msg: String, 7 | } 8 | 9 | impl CompileError { 10 | pub fn new(msg: String) -> Self { 11 | CompileError { msg } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /monkey/src/evaluator/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::object::error::ObjectError; 4 | 5 | #[derive(Debug, Clone, Error, Eq, PartialEq)] 6 | pub enum EvaluatorError { 7 | #[error("Evaluator error: {0}")] 8 | Native(String), 9 | #[error("Object error: {0}")] 10 | Object(#[from] ObjectError), 11 | } 12 | 13 | impl EvaluatorError { 14 | pub fn new(msg: String) -> Self { 15 | EvaluatorError::Native(msg) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /monkey/src/lexer.rs: -------------------------------------------------------------------------------- 1 | use crate::token::Token; 2 | 3 | pub struct Lexer { 4 | position: usize, 5 | read_position: usize, 6 | ch: u8, 7 | input: Vec, 8 | } 9 | 10 | impl Lexer { 11 | pub fn new(input: &str) -> Lexer { 12 | let mut lex = Lexer { 13 | position: 0, 14 | read_position: 0, 15 | ch: 0, 16 | input: input.as_bytes().to_vec(), 17 | }; 18 | lex.read_char(); 19 | return lex; 20 | } 21 | 22 | pub fn next_token(&mut self) -> Token { 23 | self.skip_whitespace(); 24 | if self.ch == b'/' { 25 | match self.peek() { 26 | b'/' => { 27 | self.skip_line_comment(); 28 | return self.next_token(); 29 | } 30 | b'*' => { 31 | self.skip_block_comment(); 32 | return self.next_token(); 33 | } 34 | _ => {} 35 | } 36 | } 37 | 38 | let tok = match self.ch { 39 | b'=' => self.single_or_double(b'=', Token::Assign, Token::Eq), 40 | b'!' => self.single_or_double(b'=', Token::Bang, Token::NotEq), 41 | b';' => Token::Semicolon, 42 | b'(' => Token::Lparen, 43 | b')' => Token::Rparen, 44 | b'[' => Token::LBracket, 45 | b']' => Token::RBracket, 46 | b',' => Token::Comma, 47 | b'+' => Token::Plus, 48 | b'-' => Token::Dash, 49 | b'{' => Token::Lbrace, 50 | b'}' => Token::Rbrace, 51 | b':' => Token::Colon, 52 | b'a'..=b'z' | b'A'..=b'Z' | b'_' => { 53 | let ident = self.read_ident(); 54 | return match ident.as_str() { 55 | "fn" => Token::Function, 56 | "macro" => Token::Macro, 57 | "let" => Token::Let, 58 | "if" => Token::If, 59 | "else" => Token::Else, 60 | "return" => Token::Return, 61 | "false" => Token::False, 62 | "true" => Token::True, 63 | _ => Token::Ident(ident), 64 | }; 65 | } 66 | b'0'..=b'9' => return Token::Int(self.read_int().parse::().unwrap()), 67 | b'<' => Token::Lt, 68 | b'>' => Token::Gt, 69 | b'*' => Token::Asterisk, 70 | b'/' => Token::Slash, 71 | b'"' => Token::String(self.read_string()), 72 | 73 | 0 => Token::Eof, 74 | _ => Token::Illegal, 75 | }; 76 | 77 | self.read_char(); 78 | return tok; 79 | } 80 | 81 | fn skip_line_comment(&mut self) { 82 | self.read_char(); 83 | self.read_char(); 84 | while self.ch != b'\n' && self.ch != 0 { 85 | self.read_char(); 86 | } 87 | if self.ch != 0 { 88 | self.read_char(); 89 | } 90 | } 91 | 92 | fn skip_block_comment(&mut self) { 93 | self.read_char(); 94 | self.read_char(); 95 | let mut nesting = 1; 96 | while nesting > 0 && self.ch != 0 { 97 | if self.ch == b'/' && self.peek() == b'*' { 98 | self.read_char(); 99 | self.read_char(); 100 | nesting += 1; 101 | } else if self.ch == b'*' && self.peek() == b'/' { 102 | self.read_char(); 103 | self.read_char(); 104 | nesting -= 1; 105 | } else { 106 | self.read_char(); 107 | } 108 | } 109 | } 110 | 111 | fn read_string(&mut self) -> String { 112 | let position = self.position + 1; 113 | loop { 114 | self.read_char(); 115 | if self.ch == b'"' || self.ch == 0 { 116 | break; 117 | } 118 | } 119 | return String::from_utf8_lossy(&self.input[position..self.position]).to_string(); 120 | } 121 | 122 | fn single_or_double( 123 | &mut self, 124 | expected_next: u8, 125 | single_token: Token, 126 | double_token: Token, 127 | ) -> Token { 128 | if self.peek() == expected_next { 129 | self.read_char(); 130 | return double_token; 131 | } 132 | single_token 133 | } 134 | 135 | fn read_ident(&mut self) -> String { 136 | let position = self.position; 137 | while self.ch.is_ascii_alphanumeric() || self.ch == b'_' { 138 | self.read_char(); 139 | } 140 | return String::from_utf8_lossy(&self.input[position..self.position]).to_string(); 141 | } 142 | 143 | fn read_int(&mut self) -> String { 144 | let position = self.position; 145 | while self.ch.is_ascii_alphanumeric() { 146 | self.read_char(); 147 | } 148 | return String::from_utf8_lossy(&self.input[position..self.position]).to_string(); 149 | } 150 | 151 | fn read_char(&mut self) { 152 | if self.read_position >= self.input.len() { 153 | self.ch = 0; 154 | } else { 155 | self.ch = self.input[self.read_position]; 156 | } 157 | 158 | self.position = self.read_position; 159 | self.read_position += 1; 160 | } 161 | 162 | fn skip_whitespace(&mut self) { 163 | while self.ch.is_ascii_whitespace() { 164 | self.read_char(); 165 | } 166 | } 167 | 168 | fn peek(&mut self) -> u8 { 169 | if self.read_position >= self.input.len() { 170 | return 0; 171 | } else { 172 | return self.input[self.read_position]; 173 | } 174 | } 175 | } 176 | 177 | #[cfg(test)] 178 | mod test { 179 | use super::Lexer; 180 | use crate::token::Token; 181 | use anyhow::Result; 182 | 183 | #[test] 184 | fn it_gets_next_token_correctly() -> Result<()> { 185 | let input = "=+(){},;"; 186 | 187 | let mut lexer = Lexer::new(input.into()); 188 | 189 | let tokens = vec![ 190 | Token::Assign, 191 | Token::Plus, 192 | Token::Lparen, 193 | Token::Rparen, 194 | Token::Lbrace, 195 | Token::Rbrace, 196 | Token::Comma, 197 | Token::Semicolon, 198 | ]; 199 | 200 | for token in tokens { 201 | let next_token = lexer.next_token(); 202 | println!("expected: {:?}, got: {:?}", token, next_token); 203 | assert_eq!(token, next_token); 204 | } 205 | 206 | return Ok(()); 207 | } 208 | 209 | #[test] 210 | fn it_lexes_whole_code_blocks() -> Result<()> { 211 | let input = r#"let five = 5; 212 | let ten = 10; 213 | let add = fn(x, y) { 214 | x + y; 215 | }; 216 | let result = add(five, ten); 217 | !-/ *5; 218 | 5 < 10 > 5; 219 | if (5 < 10) { 220 | return true; 221 | } else { 222 | return false; 223 | } 224 | 225 | 10 == 10; 226 | 10 != 9; // test comment 227 | "foobar" 228 | /* 229 | * 230 | */ 231 | "foo bar" 232 | [1, 2]; 233 | {"foo": "bar"} 234 | macro(x, y) { x + y; }; 235 | "#; 236 | let mut lexer = Lexer::new(input.into()); 237 | 238 | let tokens = vec![ 239 | Token::Let, 240 | Token::Ident(String::from("five")), 241 | Token::Assign, 242 | Token::Int(5), 243 | Token::Semicolon, 244 | Token::Let, 245 | Token::Ident(String::from("ten")), 246 | Token::Assign, 247 | Token::Int(10), 248 | Token::Semicolon, 249 | Token::Let, 250 | Token::Ident(String::from("add")), 251 | Token::Assign, 252 | Token::Function, 253 | Token::Lparen, 254 | Token::Ident(String::from("x")), 255 | Token::Comma, 256 | Token::Ident(String::from("y")), 257 | Token::Rparen, 258 | Token::Lbrace, 259 | Token::Ident(String::from("x")), 260 | Token::Plus, 261 | Token::Ident(String::from("y")), 262 | Token::Semicolon, 263 | Token::Rbrace, 264 | Token::Semicolon, 265 | Token::Let, 266 | Token::Ident(String::from("result")), 267 | Token::Assign, 268 | Token::Ident(String::from("add")), 269 | Token::Lparen, 270 | Token::Ident(String::from("five")), 271 | Token::Comma, 272 | Token::Ident(String::from("ten")), 273 | Token::Rparen, 274 | Token::Semicolon, 275 | Token::Bang, 276 | Token::Dash, 277 | Token::Slash, 278 | Token::Asterisk, 279 | Token::Int(5), 280 | Token::Semicolon, 281 | Token::Int(5), 282 | Token::Lt, 283 | Token::Int(10), 284 | Token::Gt, 285 | Token::Int(5), 286 | Token::Semicolon, 287 | Token::If, 288 | Token::Lparen, 289 | Token::Int(5), 290 | Token::Lt, 291 | Token::Int(10), 292 | Token::Rparen, 293 | Token::Lbrace, 294 | Token::Return, 295 | Token::True, 296 | Token::Semicolon, 297 | Token::Rbrace, 298 | Token::Else, 299 | Token::Lbrace, 300 | Token::Return, 301 | Token::False, 302 | Token::Semicolon, 303 | Token::Rbrace, 304 | Token::Int(10), 305 | Token::Eq, 306 | Token::Int(10), 307 | Token::Semicolon, 308 | Token::Int(10), 309 | Token::NotEq, 310 | Token::Int(9), 311 | Token::Semicolon, 312 | Token::String(String::from("foobar")), 313 | Token::String(String::from("foo bar")), 314 | Token::LBracket, 315 | Token::Int(1), 316 | Token::Comma, 317 | Token::Int(2), 318 | Token::RBracket, 319 | Token::Semicolon, 320 | Token::Lbrace, 321 | Token::String(String::from("foo")), 322 | Token::Colon, 323 | Token::String(String::from("bar")), 324 | Token::Rbrace, 325 | Token::Macro, 326 | Token::Lparen, 327 | Token::Ident(String::from("x")), 328 | Token::Comma, 329 | Token::Ident(String::from("y")), 330 | Token::Rparen, 331 | Token::Lbrace, 332 | Token::Ident(String::from("x")), 333 | Token::Plus, 334 | Token::Ident(String::from("y")), 335 | Token::Semicolon, 336 | Token::Rbrace, 337 | Token::Semicolon, 338 | Token::Eof, 339 | ]; 340 | 341 | for token in tokens { 342 | let next_token = lexer.next_token(); 343 | println!("expected: {:?}, got: {:?}", token, next_token); 344 | assert_eq!(token, next_token); 345 | } 346 | 347 | return Ok(()); 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /monkey/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod code; 2 | pub mod compiler; 3 | pub mod evaluator; 4 | pub mod lexer; 5 | pub mod monkey; 6 | pub mod object; 7 | pub mod parser; 8 | pub mod token; 9 | pub mod utils; 10 | pub mod vm; 11 | pub mod wasm; 12 | -------------------------------------------------------------------------------- /monkey/src/main.rs: -------------------------------------------------------------------------------- 1 | use ::monkey::monkey::ExecMode; 2 | use ::monkey::utils; 3 | use clap::arg; 4 | use clap::crate_version; 5 | use clap::Parser; 6 | use monkey::monkey; 7 | 8 | /// monkey is the binary for executing the monkey programming language 9 | #[derive(Debug, Parser)] 10 | #[command(name="monkey", version=crate_version!(), about="monkey language", long_about = "Run monkey code")] 11 | struct MonkeyCmd { 12 | /// Path 13 | #[arg(required = false, global = true)] 14 | path: Option, 15 | /// Execution mode (vm or direct) 16 | #[arg( 17 | short = 'm', 18 | long = "mode", 19 | default_value = "vm", 20 | required = false, 21 | global = true 22 | )] 23 | mode: ExecMode, 24 | 25 | /// Enter interactive mode after executing 'script' 26 | #[arg(short = 'i', long = "interactive", required = false, global = true)] 27 | script: Option, 28 | } 29 | 30 | fn main() { 31 | let args = MonkeyCmd::parse(); 32 | 33 | match args.path { 34 | Some(path) => match utils::load_monkey(path) { 35 | Ok(contents) => match monkey::interpret_chunk(args.mode, contents) { 36 | Ok(_) => return, 37 | Err(e) => { 38 | eprintln!("Error: {}", e); 39 | std::process::exit(1); 40 | } 41 | }, 42 | Err(e) => { 43 | eprintln!("Error: {}", e); 44 | std::process::exit(1); 45 | } 46 | }, 47 | None => {} 48 | }; 49 | 50 | // repl mode 51 | match args.script { 52 | Some(path) => match monkey::repl(Some(path), args.mode) { 53 | Ok(_) => {} 54 | Err(e) => eprintln!("Error: {}", e), 55 | }, 56 | None => match monkey::repl(None, args.mode) { 57 | Ok(_) => {} 58 | Err(e) => eprintln!("Error: {}", e), 59 | }, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /monkey/src/monkey/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use signal_hook::{consts::SIGINT, iterator::Signals}; 3 | use strum_macros::{Display, EnumString}; 4 | 5 | use crate::compiler::symbol_table::SymbolTable; 6 | use crate::compiler::Compiler; 7 | use crate::evaluator::{define_macros, evaluate, expand_macros}; 8 | use crate::object::builtin::Builtin; 9 | use crate::object::environment::Environment; 10 | use crate::object::Object; 11 | use crate::utils; 12 | use crate::vm::{GLOBAL_SIZE, VM}; 13 | 14 | use crate::lexer::Lexer; 15 | use crate::parser::ast::Node; 16 | use crate::parser::Parser; 17 | use std::thread; 18 | use std::{ 19 | cell::RefCell, 20 | io::{self, Write}, 21 | rc::Rc, 22 | }; 23 | 24 | #[derive(Debug, Clone, EnumString, Display)] 25 | pub enum ExecMode { 26 | #[strum(serialize = "vm")] 27 | VM, 28 | #[strum(serialize = "direct")] 29 | Direct, 30 | } 31 | 32 | const PROMPT: &str = ">> "; 33 | 34 | pub fn repl(path: Option, mode: ExecMode) -> Result<()> { 35 | let env = Rc::new(RefCell::new(Environment::new())); 36 | let macro_env = Rc::new(RefCell::new(Environment::new())); 37 | println!("Welcome to the Mokey Programming Language REPL!",); 38 | 39 | let mut signals = Signals::new(&[SIGINT])?; 40 | 41 | thread::spawn(move || { 42 | for sig in signals.forever() { 43 | match sig { 44 | SIGINT => { 45 | println!("Exiting REPL"); 46 | std::process::exit(0); 47 | } 48 | _ => {} 49 | } 50 | } 51 | }); 52 | 53 | let constants = Rc::new(RefCell::new(vec![])); 54 | let symbol_table = SymbolTable::new(); 55 | for (i, v) in Builtin::variants().iter().enumerate() { 56 | symbol_table.borrow_mut().define_builtin(i, v.to_string()); 57 | } 58 | let globals = Rc::new(RefCell::new(vec![Rc::new(Object::Null); GLOBAL_SIZE])); 59 | 60 | if let Some(path) = path { 61 | let contents = utils::load_monkey(path)?; 62 | 63 | let result = match mode { 64 | ExecMode::Direct => { 65 | interpret_direct(contents, Some(Rc::clone(&env)), Some(Rc::clone(¯o_env))) 66 | } 67 | ExecMode::VM => interpret_vm( 68 | contents, 69 | Some(Rc::clone(¯o_env)), 70 | symbol_table.clone(), 71 | constants.clone(), 72 | globals.clone(), 73 | ), 74 | }; 75 | 76 | if let Err(err) = result { 77 | eprintln!("{}", err); 78 | } 79 | } 80 | 81 | loop { 82 | print!("{}", PROMPT); 83 | io::stdout().flush()?; 84 | 85 | let mut line = String::new(); 86 | io::stdin().read_line(&mut line)?; 87 | 88 | if line.trim() == "exit" { 89 | std::process::exit(0); 90 | } 91 | 92 | let result = match mode { 93 | ExecMode::Direct => { 94 | interpret_direct(line, Some(Rc::clone(&env)), Some(Rc::clone(¯o_env))) 95 | } 96 | ExecMode::VM => interpret_vm( 97 | line, 98 | Some(Rc::clone(¯o_env)), 99 | symbol_table.clone(), 100 | constants.clone(), 101 | globals.clone(), 102 | ), 103 | }; 104 | 105 | if let Err(err) = result { 106 | eprintln!("{}", err); 107 | } 108 | } 109 | } 110 | 111 | pub fn interpret_chunk(mode: ExecMode, contents: String) -> Result<()> { 112 | let env = Rc::new(RefCell::new(Environment::new())); 113 | let macro_env = Rc::new(RefCell::new(Environment::new())); 114 | 115 | let constants = Rc::new(RefCell::new(vec![])); 116 | let symbol_table = SymbolTable::new(); 117 | for (i, v) in Builtin::variants().iter().enumerate() { 118 | symbol_table.borrow_mut().define_builtin(i, v.to_string()); 119 | } 120 | let globals = Rc::new(RefCell::new(vec![Rc::new(Object::Null); GLOBAL_SIZE])); 121 | 122 | let result = match mode { 123 | ExecMode::Direct => { 124 | interpret_direct(contents, Some(Rc::clone(&env)), Some(Rc::clone(¯o_env))) 125 | } 126 | ExecMode::VM => interpret_vm( 127 | contents, 128 | Some(Rc::clone(¯o_env)), 129 | symbol_table.clone(), 130 | constants.clone(), 131 | globals.clone(), 132 | ), 133 | }; 134 | 135 | if let Err(err) = result { 136 | eprintln!("{}", err); 137 | } 138 | 139 | Ok(()) 140 | } 141 | 142 | pub fn interpret_direct( 143 | contents: String, 144 | env: Option>>, 145 | macro_env: Option>>, 146 | ) -> Result<()> { 147 | let env = env.unwrap_or_else(|| Rc::new(RefCell::new(Environment::new()))); 148 | let macro_env = macro_env.unwrap_or_else(|| Rc::new(RefCell::new(Environment::new()))); 149 | 150 | let lexer = Lexer::new(&contents); 151 | let mut parser = Parser::new(lexer.into()); 152 | let program = parser.parse_program(); 153 | if let Ok(mut program) = program { 154 | define_macros(&mut program, Rc::clone(¯o_env)); 155 | let expanded = 156 | expand_macros(Node::Program(program.clone()), Rc::clone(¯o_env)).unwrap(); 157 | let result = evaluate(expanded, Rc::clone(&env)); 158 | if let Ok(result) = result { 159 | println!("{}", result); 160 | } 161 | } else if let Err(err) = &program { 162 | println!("Woops! We ran into some monkey business here!"); 163 | println!("parser errors:"); 164 | for e in err { 165 | eprintln!("\t{}", e); 166 | } 167 | } 168 | Ok(()) 169 | } 170 | 171 | pub fn interpret_vm( 172 | contents: String, 173 | macro_env: Option>>, 174 | symbol_table: Rc>, 175 | constants: Rc>>>, 176 | globals: Rc>>>, 177 | ) -> Result<()> { 178 | // let env = env.unwrap_or_else(|| Rc::new(RefCell::new(Environment::new()))); 179 | let macro_env = macro_env.unwrap_or_else(|| Rc::new(RefCell::new(Environment::new()))); 180 | 181 | let lexer = Lexer::new(&contents); 182 | let mut parser = Parser::new(lexer.into()); 183 | let program = parser.parse_program(); 184 | 185 | match program { 186 | Ok(mut program) => { 187 | // expand macros 188 | define_macros(&mut program, Rc::clone(¯o_env)); 189 | let expanded = expand_macros(Node::Program(program), Rc::clone(¯o_env)).unwrap(); 190 | 191 | // compile 192 | let mut compiler = Compiler::new_with_state(symbol_table, constants); 193 | compiler.compile(expanded)?; 194 | 195 | let code = compiler.bytecode(); 196 | 197 | let mut machine = VM::new_with_global_store(code, globals); 198 | machine.run()?; 199 | let last_elem = machine.last_popped_stack_elem(); 200 | println!("{}", last_elem); 201 | } 202 | Err(err) => { 203 | println!("Woops! We ran into some monkey business here!"); 204 | println!("parser errors:"); 205 | for e in err { 206 | eprintln!("\t{}", e); 207 | } 208 | } 209 | } 210 | Ok(()) 211 | } 212 | -------------------------------------------------------------------------------- /monkey/src/object/builtin.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::rc::Rc; 3 | 4 | use super::error::ObjectError; 5 | use super::Object; 6 | 7 | #[derive(Debug, PartialEq, Clone)] 8 | #[repr(u8)] 9 | pub enum Builtin { 10 | Len, 11 | First, 12 | Last, 13 | Rest, 14 | Push, 15 | Echo, 16 | Echoln, 17 | } 18 | 19 | impl From for Builtin { 20 | fn from(op: u8) -> Builtin { 21 | match op { 22 | 0 => Builtin::Len, 23 | 1 => Builtin::First, 24 | 2 => Builtin::Last, 25 | 3 => Builtin::Rest, 26 | 4 => Builtin::Push, 27 | 5 => Builtin::Echo, 28 | 6 => Builtin::Echoln, 29 | _ => panic!("unknown builtin index"), 30 | } 31 | } 32 | } 33 | 34 | impl Builtin { 35 | pub fn variants() -> Vec<&'static str> { 36 | vec!["len", "first", "last", "rest", "push", "echo", "echoln"] 37 | } 38 | 39 | pub fn lookup(name: &str) -> Option { 40 | match name { 41 | "len" => Some(Object::Builtin(Builtin::Len)), 42 | "first" => Some(Object::Builtin(Builtin::First)), 43 | "last" => Some(Object::Builtin(Builtin::Last)), 44 | "rest" => Some(Object::Builtin(Builtin::Rest)), 45 | "push" => Some(Object::Builtin(Builtin::Push)), 46 | "echo" => Some(Object::Builtin(Builtin::Echo)), 47 | "echoln" => Some(Object::Builtin(Builtin::Echoln)), 48 | _ => None, 49 | } 50 | } 51 | pub fn apply(&self, args: &Vec>) -> Result, ObjectError> { 52 | match self { 53 | Builtin::Len => { 54 | check_argument_count(1, args.len())?; 55 | match *args[0] { 56 | Object::String(ref s) => Ok(Rc::new(Object::Integer(s.len() as i64))), 57 | Object::Array(ref a) => Ok(Rc::new(Object::Integer(a.len() as i64))), 58 | _ => Err(ObjectError::new(format!( 59 | "argument to `len` not supported, got {}", 60 | args[0] 61 | ))), 62 | } 63 | } 64 | Builtin::First => { 65 | check_argument_count(1, args.len())?; 66 | match *args[0] { 67 | Object::Array(ref a) => { 68 | if a.len() > 0 { 69 | Ok(a[0].clone()) 70 | } else { 71 | Ok(Rc::new(Object::Null)) 72 | } 73 | } 74 | _ => Err(ObjectError::new(format!( 75 | "argument to `first` must be ARRAY, got {}", 76 | args[0] 77 | ))), 78 | } 79 | } 80 | 81 | Builtin::Last => { 82 | check_argument_count(1, args.len())?; 83 | match *args[0] { 84 | Object::Array(ref a) => { 85 | if a.len() > 0 { 86 | Ok(a[a.len() - 1].clone()) 87 | } else { 88 | Ok(Rc::new(Object::Null)) 89 | } 90 | } 91 | _ => Err(ObjectError::new(format!( 92 | "argument to `last` must be ARRAY, got {}", 93 | args[0] 94 | ))), 95 | } 96 | } 97 | Builtin::Rest => { 98 | check_argument_count(1, args.len())?; 99 | match *args[0] { 100 | Object::Array(ref a) => { 101 | if a.len() > 0 { 102 | let mut new_array = Vec::new(); 103 | for i in 1..a.len() { 104 | new_array.push(a[i].clone()); 105 | } 106 | Ok(Rc::new(Object::Array(new_array))) 107 | } else { 108 | Ok(Rc::new(Object::Null)) 109 | } 110 | } 111 | _ => Err(ObjectError::new(format!( 112 | "argument to `rest` must be ARRAY, got {}", 113 | args[0] 114 | ))), 115 | } 116 | } 117 | Builtin::Push => { 118 | check_argument_count(2, args.len())?; 119 | match *args[0] { 120 | Object::Array(ref a) => { 121 | let mut new_array = Vec::new(); 122 | for i in 0..a.len() { 123 | new_array.push(a[i].clone()); 124 | } 125 | new_array.push(args[1].clone()); 126 | Ok(Rc::new(Object::Array(new_array))) 127 | } 128 | _ => Err(ObjectError::new(format!( 129 | "argument to `push` must be ARRAY, got {}", 130 | args[0] 131 | ))), 132 | } 133 | } 134 | Builtin::Echo => { 135 | for arg in args { 136 | print!("{}", arg); 137 | } 138 | 139 | Ok(Rc::new(Object::Null)) 140 | } 141 | Builtin::Echoln => { 142 | for arg in args { 143 | print!("{}", arg); 144 | } 145 | println!(); 146 | Ok(Rc::new(Object::Null)) 147 | } 148 | } 149 | } 150 | } 151 | 152 | fn check_argument_count(expected: usize, actual: usize) -> Result<(), ObjectError> { 153 | if expected != actual { 154 | Err(ObjectError::new(format!( 155 | "wrong number of arguments. expected={}, got={}", 156 | expected, actual 157 | ))) 158 | } else { 159 | Ok(()) 160 | } 161 | } 162 | 163 | impl fmt::Display for Builtin { 164 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 165 | match self { 166 | Builtin::Len => write!(f, "len"), 167 | Builtin::First => write!(f, "first"), 168 | Builtin::Last => write!(f, "last"), 169 | Builtin::Rest => write!(f, "rest"), 170 | Builtin::Push => write!(f, "push"), 171 | Builtin::Echo => write!(f, "echo"), 172 | Builtin::Echoln => write!(f, "echoln"), 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /monkey/src/object/environment.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, collections::HashMap, rc::Rc}; 2 | 3 | use super::Object; 4 | 5 | #[derive(Debug, Clone, PartialEq)] 6 | pub struct Environment { 7 | store: HashMap>, 8 | outer: Option, 9 | } 10 | 11 | pub type Env = Rc>; 12 | 13 | impl Environment { 14 | pub fn new() -> Self { 15 | Self { 16 | store: HashMap::new(), 17 | outer: None, 18 | } 19 | } 20 | 21 | pub fn new_enclosed_environment(outer: Env) -> Self { 22 | let mut env: Environment = Self::new(); 23 | env.outer = Some(outer); 24 | env 25 | } 26 | 27 | pub fn get(&self, name: &str) -> Option> { 28 | match self.store.get(name) { 29 | Some(obj) => Some(obj.clone()), 30 | None => match &self.outer { 31 | Some(outer) => outer.borrow().get(name), 32 | None => None, 33 | }, 34 | } 35 | } 36 | 37 | pub fn set(&mut self, name: String, val: Rc) { 38 | self.store.insert(name, val); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /monkey/src/object/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Debug, Clone, Error, Eq, PartialEq)] 4 | #[error("{msg}")] 5 | pub struct ObjectError { 6 | pub msg: String, 7 | } 8 | 9 | impl ObjectError { 10 | pub fn new(msg: String) -> Self { 11 | ObjectError { msg } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /monkey/src/object/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod builtin; 2 | pub mod environment; 3 | pub mod error; 4 | 5 | use std::{ 6 | collections::HashMap, 7 | fmt::{Display, Formatter, Result}, 8 | hash::{Hash, Hasher}, 9 | rc::Rc, 10 | }; 11 | 12 | use crate::code; 13 | use crate::parser::ast::{Node, Statement}; 14 | 15 | use environment::Env; 16 | 17 | use self::builtin::Builtin; 18 | 19 | #[derive(Debug, Clone, PartialEq, Eq)] 20 | pub struct CompiledFunction { 21 | pub instructions: code::Instructions, 22 | pub num_parameters: usize, 23 | pub num_locals: usize, 24 | } 25 | 26 | impl CompiledFunction { 27 | pub fn new(instructions: code::Instructions, num_parameters: usize, num_locals: usize) -> Self { 28 | CompiledFunction { 29 | instructions, 30 | num_parameters, 31 | num_locals, 32 | } 33 | } 34 | 35 | pub fn instructions(&self) -> &code::Instructions { 36 | &self.instructions 37 | } 38 | 39 | pub fn num_locals(&self) -> usize { 40 | self.num_locals 41 | } 42 | 43 | pub fn num_parameters(&self) -> usize { 44 | self.num_parameters 45 | } 46 | } 47 | 48 | #[derive(Debug, PartialEq, Clone)] 49 | pub enum Object { 50 | Integer(i64), 51 | Boolean(bool), 52 | String(String), 53 | Array(Vec>), 54 | Hash(HashMap, Rc>), 55 | ReturnValue(Rc), 56 | Function(Vec, Vec, Env), 57 | CompiledFunction(Rc), 58 | Builtin(Builtin), 59 | Macro(Vec, Vec, Env), 60 | Quote(Node), 61 | Null, 62 | Closure(Rc, Vec>), 63 | } 64 | 65 | impl Eq for Object {} 66 | 67 | impl Object { 68 | pub fn is_integer(&self) -> bool { 69 | match self { 70 | Object::Integer(_) => true, 71 | _ => false, 72 | } 73 | } 74 | } 75 | 76 | impl Display for Object { 77 | fn fmt(&self, f: &mut Formatter) -> Result { 78 | match self { 79 | Object::Integer(i) => write!(f, "{}", i), 80 | Object::Boolean(b) => write!(f, "{}", b), 81 | Object::String(s) => write!(f, "{}", s), 82 | Object::ReturnValue(o) => write!(f, "{}", o), 83 | Object::Null => write!(f, "null"), 84 | Object::Function(parameters, _, _) => { 85 | let params = parameters.join(", "); 86 | write!(f, "fn({}) {{...}}", params) 87 | } 88 | Object::Builtin(b) => write!(f, "{}", b), 89 | Object::Array(a) => { 90 | let elements: Vec = a.iter().map(|e| format!("{}", e)).collect(); 91 | write!(f, "[{}]", elements.join(", ")) 92 | } 93 | Object::Hash(h) => { 94 | let mut pairs: Vec = Vec::new(); 95 | for (k, v) in h.iter() { 96 | pairs.push(format!("{}: {}", k, v)); 97 | } 98 | write!(f, "{{{}}}", pairs.join(", ")) 99 | } 100 | Object::Quote(s) => { 101 | write!(f, "QUOTE({})", s) 102 | } 103 | Object::Macro(parameters, _, _) => { 104 | let params = parameters.join(", "); 105 | write!(f, "macro({}) {{...}}", params) 106 | } 107 | Object::CompiledFunction(compiled_function) => { 108 | write!(f, "{}", compiled_function.instructions) 109 | } 110 | Object::Closure(_, _) => write!(f, "closure | |"), 111 | } 112 | } 113 | } 114 | 115 | impl From for Object { 116 | fn from(b: bool) -> Self { 117 | Object::Boolean(b) 118 | } 119 | } 120 | 121 | impl From for Object { 122 | fn from(i: i64) -> Self { 123 | Object::Integer(i) 124 | } 125 | } 126 | 127 | impl From for Object { 128 | fn from(s: String) -> Self { 129 | Object::String(s) 130 | } 131 | } 132 | 133 | impl Object { 134 | pub fn is_truthy(&self) -> bool { 135 | match self { 136 | Object::Boolean(b) => *b, 137 | Object::Null => false, 138 | _ => true, 139 | } 140 | } 141 | 142 | pub fn is_empty(&self) -> bool { 143 | match self { 144 | Object::String(s) => s.is_empty(), 145 | Object::Array(a) => a.is_empty(), 146 | _ => false, 147 | } 148 | } 149 | 150 | pub fn is_hashable(&self) -> bool { 151 | match self { 152 | Object::Integer(_) => true, 153 | Object::Boolean(_) => true, 154 | Object::String(_) => true, 155 | _ => false, 156 | } 157 | } 158 | } 159 | 160 | impl Hash for Object { 161 | fn hash(&self, state: &mut H) { 162 | match self { 163 | Object::Integer(i) => i.hash(state), 164 | Object::Boolean(b) => b.hash(state), 165 | Object::String(s) => s.hash(state), 166 | _ => "".hash(state), 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /monkey/src/parser/errors.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | pub type ParserErrors = Vec; 4 | 5 | #[derive(Debug, Clone, Error)] 6 | #[error("{msg}")] 7 | pub struct ParserError { 8 | pub msg: String, 9 | } 10 | 11 | impl ParserError { 12 | pub fn new(msg: String) -> Self { 13 | ParserError { msg } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /monkey/src/parser/precedence.rs: -------------------------------------------------------------------------------- 1 | use crate::token::Token; 2 | 3 | #[derive(PartialOrd, PartialEq, Debug, Copy, Clone)] 4 | pub enum Precedence { 5 | Lowest, 6 | Equals, // == 7 | LessGreater, // > or < 8 | Sum, // + 9 | Product, // * 10 | Prefix, // -X or !X 11 | Call, // myFunction(X) 12 | Index, 13 | } 14 | 15 | pub fn token_precedence(token: &Token) -> Precedence { 16 | match token { 17 | Token::Eq | Token::NotEq => Precedence::Equals, 18 | Token::Plus | Token::Dash => Precedence::Sum, 19 | Token::Lt | Token::Gt => Precedence::LessGreater, 20 | Token::Slash | Token::Asterisk => Precedence::Product, 21 | Token::Lparen => Precedence::Call, 22 | Token::LBracket => Precedence::Index, 23 | _ => Precedence::Lowest, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /monkey/src/token.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter, Result}; 2 | 3 | #[derive(Debug, PartialEq, Clone, Ord, PartialOrd, Eq)] 4 | pub enum Token { 5 | Illegal, 6 | Eof, 7 | 8 | Ident(String), 9 | Int(i64), 10 | String(String), 11 | 12 | // operators 13 | Assign, 14 | Plus, 15 | Dash, 16 | Bang, 17 | Asterisk, 18 | Slash, 19 | 20 | // comparators 21 | Lt, 22 | Gt, 23 | Eq, 24 | NotEq, 25 | 26 | Comma, 27 | Semicolon, 28 | Lparen, 29 | Rparen, 30 | Lbrace, 31 | Rbrace, 32 | LBracket, 33 | RBracket, 34 | Colon, 35 | 36 | // keywords 37 | Function, 38 | Let, 39 | True, 40 | False, 41 | If, 42 | Else, 43 | Return, 44 | Macro, 45 | } 46 | 47 | impl Display for Token { 48 | fn fmt(&self, f: &mut Formatter) -> Result { 49 | return match self { 50 | Token::Illegal => write!(f, "Illegal"), 51 | Token::Colon => write!(f, ":"), 52 | Token::Eof => write!(f, "Eof"), 53 | Token::Ident(s) => write!(f, "{}", s), 54 | Token::Int(s) => write!(f, "{}", s), 55 | Token::Assign => write!(f, "="), 56 | Token::Plus => write!(f, "+"), 57 | Token::Dash => write!(f, "-"), 58 | Token::Bang => write!(f, "!"), 59 | Token::Asterisk => write!(f, "*"), 60 | Token::Slash => write!(f, "/"), 61 | Token::Lt => write!(f, "<"), 62 | Token::Gt => write!(f, ">"), 63 | Token::Eq => write!(f, "=="), 64 | Token::NotEq => write!(f, "!="), 65 | Token::Comma => write!(f, ","), 66 | Token::Semicolon => write!(f, ";"), 67 | Token::Lparen => write!(f, "("), 68 | Token::Rparen => write!(f, ")"), 69 | Token::Lbrace => write!(f, "{{"), 70 | Token::Rbrace => write!(f, "}}"), 71 | Token::LBracket => write!(f, "["), 72 | Token::RBracket => write!(f, "]"), 73 | Token::Function => write!(f, "fn"), 74 | Token::Macro => write!(f, "macro"), 75 | Token::Let => write!(f, "let"), 76 | Token::True => write!(f, "true"), 77 | Token::False => write!(f, "false"), 78 | Token::If => write!(f, "if"), 79 | Token::Else => write!(f, "else"), 80 | Token::Return => write!(f, "return"), 81 | Token::String(s) => write!(f, "{}", s), 82 | }; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /monkey/src/utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | pub fn load_monkey(path: String) -> Result { 4 | let contents = std::fs::read_to_string(path)?; 5 | Ok(contents) 6 | } 7 | -------------------------------------------------------------------------------- /monkey/src/vm/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Debug, Clone, Error)] 4 | #[error("{msg}")] 5 | pub struct VmError { 6 | pub msg: String, 7 | } 8 | 9 | impl VmError { 10 | pub fn new(msg: String) -> Self { 11 | VmError { msg } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /monkey/src/vm/frame.rs: -------------------------------------------------------------------------------- 1 | use crate::{code::Instructions, object::Object}; 2 | use std::rc::Rc; 3 | 4 | use super::error::VmError; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct Frame { 8 | pub function: Rc, 9 | pub ip: isize, 10 | pub base_pointer: usize, 11 | } 12 | 13 | impl Frame { 14 | pub fn new(function: Rc, base_pointer: usize) -> Result { 15 | match &*function { 16 | Object::Closure(compiled_function, num_free) => Ok(Frame { 17 | function: Rc::new(Object::Closure( 18 | compiled_function.clone(), 19 | num_free.to_vec(), 20 | )), 21 | ip: -1, 22 | base_pointer, 23 | }), 24 | _ => Err(VmError::new(format!( 25 | "Expected Closure, got {:?}", 26 | function 27 | ))), 28 | } 29 | } 30 | 31 | pub fn instructions(&self) -> Result { 32 | match &*self.function { 33 | Object::Closure(compiled_function, _) => Ok(compiled_function.instructions().clone()), 34 | _ => Err(VmError::new(format!( 35 | "Expected Closure, got {:?}", 36 | self.function 37 | ))), 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /monkey/src/wasm.rs: -------------------------------------------------------------------------------- 1 | use crate::evaluator::{define_macros, evaluate, expand_macros}; 2 | use crate::object::environment::Environment; 3 | use wasm_bindgen::prelude::*; 4 | 5 | use crate::lexer::Lexer; 6 | use crate::parser::ast::Node; 7 | use crate::parser::Parser; 8 | use std::cell::RefCell; 9 | use std::rc::Rc; 10 | 11 | #[wasm_bindgen] 12 | pub fn interpret(input: &str) -> String { 13 | let env = Rc::new(RefCell::new(Environment::new())); 14 | let macro_env = Rc::new(RefCell::new(Environment::new())); 15 | 16 | let lexer = Lexer::new(&input); 17 | let mut parser = Parser::new(lexer.into()); 18 | let program = parser.parse_program(); 19 | 20 | match program { 21 | Ok(mut program) => { 22 | define_macros(&mut program, Rc::clone(¯o_env)); 23 | let expanded = expand_macros(Node::Program(program.clone()), Rc::clone(¯o_env)); 24 | 25 | match expanded { 26 | Ok(expanded) => { 27 | // Note: You may want to return the result of evaluation. Assuming `evaluate` returns a Result: 28 | match evaluate(expanded, Rc::clone(&env)) { 29 | Ok(result) => return result.to_string(), 30 | Err(err) => return format!("Evaluation error: {:?}", err), 31 | } 32 | } 33 | Err(err) => return format!("macro expansion error: {:?}", err), 34 | } 35 | } 36 | Err(err) => { 37 | let mut error_msg = 38 | String::from("Woops! We ran into some monkey business here!\nparser errors:\n"); 39 | for e in err { 40 | error_msg.push_str(&format!("\t{}\n", e)); 41 | } 42 | error_msg 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /monkey/web/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /monkey/web/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | resolution-mode=highest 3 | -------------------------------------------------------------------------------- /monkey/web/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /monkey/web/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xcacti/interpreter/958b9df588b9046d758b6abb18477a11f592d371/monkey/web/bun.lockb -------------------------------------------------------------------------------- /monkey/web/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module "interpreter"; 2 | 3 | 4 | -------------------------------------------------------------------------------- /monkey/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-interpreter", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" 11 | }, 12 | "devDependencies": { 13 | "@sveltejs/adapter-auto": "^2.0.0", 14 | "@sveltejs/kit": "^1.20.4", 15 | "autoprefixer": "^10.4.15", 16 | "postcss": "^8.4.29", 17 | "svelte": "^4.0.5", 18 | "svelte-check": "^3.4.3", 19 | "tailwindcss": "^3.3.3", 20 | "tslib": "^2.4.1", 21 | "typescript": "^5.0.0", 22 | "vite": "^4.4.2" 23 | }, 24 | "type": "module", 25 | "dependencies": { 26 | "vite-plugin-wasm": "^3.2.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /monkey/web/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /monkey/web/src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /monkey/web/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface Platform {} 9 | } 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /monkey/web/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /monkey/web/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /monkey/web/src/lib/pkg/README.md: -------------------------------------------------------------------------------- 1 | ### MonkeyLang Interpreter in Rust 2 | 3 | Going through Thorsten Ball's Interpreter Book but implementing it in Rust. 4 | Still trying to learn Rust. Please rip me and my implementation apart. 5 | 6 | 7 | Some of the code here was influenced by [ ThePrimeagen's ](https://github.com/ThePrimeagen) version which he abandoned 8 | two chapters into the book. Much of the code is influenced by [monkey-wasm](https://github.com/shioyama18/monkey-wasm/tree/master) 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /monkey/web/src/lib/pkg/interpreter.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | * @param {string} input 5 | * @returns {string} 6 | */ 7 | export function interpret(input: string): string; 8 | 9 | export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; 10 | 11 | export interface InitOutput { 12 | readonly memory: WebAssembly.Memory; 13 | readonly interpret: (a: number, b: number, c: number) => void; 14 | readonly __wbindgen_add_to_stack_pointer: (a: number) => number; 15 | readonly __wbindgen_malloc: (a: number, b: number) => number; 16 | readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; 17 | readonly __wbindgen_free: (a: number, b: number, c: number) => void; 18 | } 19 | 20 | export type SyncInitInput = BufferSource | WebAssembly.Module; 21 | /** 22 | * Instantiates the given `module`, which can either be bytes or 23 | * a precompiled `WebAssembly.Module`. 24 | * 25 | * @param {SyncInitInput} module 26 | * 27 | * @returns {InitOutput} 28 | */ 29 | export function initSync(module: SyncInitInput): InitOutput; 30 | 31 | /** 32 | * If `module_or_path` is {RequestInfo} or {URL}, makes a request and 33 | * for everything else, calls `WebAssembly.instantiate` directly. 34 | * 35 | * @param {InitInput | Promise} module_or_path 36 | * 37 | * @returns {Promise} 38 | */ 39 | export default function __wbg_init (module_or_path?: InitInput | Promise): Promise; 40 | -------------------------------------------------------------------------------- /monkey/web/src/lib/pkg/interpreter.js: -------------------------------------------------------------------------------- 1 | let wasm; 2 | 3 | let WASM_VECTOR_LEN = 0; 4 | 5 | let cachedUint8Memory0 = null; 6 | 7 | function getUint8Memory0() { 8 | if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { 9 | cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); 10 | } 11 | return cachedUint8Memory0; 12 | } 13 | 14 | const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); 15 | 16 | const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' 17 | ? function (arg, view) { 18 | return cachedTextEncoder.encodeInto(arg, view); 19 | } 20 | : function (arg, view) { 21 | const buf = cachedTextEncoder.encode(arg); 22 | view.set(buf); 23 | return { 24 | read: arg.length, 25 | written: buf.length 26 | }; 27 | }); 28 | 29 | function passStringToWasm0(arg, malloc, realloc) { 30 | 31 | if (realloc === undefined) { 32 | const buf = cachedTextEncoder.encode(arg); 33 | const ptr = malloc(buf.length, 1) >>> 0; 34 | getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); 35 | WASM_VECTOR_LEN = buf.length; 36 | return ptr; 37 | } 38 | 39 | let len = arg.length; 40 | let ptr = malloc(len, 1) >>> 0; 41 | 42 | const mem = getUint8Memory0(); 43 | 44 | let offset = 0; 45 | 46 | for (; offset < len; offset++) { 47 | const code = arg.charCodeAt(offset); 48 | if (code > 0x7F) break; 49 | mem[ptr + offset] = code; 50 | } 51 | 52 | if (offset !== len) { 53 | if (offset !== 0) { 54 | arg = arg.slice(offset); 55 | } 56 | ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; 57 | const view = getUint8Memory0().subarray(ptr + offset, ptr + len); 58 | const ret = encodeString(arg, view); 59 | 60 | offset += ret.written; 61 | } 62 | 63 | WASM_VECTOR_LEN = offset; 64 | return ptr; 65 | } 66 | 67 | let cachedInt32Memory0 = null; 68 | 69 | function getInt32Memory0() { 70 | if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { 71 | cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); 72 | } 73 | return cachedInt32Memory0; 74 | } 75 | 76 | const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); 77 | 78 | if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; 79 | 80 | function getStringFromWasm0(ptr, len) { 81 | ptr = ptr >>> 0; 82 | return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); 83 | } 84 | /** 85 | * @param {string} input 86 | * @returns {string} 87 | */ 88 | export function interpret(input) { 89 | let deferred2_0; 90 | let deferred2_1; 91 | try { 92 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 93 | const ptr0 = passStringToWasm0(input, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 94 | const len0 = WASM_VECTOR_LEN; 95 | wasm.interpret(retptr, ptr0, len0); 96 | var r0 = getInt32Memory0()[retptr / 4 + 0]; 97 | var r1 = getInt32Memory0()[retptr / 4 + 1]; 98 | deferred2_0 = r0; 99 | deferred2_1 = r1; 100 | return getStringFromWasm0(r0, r1); 101 | } finally { 102 | wasm.__wbindgen_add_to_stack_pointer(16); 103 | wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); 104 | } 105 | } 106 | 107 | async function __wbg_load(module, imports) { 108 | if (typeof Response === 'function' && module instanceof Response) { 109 | if (typeof WebAssembly.instantiateStreaming === 'function') { 110 | try { 111 | return await WebAssembly.instantiateStreaming(module, imports); 112 | 113 | } catch (e) { 114 | if (module.headers.get('Content-Type') != 'application/wasm') { 115 | 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); 116 | 117 | } else { 118 | throw e; 119 | } 120 | } 121 | } 122 | 123 | const bytes = await module.arrayBuffer(); 124 | return await WebAssembly.instantiate(bytes, imports); 125 | 126 | } else { 127 | const instance = await WebAssembly.instantiate(module, imports); 128 | 129 | if (instance instanceof WebAssembly.Instance) { 130 | return { instance, module }; 131 | 132 | } else { 133 | return instance; 134 | } 135 | } 136 | } 137 | 138 | function __wbg_get_imports() { 139 | const imports = {}; 140 | imports.wbg = {}; 141 | 142 | return imports; 143 | } 144 | 145 | function __wbg_init_memory(imports, maybe_memory) { 146 | 147 | } 148 | 149 | function __wbg_finalize_init(instance, module) { 150 | wasm = instance.exports; 151 | __wbg_init.__wbindgen_wasm_module = module; 152 | cachedInt32Memory0 = null; 153 | cachedUint8Memory0 = null; 154 | 155 | 156 | return wasm; 157 | } 158 | 159 | function initSync(module) { 160 | if (wasm !== undefined) return wasm; 161 | 162 | const imports = __wbg_get_imports(); 163 | 164 | __wbg_init_memory(imports); 165 | 166 | if (!(module instanceof WebAssembly.Module)) { 167 | module = new WebAssembly.Module(module); 168 | } 169 | 170 | const instance = new WebAssembly.Instance(module, imports); 171 | 172 | return __wbg_finalize_init(instance, module); 173 | } 174 | 175 | async function __wbg_init(input) { 176 | if (wasm !== undefined) return wasm; 177 | 178 | if (typeof input === 'undefined') { 179 | input = new URL('/pkg/interpreter_bg.wasm', document.baseURI); 180 | } 181 | const imports = __wbg_get_imports(); 182 | 183 | if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { 184 | input = fetch(input); 185 | } 186 | 187 | __wbg_init_memory(imports); 188 | 189 | const { instance, module } = await __wbg_load(await input, imports); 190 | 191 | return __wbg_finalize_init(instance, module); 192 | } 193 | 194 | export { initSync } 195 | export default __wbg_init; 196 | -------------------------------------------------------------------------------- /monkey/web/src/lib/pkg/interpreter_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xcacti/interpreter/958b9df588b9046d758b6abb18477a11f592d371/monkey/web/src/lib/pkg/interpreter_bg.wasm -------------------------------------------------------------------------------- /monkey/web/src/lib/pkg/interpreter_bg.wasm.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | export const memory: WebAssembly.Memory; 4 | export function interpret(a: number, b: number, c: number): void; 5 | export function __wbindgen_add_to_stack_pointer(a: number): number; 6 | export function __wbindgen_malloc(a: number, b: number): number; 7 | export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number; 8 | export function __wbindgen_free(a: number, b: number, c: number): void; 9 | -------------------------------------------------------------------------------- /monkey/web/src/lib/pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "interpreter", 3 | "version": "0.1.0", 4 | "files": [ 5 | "interpreter_bg.wasm", 6 | "interpreter.js", 7 | "interpreter.d.ts" 8 | ], 9 | "module": "interpreter.js", 10 | "types": "interpreter.d.ts", 11 | "sideEffects": [ 12 | "./snippets/*" 13 | ] 14 | } -------------------------------------------------------------------------------- /monkey/web/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 42 | 43 |
44 |
45 |

Monkey Playground

46 |
47 | 48 |
49 |
50 |
51 |
52 |