diff --git a/Cargo.lock b/Cargo.lock index 4b1cdad..7ea46e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -468,6 +468,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bit-set" version = "0.8.0" @@ -660,6 +666,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link 0.2.1", ] @@ -849,6 +856,40 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -906,6 +947,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ecolor" version = "0.33.3" @@ -1236,12 +1283,48 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.32" @@ -1272,6 +1355,12 @@ dependencies = [ "syn", ] +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + [[package]] name = "futures-task" version = "0.3.32" @@ -1284,9 +1373,13 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] @@ -1631,6 +1724,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1918,6 +2017,9 @@ dependencies = [ "fastrand", "insta", "regex", + "rmcp", + "serde", + "tokio", ] [[package]] @@ -2379,6 +2481,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2598,6 +2706,26 @@ dependencies = [ "bitflags 2.11.0", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex" version = "1.12.3" @@ -2633,6 +2761,41 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rmcp" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6b9d2f0efe2258b23767f1f9e0054cfbcac9c2d6f81a031214143096d7864f" +dependencies = [ + "async-trait", + "base64", + "chrono", + "futures", + "pastey", + "pin-project-lite", + "rmcp-macros", + "schemars", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "rmcp-macros" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9d95d7ed26ad8306352b0d5f05b593222b272790564589790d210aa15caa9e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2686,6 +2849,32 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "chrono", + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -2747,6 +2936,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_json" version = "1.0.149" @@ -3058,6 +3258,41 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" diff --git a/Cargo.toml b/Cargo.toml index fb1bb97..e187472 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ clap = { version = "4.5", features = ["derive"] } chrono = "0.4" regex = "1.10" fastrand = "2.3" +rmcp = { version = "1.2", features = ["server", "transport-io"] } +tokio = { version = "1", features = ["rt", "io-std", "macros"] } +serde = { version = "1", features = ["derive"] } [dev-dependencies] insta = { version = "1.39", features = ["yaml"] } diff --git a/docs/BNF.md b/docs/BNF.md index e6ecc1a..3d1af8a 100644 --- a/docs/BNF.md +++ b/docs/BNF.md @@ -147,11 +147,11 @@ The Myc runtime environment provides a collection of built-in functions and macr (do ;; Create a series with a typed layout (Struct-of-Arrays under the hood) (def my_ticks (series {:price :float :volume :int :msg :text})) - + (push my_ticks {:price 10.5 :volume 100 :msg "A"}) (push my_ticks {:price 11.2 :volume 200 :msg "B"}) (push my_ticks {:price 15.5 :volume 300 :msg "C"}) - + ;; Extract the price series specifically (def prices (.price my_ticks)) @@ -159,3 +159,123 @@ The Myc runtime environment provides a collection of built-in functions and macr (+ (prices 0) (prices 1)) ;; Output: 26.7 ) ``` + +## 8. Common Mistakes & Pitfalls + +This section is especially relevant for LLMs generating Myc code. + +### Wrong keywords from other Lisps + +| Wrong (Clojure/Scheme) | Correct Myc | +|---|---| +| `(let [x 1] ...)` | `(do (def x 1) ...)` | +| `(begin ...)` | `(do ...)` | +| `(lambda [x] ...)` | `(fn [x] ...)` | +| `(define x 1)` | `(def x 1)` | +| `(set! x 2)` | `(assign x 2)` | +| `(cond ...)` | nested `(if ...)` | +| `(not= a b)` | `(<> a b)` | + +### Series indexing is reversed + +Index `0` is the **most recently pushed** item, not the oldest. This is the opposite of normal array indexing: + +```clojure +(push s {:v 1}) +(push s {:v 2}) +(push s {:v 3}) +;; ((.v s) 0) => 3 (newest) +;; ((.v s) 1) => 2 +;; ((.v s) 2) => 1 (oldest) +``` + +### Record keys must be keywords, not strings + +```clojure +;; WRONG: +{"price" 10.5} + +;; CORRECT: +{:price 10.5} +``` + +### `again` is only valid inside `fn` + +`(again ...)` jumps back to the enclosing `fn`. Using it outside a function is a compile error. + +```clojure +;; WRONG — top-level again: +(again 1 2) + +;; CORRECT — inside fn: +(fn [n] (if (= n 0) "done" (again (- n 1)))) +``` + +### `check_syntax` accepts only a single expression + +The `check_syntax` tool uses the single-expression compiler pass. Wrap multiple expressions in `(do ...)`: + +```clojure +;; WRONG: +(def x 1) (+ x 2) + +;; CORRECT: +(do (def x 1) (+ x 2)) +``` + +### Field accessors are functions, not property syntax + +```clojure +;; WRONG — not valid syntax: +user.name + +;; CORRECT — field accessor called as a function: +(.name user) + +;; Also correct — extract accessor first, then call: +(def get-name .name) +(get-name user) +``` + +**Recursive Function with Tail-Call Optimization:** +```clojure +(do + ;; Factorial using explicit TCO via (again ...) — like Clojure's recur + (def factorial + (fn [n acc] + (if (= n 0) + acc + (again (- n 1) (* acc n))))) + + (factorial 10 1) ;; Output: 3628800 +) +``` + +**Macro Definition:** +```clojure +(do + ;; Define a macro that inverts a condition + (macro unless [c body] + `(if ~c ... ~body)) + + (unless false "executed") ;; Output: "executed" +) +``` + +**Series with while Loop:** +```clojure +(do + (def ticks (series {:price :float})) + (def i 0) + + ;; Push 5 prices into the series + (while (< i 5) + (do + (push ticks {:price (* i 1.5)}) + (assign i (+ i 1)))) + + ;; Read back the two most recent prices + (def p (.price ticks)) + (+ (p 0) (p 1)) ;; newest + second-newest +) +``` diff --git a/src/ast/environment.rs b/src/ast/environment.rs index e00fdc6..a0da692 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -600,6 +600,19 @@ impl Environment { } + /// Returns the names of all bindings registered in the fixed RTL scope. + /// Useful for introspection (e.g., MCP server listing available built-ins). + pub fn list_bindings(&self) -> Vec { + let root_scopes = self.root_scopes.borrow(); + let mut names: Vec = root_scopes[0] + .locals + .keys() + .map(|sym| sym.name.to_string()) + .collect(); + names.sort(); + names + } + pub fn dump_ast(&self, source: &str) -> Result { self.preload_dependencies(source, None)?; let compiled = self.compile(source).into_result()?; diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2307e48..39ddc02 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3,6 +3,7 @@ pub mod compiler; pub mod diagnostics; pub mod environment; pub mod lexer; +pub mod mcp_server; pub mod nodes; pub mod parser; pub mod rtl; diff --git a/src/bin/ast.rs b/src/bin/ast.rs index 7ecbb53..d73cd0e 100644 --- a/src/bin/ast.rs +++ b/src/bin/ast.rs @@ -39,10 +39,22 @@ struct Cli { /// Disable optimization #[arg(long)] no_opt: bool, + + /// Start MCP server (stdio transport, for LLM integration) + #[arg(long)] + mcp: bool, } fn main() { let cli = Cli::parse(); + + if cli.mcp { + if let Err(e) = myc::ast::mcp_server::run() { + eprintln!("MCP server error: {}", e); + std::process::exit(1); + } + return; + } let mut env = Environment::new(); env.optimization = !cli.no_opt;