From 97542b17008090f0cc085ecde95c76ef04141568 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Dec 2025 03:37:40 +0000 Subject: [PATCH 1/2] Implement WokeLang interpreter in Rust Complete implementation of WokeLang with: Lexer (src/lexer/): - Token definitions for all keywords, operators, literals - Uses logos for fast tokenization - Handles comments, strings with escapes, numbers Parser (src/parser/): - Recursive descent parser - Full expression parsing with correct precedence - All statement types: remember, when/otherwise, repeat, attempt safely - Pattern matching with decide based on - Emote tags, consent blocks, gratitude declarations AST (src/ast/): - Complete type definitions for all language constructs - Spanned nodes for error reporting Interpreter (src/interpreter/): - Tree-walking interpreter - Scoped environments for variables - Built-in functions: print, len, toString, toInt - Consent system with interactive prompts - Pattern matching evaluation - All operators (arithmetic, comparison, logical) CLI (src/main.rs): - woke - run program - woke --tokenize - show tokens - woke --parse - show AST Examples: - examples/hello.woke - feature showcase - examples/demo.woke - runnable demo --- Cargo.lock | 459 ++++++++++++++ Cargo.toml | 32 + examples/demo.woke | 92 +++ examples/hello.woke | 47 ++ src/ast/mod.rs | 393 ++++++++++++ src/interpreter/mod.rs | 681 ++++++++++++++++++++ src/interpreter/value.rs | 48 ++ src/lexer/mod.rs | 117 ++++ src/lexer/token.rs | 380 +++++++++++ src/lib.rs | 9 + src/main.rs | 84 +++ src/parser/mod.rs | 1291 ++++++++++++++++++++++++++++++++++++++ 12 files changed, 3633 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 examples/demo.woke create mode 100644 examples/hello.woke create mode 100644 src/ast/mod.rs create mode 100644 src/interpreter/mod.rs create mode 100644 src/interpreter/value.rs create mode 100644 src/lexer/mod.rs create mode 100644 src/lexer/token.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/parser/mod.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..71b3283 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,459 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "logos" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7251356ef8cb7aec833ddf598c6cb24d17b689d20b993f9d11a3d764e34e6458" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f80069600c0d66734f5ff52cc42f2dabd6b29d205f333d61fd7832e9e9963f" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24fb722b06a9dc12adb0963ed585f19fc61dc5413e6a9be9422ef92c091e731d" +dependencies = [ + "logos-codegen", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminal_size" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +dependencies = [ + "rustix", + "windows-sys 0.60.2", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-linebreak", + "unicode-width 0.2.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wokelang" +version = "0.1.0" +dependencies = [ + "logos", + "miette", + "pretty_assertions", + "thiserror", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5172001 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "wokelang" +version = "0.1.0" +edition = "2021" +description = "A human-centered, consent-driven programming language" +license = "MIT" +repository = "https://github.com/hyperpolymath/wokelang" + +[lib] +name = "wokelang" +path = "src/lib.rs" + +[[bin]] +name = "woke" +path = "src/main.rs" + +[dependencies] +logos = "0.14" +thiserror = "1.0" +miette = { version = "7.0", features = ["fancy"] } + +[dev-dependencies] +pretty_assertions = "1.4" + +[profile.release] +lto = true +opt-level = "z" +strip = true + +[profile.release-wasm] +inherits = "release" +opt-level = "s" diff --git a/examples/demo.woke b/examples/demo.woke new file mode 100644 index 0000000..e6186a0 --- /dev/null +++ b/examples/demo.woke @@ -0,0 +1,92 @@ +// WokeLang Demo - showing interpreter capabilities + +thanks to { + "You" -> "For trying WokeLang!"; +} + +to greet(name: String) -> String { + hello "Starting greeting"; + remember message = "Hello, " + name + "!"; + give back message; + goodbye "Greeting complete"; +} + +to factorial(n: Int) -> Int { + when n <= 1 { + give back 1; + } + give back n * factorial(n - 1); +} + +to fizzbuzz(n: Int) { + repeat n times { + remember i = n; + when i % 15 == 0 { + print("FizzBuzz"); + } otherwise { + when i % 3 == 0 { + print("Fizz"); + } otherwise { + when i % 5 == 0 { + print("Buzz"); + } otherwise { + print(i); + } + } + } + } +} + +to main() { + // Basic output + print("Welcome to WokeLang!"); + print(""); + + // Function calls + remember greeting = greet("World"); + print(greeting); + print(""); + + // Arithmetic and loops + print("Counting to 5:"); + remember count = 0; + repeat 5 times { + count = count + 1; + print(count); + } + print(""); + + // Conditionals + remember x = 10; + when x > 5 { + print("x is greater than 5"); + } otherwise { + print("x is 5 or less"); + } + print(""); + + // Pattern matching + remember mood = "happy"; + decide based on mood { + "happy" -> { print("Celebrating!"); } + "sad" -> { print("Sending comfort..."); } + _ -> { print("Acknowledged."); } + } + print(""); + + // Recursive function + print("Factorial of 5:"); + remember fact = factorial(5); + print(fact); + print(""); + + // Arrays + remember numbers = [1, 2, 3, 4, 5]; + print("Array:"); + print(numbers); + print("Length:"); + print(len(numbers)); + + print(""); + print("WokeLang demo complete!"); +} diff --git a/examples/hello.woke b/examples/hello.woke new file mode 100644 index 0000000..8cbd4e0 --- /dev/null +++ b/examples/hello.woke @@ -0,0 +1,47 @@ +// A simple WokeLang program demonstrating key features + +thanks to { + "Rust Community" → "For the amazing language tooling"; + "You" → "For trying WokeLang"; +} + +@enthusiastic +to greet(name: String) → String { + hello "Starting the greeting ritual"; + + remember message = "Hello, " + name + "!"; + + give back message; + + goodbye "Greeting complete"; +} + +to main() { + // Consent-based camera access + only if okay "camera_access" { + remember photo = takePhoto(); + } + + // Safe error handling + attempt safely { + remember result = riskyOperation(); + } or reassure "Don't worry, we handled that gracefully"; + + // Loop with natural language + repeat 3 times { + greet("World"); + } + + // Pattern matching + decide based on mood { + "happy" → { celebrate(); } + "sad" → { comfort(); } + _ → { acknowledge(); } + } +} + +// Worker for background tasks +worker dataProcessor { + remember data = fetchData(); + processInBackground(data); +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs new file mode 100644 index 0000000..ac07092 --- /dev/null +++ b/src/ast/mod.rs @@ -0,0 +1,393 @@ +use std::ops::Range; + +/// Source span for error reporting +pub type Span = Range; + +/// A spanned AST node +#[derive(Debug, Clone)] +pub struct Spanned { + pub node: T, + pub span: Span, +} + +impl Spanned { + pub fn new(node: T, span: Span) -> Self { + Self { node, span } + } +} + +/// The root of a WokeLang program +#[derive(Debug, Clone)] +pub struct Program { + pub items: Vec, +} + +/// Top-level items in a program +#[derive(Debug, Clone)] +pub enum TopLevelItem { + Function(FunctionDef), + ConsentBlock(ConsentBlock), + GratitudeDecl(GratitudeDecl), + WorkerDef(WorkerDef), + SideQuestDef(SideQuestDef), + SuperpowerDecl(SuperpowerDecl), + ModuleImport(ModuleImport), + Pragma(Pragma), + TypeDef(TypeDef), + ConstDef(ConstDef), +} + +/// Module import: `use foo.bar renamed baz;` +#[derive(Debug, Clone)] +pub struct ModuleImport { + pub path: QualifiedName, + pub rename: Option, + pub span: Span, +} + +/// Qualified name: `foo.bar.baz` +#[derive(Debug, Clone)] +pub struct QualifiedName { + pub parts: Vec, + pub span: Span, +} + +/// Function definition +#[derive(Debug, Clone)] +pub struct FunctionDef { + pub emote: Option, + pub name: String, + pub params: Vec, + pub return_type: Option, + pub hello: Option, + pub body: Vec, + pub goodbye: Option, + pub span: Span, +} + +/// Function parameter +#[derive(Debug, Clone)] +pub struct Parameter { + pub name: String, + pub ty: Option, + pub span: Span, +} + +/// Consent block: `only if okay "permission" { ... }` +#[derive(Debug, Clone)] +pub struct ConsentBlock { + pub permission: String, + pub body: Vec, + pub span: Span, +} + +/// Gratitude declaration: `thanks to { ... }` +#[derive(Debug, Clone)] +pub struct GratitudeDecl { + pub entries: Vec, + pub span: Span, +} + +/// Single gratitude entry: `"name" → "reason";` +#[derive(Debug, Clone)] +pub struct GratitudeEntry { + pub recipient: String, + pub reason: String, + pub span: Span, +} + +/// Statement types +#[derive(Debug, Clone)] +pub enum Statement { + /// `remember x = expr;` + VarDecl(VarDecl), + /// `x = expr;` + Assignment(Assignment), + /// `give back expr;` + Return(ReturnStmt), + /// `when expr { ... } otherwise { ... }` + Conditional(Conditional), + /// `repeat n times { ... }` + Loop(Loop), + /// `attempt safely { ... } or reassure "msg";` + AttemptBlock(AttemptBlock), + /// `only if okay "perm" { ... }` + ConsentBlock(ConsentBlock), + /// `expr;` + Expression(Spanned), + /// `spawn worker name;` + WorkerSpawn(WorkerSpawn), + /// `complain "message";` + Complain(ComplainStmt), + /// `@emote statement` + EmoteAnnotated(EmoteAnnotatedStmt), + /// `decide based on expr { ... }` + Decide(DecideStmt), +} + +/// Variable declaration: `remember x = expr measured in unit;` +#[derive(Debug, Clone)] +pub struct VarDecl { + pub name: String, + pub value: Spanned, + pub unit: Option, + pub span: Span, +} + +/// Assignment: `x = expr;` +#[derive(Debug, Clone)] +pub struct Assignment { + pub target: String, + pub value: Spanned, + pub span: Span, +} + +/// Return statement: `give back expr;` +#[derive(Debug, Clone)] +pub struct ReturnStmt { + pub value: Spanned, + pub span: Span, +} + +/// Conditional: `when expr { ... } otherwise { ... }` +#[derive(Debug, Clone)] +pub struct Conditional { + pub condition: Spanned, + pub then_branch: Vec, + pub else_branch: Option>, + pub span: Span, +} + +/// Loop: `repeat n times { ... }` +#[derive(Debug, Clone)] +pub struct Loop { + pub count: Spanned, + pub body: Vec, + pub span: Span, +} + +/// Attempt block: `attempt safely { ... } or reassure "msg";` +#[derive(Debug, Clone)] +pub struct AttemptBlock { + pub body: Vec, + pub reassurance: String, + pub span: Span, +} + +/// Worker spawn: `spawn worker name;` +#[derive(Debug, Clone)] +pub struct WorkerSpawn { + pub worker_name: String, + pub span: Span, +} + +/// Complain statement: `complain "message";` +#[derive(Debug, Clone)] +pub struct ComplainStmt { + pub message: String, + pub span: Span, +} + +/// Emote-annotated statement: `@emote statement` +#[derive(Debug, Clone)] +pub struct EmoteAnnotatedStmt { + pub emote: EmoteTag, + pub statement: Box, + pub span: Span, +} + +/// Decide statement (pattern matching): `decide based on expr { ... }` +#[derive(Debug, Clone)] +pub struct DecideStmt { + pub scrutinee: Spanned, + pub arms: Vec, + pub span: Span, +} + +/// Match arm: `pattern → { ... }` +#[derive(Debug, Clone)] +pub struct MatchArm { + pub pattern: Pattern, + pub body: Vec, + pub span: Span, +} + +/// Pattern for matching +#[derive(Debug, Clone)] +pub enum Pattern { + Literal(Literal), + Identifier(String), + Wildcard, +} + +/// Expression types +#[derive(Debug, Clone)] +pub enum Expr { + /// Literal value + Literal(Literal), + /// Variable reference + Identifier(String), + /// Binary operation + Binary(BinaryOp, Box>, Box>), + /// Unary operation + Unary(UnaryOp, Box>), + /// Function call + Call(String, Vec>), + /// Unit measurement: `expr measured in unit` + UnitMeasurement(Box>, String), + /// Gratitude literal: `thanks("name")` + GratitudeLiteral(String), + /// Array literal + Array(Vec>), +} + +/// Binary operators +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BinaryOp { + Add, + Sub, + Mul, + Div, + Mod, + Eq, + NotEq, + Lt, + Gt, + LtEq, + GtEq, + And, + Or, +} + +/// Unary operators +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum UnaryOp { + Neg, + Not, +} + +/// Literal values +#[derive(Debug, Clone)] +pub enum Literal { + Integer(i64), + Float(f64), + String(String), + Bool(bool), +} + +/// Emote tag: `@name(params)` +#[derive(Debug, Clone)] +pub struct EmoteTag { + pub name: String, + pub params: Vec, + pub span: Span, +} + +/// Emote parameter: `name=value` +#[derive(Debug, Clone)] +pub struct EmoteParam { + pub name: String, + pub value: EmoteValue, +} + +/// Emote parameter value +#[derive(Debug, Clone)] +pub enum EmoteValue { + Number(f64), + String(String), + Identifier(String), +} + +/// Worker definition: `worker name { ... }` +#[derive(Debug, Clone)] +pub struct WorkerDef { + pub name: String, + pub body: Vec, + pub span: Span, +} + +/// Side quest definition: `side quest name { ... }` +#[derive(Debug, Clone)] +pub struct SideQuestDef { + pub name: String, + pub body: Vec, + pub span: Span, +} + +/// Superpower declaration: `superpower name { ... }` +#[derive(Debug, Clone)] +pub struct SuperpowerDecl { + pub name: String, + pub body: Vec, + pub span: Span, +} + +/// Pragma: `#care on;` +#[derive(Debug, Clone)] +pub struct Pragma { + pub directive: PragmaDirective, + pub enabled: bool, + pub span: Span, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum PragmaDirective { + Care, + Strict, + Verbose, +} + +/// Type annotation +#[derive(Debug, Clone)] +pub enum Type { + /// Basic types: String, Int, Float, Bool, or custom + Basic(String), + /// Array type: [T] + Array(Box), + /// Optional type: Maybe T + Optional(Box), + /// Reference type: &T + Reference(Box), +} + +/// Type definition: `type Name = ...;` +#[derive(Debug, Clone)] +pub struct TypeDef { + pub name: String, + pub definition: TypeVariant, + pub span: Span, +} + +/// Type variant for type definitions +#[derive(Debug, Clone)] +pub enum TypeVariant { + /// Struct: `{ field: Type, ... }` + Struct(Vec), + /// Enum: `Variant1 | Variant2(Type)` + Enum(Vec), + /// Alias: `= OtherType` + Alias(Type), +} + +/// Struct field +#[derive(Debug, Clone)] +pub struct Field { + pub name: String, + pub ty: Type, +} + +/// Enum variant +#[derive(Debug, Clone)] +pub struct Variant { + pub name: String, + pub fields: Vec, +} + +/// Constant definition: `const NAME: Type = expr;` +#[derive(Debug, Clone)] +pub struct ConstDef { + pub name: String, + pub ty: Type, + pub value: Spanned, + pub span: Span, +} diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs new file mode 100644 index 0000000..34419ca --- /dev/null +++ b/src/interpreter/mod.rs @@ -0,0 +1,681 @@ +mod value; + +pub use value::Value; + +use crate::ast::*; +use std::collections::HashMap; +use std::io::{self, Write}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum RuntimeError { + #[error("Undefined variable: {0}")] + UndefinedVariable(String), + + #[error("Undefined function: {0}")] + UndefinedFunction(String), + + #[error("Type error: {0}")] + TypeError(String), + + #[error("Division by zero")] + DivisionByZero, + + #[error("Consent denied for: {0}")] + ConsentDenied(String), + + #[error("Complaint: {0}")] + Complaint(String), + + #[error("Index out of bounds: {0}")] + IndexOutOfBounds(usize), + + #[error("Arity mismatch: expected {expected}, got {got}")] + ArityMismatch { expected: usize, got: usize }, +} + +type Result = std::result::Result; + +/// Control flow signals for return statements +enum ControlFlow { + Continue, + Return(Value), +} + +/// Runtime environment for variable bindings +#[derive(Clone)] +struct Environment { + scopes: Vec>, +} + +impl Environment { + fn new() -> Self { + Self { + scopes: vec![HashMap::new()], + } + } + + fn push_scope(&mut self) { + self.scopes.push(HashMap::new()); + } + + fn pop_scope(&mut self) { + self.scopes.pop(); + } + + fn define(&mut self, name: String, value: Value) { + if let Some(scope) = self.scopes.last_mut() { + scope.insert(name, value); + } + } + + fn get(&self, name: &str) -> Option<&Value> { + for scope in self.scopes.iter().rev() { + if let Some(value) = scope.get(name) { + return Some(value); + } + } + None + } + + fn set(&mut self, name: &str, value: Value) -> bool { + for scope in self.scopes.iter_mut().rev() { + if scope.contains_key(name) { + scope.insert(name.to_string(), value); + return true; + } + } + false + } +} + +/// The WokeLang interpreter +pub struct Interpreter { + env: Environment, + functions: HashMap, + workers: HashMap, + gratitude: Vec<(String, String)>, + consent_cache: HashMap, + verbose: bool, + care_mode: bool, +} + +impl Interpreter { + pub fn new() -> Self { + Self { + env: Environment::new(), + functions: HashMap::new(), + workers: HashMap::new(), + gratitude: Vec::new(), + consent_cache: HashMap::new(), + verbose: false, + care_mode: true, + } + } + + pub fn run(&mut self, program: &Program) -> Result<()> { + // First pass: collect all function and worker definitions + for item in &program.items { + match item { + TopLevelItem::Function(f) => { + self.functions.insert(f.name.clone(), f.clone()); + } + TopLevelItem::WorkerDef(w) => { + self.workers.insert(w.name.clone(), w.clone()); + } + TopLevelItem::GratitudeDecl(g) => { + for entry in &g.entries { + self.gratitude + .push((entry.recipient.clone(), entry.reason.clone())); + } + } + TopLevelItem::Pragma(p) => { + match p.directive { + PragmaDirective::Verbose => self.verbose = p.enabled, + PragmaDirective::Care => self.care_mode = p.enabled, + PragmaDirective::Strict => {} // TODO + } + } + _ => {} + } + } + + // Show gratitude if verbose + if self.verbose && !self.gratitude.is_empty() { + println!("=== Gratitude ==="); + for (recipient, reason) in &self.gratitude { + println!(" Thanks to {} for: {}", recipient, reason); + } + println!(); + } + + // Second pass: execute top-level items + for item in &program.items { + match item { + TopLevelItem::ConsentBlock(c) => { + self.execute_consent_block(c)?; + } + TopLevelItem::Function(_) + | TopLevelItem::WorkerDef(_) + | TopLevelItem::GratitudeDecl(_) + | TopLevelItem::Pragma(_) => { + // Already processed + } + _ => {} + } + } + + // Look for and execute main function + if self.functions.contains_key("main") { + self.call_function("main", vec![])?; + } + + Ok(()) + } + + fn execute_statement(&mut self, stmt: &Statement) -> Result { + match stmt { + Statement::VarDecl(decl) => { + let value = self.evaluate(&decl.value)?; + if self.verbose { + if let Some(unit) = &decl.unit { + println!(" remember {} = {:?} measured in {}", decl.name, value, unit); + } else { + println!(" remember {} = {:?}", decl.name, value); + } + } + self.env.define(decl.name.clone(), value); + Ok(ControlFlow::Continue) + } + Statement::Assignment(assign) => { + let value = self.evaluate(&assign.value)?; + if !self.env.set(&assign.target, value) { + return Err(RuntimeError::UndefinedVariable(assign.target.clone())); + } + Ok(ControlFlow::Continue) + } + Statement::Return(ret) => { + let value = self.evaluate(&ret.value)?; + Ok(ControlFlow::Return(value)) + } + Statement::Conditional(cond) => { + let condition = self.evaluate(&cond.condition)?; + if condition.is_truthy() { + for stmt in &cond.then_branch { + if let ControlFlow::Return(v) = self.execute_statement(stmt)? { + return Ok(ControlFlow::Return(v)); + } + } + } else if let Some(else_branch) = &cond.else_branch { + for stmt in else_branch { + if let ControlFlow::Return(v) = self.execute_statement(stmt)? { + return Ok(ControlFlow::Return(v)); + } + } + } + Ok(ControlFlow::Continue) + } + Statement::Loop(loop_stmt) => { + let count = self.evaluate(&loop_stmt.count)?; + let n = match count { + Value::Int(n) => n, + _ => return Err(RuntimeError::TypeError("Loop count must be an integer".into())), + }; + + for _ in 0..n { + for stmt in &loop_stmt.body { + if let ControlFlow::Return(v) = self.execute_statement(stmt)? { + return Ok(ControlFlow::Return(v)); + } + } + } + Ok(ControlFlow::Continue) + } + Statement::AttemptBlock(attempt) => { + self.env.push_scope(); + let result: Result = (|| { + for stmt in &attempt.body { + if let ControlFlow::Return(v) = self.execute_statement(stmt)? { + return Ok(ControlFlow::Return(v)); + } + } + Ok(ControlFlow::Continue) + })(); + self.env.pop_scope(); + + match result { + Ok(cf) => Ok(cf), + Err(_) => { + if self.verbose { + println!(" Reassurance: {}", attempt.reassurance); + } + Ok(ControlFlow::Continue) + } + } + } + Statement::ConsentBlock(consent) => { + self.execute_consent_block(consent)?; + Ok(ControlFlow::Continue) + } + Statement::Expression(expr) => { + self.evaluate(expr)?; + Ok(ControlFlow::Continue) + } + Statement::WorkerSpawn(spawn) => { + if self.verbose { + println!(" Spawning worker: {}", spawn.worker_name); + } + // In a real implementation, this would spawn a thread/task + // For now, we just execute the worker synchronously + if let Some(worker) = self.workers.get(&spawn.worker_name).cloned() { + self.env.push_scope(); + for stmt in &worker.body { + self.execute_statement(stmt)?; + } + self.env.pop_scope(); + } + Ok(ControlFlow::Continue) + } + Statement::Complain(complain) => { + if self.care_mode { + eprintln!("Complaint: {}", complain.message); + } + Ok(ControlFlow::Continue) + } + Statement::EmoteAnnotated(annotated) => { + if self.verbose { + println!(" @{}", annotated.emote.name); + } + self.execute_statement(&annotated.statement) + } + Statement::Decide(decide) => { + let scrutinee = self.evaluate(&decide.scrutinee)?; + + for arm in &decide.arms { + if self.pattern_matches(&arm.pattern, &scrutinee) { + self.env.push_scope(); + // Bind pattern variables + if let Pattern::Identifier(name) = &arm.pattern { + self.env.define(name.clone(), scrutinee.clone()); + } + for stmt in &arm.body { + if let ControlFlow::Return(v) = self.execute_statement(stmt)? { + self.env.pop_scope(); + return Ok(ControlFlow::Return(v)); + } + } + self.env.pop_scope(); + break; + } + } + Ok(ControlFlow::Continue) + } + } + } + + fn execute_consent_block(&mut self, consent: &ConsentBlock) -> Result<()> { + let permission = &consent.permission; + + // Check cache first + let granted = if let Some(&cached) = self.consent_cache.get(permission) { + cached + } else { + // Ask user for consent + print!("Permission requested: '{}'. Allow? [y/N]: ", permission); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + let granted = input.trim().eq_ignore_ascii_case("y"); + + self.consent_cache.insert(permission.clone(), granted); + granted + }; + + if granted { + self.env.push_scope(); + for stmt in &consent.body { + self.execute_statement(stmt)?; + } + self.env.pop_scope(); + } else if self.verbose { + println!(" Consent denied for: {}", permission); + } + + Ok(()) + } + + fn pattern_matches(&self, pattern: &Pattern, value: &Value) -> bool { + match pattern { + Pattern::Wildcard => true, + Pattern::Identifier(_) => true, // Identifier patterns always match and bind + Pattern::Literal(lit) => { + let lit_value = self.literal_to_value(lit); + value == &lit_value + } + } + } + + fn literal_to_value(&self, lit: &Literal) -> Value { + match lit { + Literal::Integer(n) => Value::Int(*n), + Literal::Float(n) => Value::Float(*n), + Literal::String(s) => Value::String(s.clone()), + Literal::Bool(b) => Value::Bool(*b), + } + } + + fn evaluate(&mut self, expr: &Spanned) -> Result { + match &expr.node { + Expr::Literal(lit) => Ok(self.literal_to_value(lit)), + Expr::Identifier(name) => self + .env + .get(name) + .cloned() + .ok_or_else(|| RuntimeError::UndefinedVariable(name.clone())), + Expr::Binary(op, left, right) => { + let left_val = self.evaluate(left)?; + let right_val = self.evaluate(right)?; + self.apply_binary_op(*op, left_val, right_val) + } + Expr::Unary(op, operand) => { + let val = self.evaluate(operand)?; + self.apply_unary_op(*op, val) + } + Expr::Call(name, args) => { + let arg_values: Vec = args + .iter() + .map(|a| self.evaluate(a)) + .collect::>()?; + + // Check for built-in functions first + if let Some(result) = self.call_builtin(name, &arg_values)? { + return Ok(result); + } + + self.call_function(name, arg_values) + } + Expr::UnitMeasurement(inner, _unit) => { + // For now, just evaluate the inner expression + // A full implementation would track units + self.evaluate(inner) + } + Expr::GratitudeLiteral(name) => { + if self.verbose { + println!(" Expressing gratitude to: {}", name); + } + Ok(Value::String(format!("Thanks to {}", name))) + } + Expr::Array(elements) => { + let values: Vec = elements + .iter() + .map(|e| self.evaluate(e)) + .collect::>()?; + Ok(Value::Array(values)) + } + } + } + + fn call_builtin(&mut self, name: &str, args: &[Value]) -> Result> { + match name { + "print" => { + for (i, arg) in args.iter().enumerate() { + if i > 0 { + print!(" "); + } + print!("{}", arg); + } + println!(); + Ok(Some(Value::Unit)) + } + "len" => { + if args.len() != 1 { + return Err(RuntimeError::ArityMismatch { + expected: 1, + got: args.len(), + }); + } + match &args[0] { + Value::String(s) => Ok(Some(Value::Int(s.len() as i64))), + Value::Array(a) => Ok(Some(Value::Int(a.len() as i64))), + _ => Err(RuntimeError::TypeError("len() requires string or array".into())), + } + } + "toString" => { + if args.len() != 1 { + return Err(RuntimeError::ArityMismatch { + expected: 1, + got: args.len(), + }); + } + Ok(Some(Value::String(args[0].to_string()))) + } + "toInt" => { + if args.len() != 1 { + return Err(RuntimeError::ArityMismatch { + expected: 1, + got: args.len(), + }); + } + match &args[0] { + Value::String(s) => { + let n = s.parse::().map_err(|_| { + RuntimeError::TypeError(format!("Cannot convert '{}' to Int", s)) + })?; + Ok(Some(Value::Int(n))) + } + Value::Float(f) => Ok(Some(Value::Int(*f as i64))), + Value::Int(n) => Ok(Some(Value::Int(*n))), + _ => Err(RuntimeError::TypeError("Cannot convert to Int".into())), + } + } + _ => Ok(None), // Not a builtin + } + } + + fn call_function(&mut self, name: &str, args: Vec) -> Result { + let func = self + .functions + .get(name) + .cloned() + .ok_or_else(|| RuntimeError::UndefinedFunction(name.to_string()))?; + + if func.params.len() != args.len() { + return Err(RuntimeError::ArityMismatch { + expected: func.params.len(), + got: args.len(), + }); + } + + // Print hello message + if let Some(hello) = &func.hello { + if self.verbose { + println!("[{}] {}", name, hello); + } + } + + // Create new scope and bind parameters + self.env.push_scope(); + for (param, arg) in func.params.iter().zip(args) { + self.env.define(param.name.clone(), arg); + } + + // Execute function body + let mut result = Value::Unit; + for stmt in &func.body { + match self.execute_statement(stmt)? { + ControlFlow::Return(v) => { + result = v; + break; + } + ControlFlow::Continue => {} + } + } + + self.env.pop_scope(); + + // Print goodbye message + if let Some(goodbye) = &func.goodbye { + if self.verbose { + println!("[{}] {}", name, goodbye); + } + } + + Ok(result) + } + + fn apply_binary_op(&self, op: BinaryOp, left: Value, right: Value) -> Result { + match op { + BinaryOp::Add => match (left, right) { + (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a + b)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)), + (Value::Int(a), Value::Float(b)) => Ok(Value::Float(a as f64 + b)), + (Value::Float(a), Value::Int(b)) => Ok(Value::Float(a + b as f64)), + (Value::String(a), Value::String(b)) => Ok(Value::String(a + &b)), + (Value::String(a), b) => Ok(Value::String(a + &b.to_string())), + (a, Value::String(b)) => Ok(Value::String(a.to_string() + &b)), + _ => Err(RuntimeError::TypeError("Cannot add these types".into())), + }, + BinaryOp::Sub => match (left, right) { + (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a - b)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a - b)), + (Value::Int(a), Value::Float(b)) => Ok(Value::Float(a as f64 - b)), + (Value::Float(a), Value::Int(b)) => Ok(Value::Float(a - b as f64)), + _ => Err(RuntimeError::TypeError("Cannot subtract these types".into())), + }, + BinaryOp::Mul => match (left, right) { + (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a * b)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a * b)), + (Value::Int(a), Value::Float(b)) => Ok(Value::Float(a as f64 * b)), + (Value::Float(a), Value::Int(b)) => Ok(Value::Float(a * b as f64)), + _ => Err(RuntimeError::TypeError("Cannot multiply these types".into())), + }, + BinaryOp::Div => match (left, right) { + (_, Value::Int(0)) => Err(RuntimeError::DivisionByZero), + (_, Value::Float(f)) if f == 0.0 => Err(RuntimeError::DivisionByZero), + (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a / b)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a / b)), + (Value::Int(a), Value::Float(b)) => Ok(Value::Float(a as f64 / b)), + (Value::Float(a), Value::Int(b)) => Ok(Value::Float(a / b as f64)), + _ => Err(RuntimeError::TypeError("Cannot divide these types".into())), + }, + BinaryOp::Mod => match (left, right) { + (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a % b)), + _ => Err(RuntimeError::TypeError("Modulo requires integers".into())), + }, + BinaryOp::Eq => Ok(Value::Bool(left == right)), + BinaryOp::NotEq => Ok(Value::Bool(left != right)), + BinaryOp::Lt => match (left, right) { + (Value::Int(a), Value::Int(b)) => Ok(Value::Bool(a < b)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Bool(a < b)), + (Value::String(a), Value::String(b)) => Ok(Value::Bool(a < b)), + _ => Err(RuntimeError::TypeError("Cannot compare these types".into())), + }, + BinaryOp::Gt => match (left, right) { + (Value::Int(a), Value::Int(b)) => Ok(Value::Bool(a > b)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Bool(a > b)), + (Value::String(a), Value::String(b)) => Ok(Value::Bool(a > b)), + _ => Err(RuntimeError::TypeError("Cannot compare these types".into())), + }, + BinaryOp::LtEq => match (left, right) { + (Value::Int(a), Value::Int(b)) => Ok(Value::Bool(a <= b)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Bool(a <= b)), + (Value::String(a), Value::String(b)) => Ok(Value::Bool(a <= b)), + _ => Err(RuntimeError::TypeError("Cannot compare these types".into())), + }, + BinaryOp::GtEq => match (left, right) { + (Value::Int(a), Value::Int(b)) => Ok(Value::Bool(a >= b)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Bool(a >= b)), + (Value::String(a), Value::String(b)) => Ok(Value::Bool(a >= b)), + _ => Err(RuntimeError::TypeError("Cannot compare these types".into())), + }, + BinaryOp::And => Ok(Value::Bool(left.is_truthy() && right.is_truthy())), + BinaryOp::Or => Ok(Value::Bool(left.is_truthy() || right.is_truthy())), + } + } + + fn apply_unary_op(&self, op: UnaryOp, val: Value) -> Result { + match op { + UnaryOp::Neg => match val { + Value::Int(n) => Ok(Value::Int(-n)), + Value::Float(f) => Ok(Value::Float(-f)), + _ => Err(RuntimeError::TypeError("Cannot negate this type".into())), + }, + UnaryOp::Not => Ok(Value::Bool(!val.is_truthy())), + } + } +} + +impl Default for Interpreter { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lexer::Lexer; + use crate::parser::Parser; + + fn run_program(source: &str) -> Result<()> { + let lexer = Lexer::new(source); + let tokens = lexer.tokenize().expect("Lexer failed"); + let mut parser = Parser::new(tokens, source); + let program = parser.parse().expect("Parser failed"); + let mut interpreter = Interpreter::new(); + interpreter.run(&program) + } + + #[test] + fn test_simple_arithmetic() { + let source = r#" + to main() { + remember x = 1 + 2 * 3; + remember y = (1 + 2) * 3; + } + "#; + assert!(run_program(source).is_ok()); + } + + #[test] + fn test_function_call() { + let source = r#" + to add(a: Int, b: Int) -> Int { + give back a + b; + } + to main() { + remember result = add(2, 3); + } + "#; + assert!(run_program(source).is_ok()); + } + + #[test] + fn test_conditional() { + let source = r#" + to main() { + remember x = 10; + when x > 5 { + remember y = "big"; + } otherwise { + remember y = "small"; + } + } + "#; + assert!(run_program(source).is_ok()); + } + + #[test] + fn test_loop() { + let source = r#" + to main() { + remember count = 0; + repeat 5 times { + count = count + 1; + } + } + "#; + assert!(run_program(source).is_ok()); + } +} diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs new file mode 100644 index 0000000..061e139 --- /dev/null +++ b/src/interpreter/value.rs @@ -0,0 +1,48 @@ +use std::fmt; + +/// Runtime value in WokeLang +#[derive(Debug, Clone, PartialEq)] +pub enum Value { + Int(i64), + Float(f64), + String(String), + Bool(bool), + Array(Vec), + Unit, +} + +impl Value { + /// Check if the value is truthy + pub fn is_truthy(&self) -> bool { + match self { + Value::Bool(b) => *b, + Value::Int(n) => *n != 0, + Value::Float(f) => *f != 0.0, + Value::String(s) => !s.is_empty(), + Value::Array(a) => !a.is_empty(), + Value::Unit => false, + } + } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Value::Int(n) => write!(f, "{}", n), + Value::Float(n) => write!(f, "{}", n), + Value::String(s) => write!(f, "{}", s), + Value::Bool(b) => write!(f, "{}", b), + Value::Array(elements) => { + write!(f, "[")?; + for (i, elem) in elements.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", elem)?; + } + write!(f, "]") + } + Value::Unit => write!(f, "()"), + } + } +} diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs new file mode 100644 index 0000000..f59d36e --- /dev/null +++ b/src/lexer/mod.rs @@ -0,0 +1,117 @@ +mod token; + +pub use token::Token; + +use logos::Logos; +use miette::{Diagnostic, SourceSpan}; +use thiserror::Error; + +#[derive(Error, Debug, Diagnostic)] +#[error("Unexpected character")] +#[diagnostic(code(wokelang::lexer::unexpected_char))] +pub struct LexerError { + #[source_code] + pub src: String, + #[label("here")] + pub span: SourceSpan, +} + +#[derive(Debug, Clone)] +pub struct Spanned { + pub value: T, + pub span: std::ops::Range, +} + +impl Spanned { + pub fn new(value: T, span: std::ops::Range) -> Self { + Self { value, span } + } +} + +pub struct Lexer<'src> { + source: &'src str, +} + +impl<'src> Lexer<'src> { + pub fn new(source: &'src str) -> Self { + Self { source } + } + + pub fn tokenize(&self) -> Result>, LexerError> { + let mut tokens = Vec::new(); + let mut lexer = Token::lexer(self.source); + + while let Some(result) = lexer.next() { + match result { + Ok(token) => { + tokens.push(Spanned::new(token, lexer.span())); + } + Err(_) => { + return Err(LexerError { + src: self.source.to_string(), + span: lexer.span().into(), + }); + } + } + } + + tokens.push(Spanned::new(Token::Eof, self.source.len()..self.source.len())); + Ok(tokens) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_simple_function() { + let source = r#"to greet() { + hello "Starting greet"; + give back "Hello, World!"; + goodbye "Ending greet"; + }"#; + + let lexer = Lexer::new(source); + let tokens = lexer.tokenize().unwrap(); + + assert!(matches!(tokens[0].value, Token::To)); + assert!(matches!(tokens[1].value, Token::Identifier(_))); + } + + #[test] + fn test_consent_block() { + let source = r#"only if okay "access_camera" { }"#; + + let lexer = Lexer::new(source); + let tokens = lexer.tokenize().unwrap(); + + assert!(matches!(tokens[0].value, Token::Only)); + assert!(matches!(tokens[1].value, Token::If)); + assert!(matches!(tokens[2].value, Token::Okay)); + } + + #[test] + fn test_numbers() { + let source = "42 3.14 -17"; + + let lexer = Lexer::new(source); + let tokens = lexer.tokenize().unwrap(); + + assert!(matches!(tokens[0].value, Token::Integer(42))); + assert!(matches!(tokens[1].value, Token::Float(_))); + assert!(matches!(tokens[2].value, Token::Minus)); + assert!(matches!(tokens[3].value, Token::Integer(17))); + } + + #[test] + fn test_emote_tag() { + let source = "@happy(intensity=10)"; + + let lexer = Lexer::new(source); + let tokens = lexer.tokenize().unwrap(); + + assert!(matches!(tokens[0].value, Token::At)); + assert!(matches!(tokens[1].value, Token::Identifier(_))); + } +} diff --git a/src/lexer/token.rs b/src/lexer/token.rs new file mode 100644 index 0000000..0270255 --- /dev/null +++ b/src/lexer/token.rs @@ -0,0 +1,380 @@ +use logos::Logos; + +fn parse_string(lex: &mut logos::Lexer) -> Option { + let slice = lex.slice(); + // Remove surrounding quotes and handle escape sequences + let inner = &slice[1..slice.len() - 1]; + let mut result = String::new(); + let mut chars = inner.chars().peekable(); + + while let Some(c) = chars.next() { + if c == '\\' { + match chars.next() { + Some('n') => result.push('\n'), + Some('t') => result.push('\t'), + Some('r') => result.push('\r'), + Some('"') => result.push('"'), + Some('\'') => result.push('\''), + Some('\\') => result.push('\\'), + _ => return None, + } + } else { + result.push(c); + } + } + + Some(result) +} + +#[derive(Logos, Debug, Clone, PartialEq)] +#[logos(skip r"[ \t\n\r\f]+")] +#[logos(skip r"//[^\n]*")] +#[logos(skip r"/\*[^*]*\*+(?:[^/*][^*]*\*+)*/")] +pub enum Token { + // === Keywords - Control Flow === + #[token("to")] + To, + + #[token("give")] + Give, + + #[token("back")] + Back, + + #[token("remember")] + Remember, + + #[token("when")] + When, + + #[token("otherwise")] + Otherwise, + + #[token("repeat")] + Repeat, + + #[token("times")] + Times, + + // === Keywords - Consent & Safety === + #[token("only")] + Only, + + #[token("if")] + If, + + #[token("okay")] + Okay, + + #[token("attempt")] + Attempt, + + #[token("safely")] + Safely, + + #[token("reassure")] + Reassure, + + #[token("complain")] + Complain, + + // === Keywords - Gratitude === + #[token("thanks")] + Thanks, + + // === Keywords - Lifecycle === + #[token("hello")] + Hello, + + #[token("goodbye")] + Goodbye, + + // === Keywords - Concurrency === + #[token("worker")] + Worker, + + #[token("side")] + Side, + + #[token("quest")] + Quest, + + #[token("superpower")] + Superpower, + + #[token("spawn")] + Spawn, + + // === Keywords - Pattern Matching === + #[token("decide")] + Decide, + + #[token("based")] + Based, + + #[token("on")] + On, + + // === Keywords - Units === + #[token("measured")] + Measured, + + #[token("in")] + In, + + // === Keywords - Module === + #[token("use")] + Use, + + #[token("renamed")] + Renamed, + + // === Keywords - Types === + #[token("type")] + Type, + + #[token("const")] + Const, + + #[token("String")] + TypeString, + + #[token("Int")] + TypeInt, + + #[token("Float")] + TypeFloat, + + #[token("Bool")] + TypeBool, + + #[token("Maybe")] + Maybe, + + // === Keywords - Constraints === + #[token("must")] + Must, + + #[token("have")] + Have, + + // === Keywords - Pragmas === + #[token("care")] + Care, + + #[token("strict")] + Strict, + + #[token("verbose")] + Verbose, + + // === Keywords - Boolean === + #[token("true")] + True, + + #[token("false")] + False, + + #[token("and")] + And, + + #[token("or")] + Or, + + #[token("not")] + Not, + + // === Operators === + #[token("+")] + Plus, + + #[token("-")] + Minus, + + #[token("*")] + Star, + + #[token("/")] + Slash, + + #[token("%")] + Percent, + + #[token("==")] + EqualEqual, + + #[token("!=")] + BangEqual, + + #[token("<")] + Less, + + #[token(">")] + Greater, + + #[token("<=")] + LessEqual, + + #[token(">=")] + GreaterEqual, + + #[token("=")] + Equal, + + #[token("→")] + Arrow, + + #[token("->")] + AsciiArrow, + + // === Delimiters === + #[token("(")] + LParen, + + #[token(")")] + RParen, + + #[token("{")] + LBrace, + + #[token("}")] + RBrace, + + #[token("[")] + LBracket, + + #[token("]")] + RBracket, + + #[token(",")] + Comma, + + #[token(";")] + Semicolon, + + #[token(":")] + Colon, + + #[token(".")] + Dot, + + #[token("@")] + At, + + #[token("&")] + Ampersand, + + #[token("|")] + Pipe, + + #[token("#")] + Hash, + + #[token("_")] + Underscore, + + // === Literals === + #[regex(r"[0-9]+", |lex| lex.slice().parse::().ok())] + Integer(i64), + + #[regex(r"[0-9]+\.[0-9]+", |lex| lex.slice().parse::().ok())] + Float(f64), + + #[regex(r#""([^"\\]|\\.)*""#, parse_string)] + String(String), + + // === Identifiers === + #[regex(r"[a-zA-Z][a-zA-Z0-9_]*", |lex| lex.slice().to_string())] + Identifier(String), + + // === Special === + Eof, +} + +impl std::fmt::Display for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Token::To => write!(f, "to"), + Token::Give => write!(f, "give"), + Token::Back => write!(f, "back"), + Token::Remember => write!(f, "remember"), + Token::When => write!(f, "when"), + Token::Otherwise => write!(f, "otherwise"), + Token::Repeat => write!(f, "repeat"), + Token::Times => write!(f, "times"), + Token::Only => write!(f, "only"), + Token::If => write!(f, "if"), + Token::Okay => write!(f, "okay"), + Token::Attempt => write!(f, "attempt"), + Token::Safely => write!(f, "safely"), + Token::Reassure => write!(f, "reassure"), + Token::Complain => write!(f, "complain"), + Token::Thanks => write!(f, "thanks"), + Token::Hello => write!(f, "hello"), + Token::Goodbye => write!(f, "goodbye"), + Token::Worker => write!(f, "worker"), + Token::Side => write!(f, "side"), + Token::Quest => write!(f, "quest"), + Token::Superpower => write!(f, "superpower"), + Token::Spawn => write!(f, "spawn"), + Token::Decide => write!(f, "decide"), + Token::Based => write!(f, "based"), + Token::On => write!(f, "on"), + Token::Measured => write!(f, "measured"), + Token::In => write!(f, "in"), + Token::Use => write!(f, "use"), + Token::Renamed => write!(f, "renamed"), + Token::Type => write!(f, "type"), + Token::Const => write!(f, "const"), + Token::TypeString => write!(f, "String"), + Token::TypeInt => write!(f, "Int"), + Token::TypeFloat => write!(f, "Float"), + Token::TypeBool => write!(f, "Bool"), + Token::Maybe => write!(f, "Maybe"), + Token::Must => write!(f, "must"), + Token::Have => write!(f, "have"), + Token::Care => write!(f, "care"), + Token::Strict => write!(f, "strict"), + Token::Verbose => write!(f, "verbose"), + Token::True => write!(f, "true"), + Token::False => write!(f, "false"), + Token::And => write!(f, "and"), + Token::Or => write!(f, "or"), + Token::Not => write!(f, "not"), + Token::Plus => write!(f, "+"), + Token::Minus => write!(f, "-"), + Token::Star => write!(f, "*"), + Token::Slash => write!(f, "/"), + Token::Percent => write!(f, "%"), + Token::EqualEqual => write!(f, "=="), + Token::BangEqual => write!(f, "!="), + Token::Less => write!(f, "<"), + Token::Greater => write!(f, ">"), + Token::LessEqual => write!(f, "<="), + Token::GreaterEqual => write!(f, ">="), + Token::Equal => write!(f, "="), + Token::Arrow => write!(f, "→"), + Token::AsciiArrow => write!(f, "->"), + Token::LParen => write!(f, "("), + Token::RParen => write!(f, ")"), + Token::LBrace => write!(f, "{{"), + Token::RBrace => write!(f, "}}"), + Token::LBracket => write!(f, "["), + Token::RBracket => write!(f, "]"), + Token::Comma => write!(f, ","), + Token::Semicolon => write!(f, ";"), + Token::Colon => write!(f, ":"), + Token::Dot => write!(f, "."), + Token::At => write!(f, "@"), + Token::Ampersand => write!(f, "&"), + Token::Pipe => write!(f, "|"), + Token::Hash => write!(f, "#"), + Token::Underscore => write!(f, "_"), + Token::Integer(n) => write!(f, "{}", n), + Token::Float(n) => write!(f, "{}", n), + Token::String(s) => write!(f, "\"{}\"", s), + Token::Identifier(s) => write!(f, "{}", s), + Token::Eof => write!(f, "EOF"), + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b28e53e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +pub mod ast; +pub mod interpreter; +pub mod lexer; +pub mod parser; + +pub use ast::Program; +pub use interpreter::Interpreter; +pub use lexer::Lexer; +pub use parser::Parser; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..76ce4cf --- /dev/null +++ b/src/main.rs @@ -0,0 +1,84 @@ +use miette::Result; +use std::env; +use std::fs; +use wokelang::{Interpreter, Lexer, Parser}; + +fn main() -> Result<()> { + let args: Vec = env::args().collect(); + + if args.len() < 2 { + println!("WokeLang v0.1.0 - A human-centered, consent-driven programming language"); + println!(); + println!("Usage: woke Run a WokeLang program"); + println!(" woke --tokenize Show lexer tokens"); + println!(" woke --parse Show parsed AST"); + return Ok(()); + } + + let (mode, file_path) = match args.get(1).map(|s| s.as_str()) { + Some("--tokenize") => ("tokenize", args.get(2)), + Some("--parse") => ("parse", args.get(2)), + Some(_) => ("run", Some(&args[1])), + None => { + eprintln!("Expected file path"); + return Ok(()); + } + }; + + let file_path = match file_path { + Some(p) => p, + None => { + eprintln!("Expected file path after flag"); + return Ok(()); + } + }; + + let source = fs::read_to_string(file_path).expect("Failed to read file"); + let lexer = Lexer::new(&source); + + let tokens = match lexer.tokenize() { + Ok(t) => t, + Err(e) => { + eprintln!("{:?}", miette::Report::new(e)); + return Ok(()); + } + }; + + match mode { + "tokenize" => { + for token in &tokens { + println!("{:?} @ {:?}", token.value, token.span); + } + println!("\nTokenized {} tokens successfully.", tokens.len()); + } + "parse" => { + let mut parser = Parser::new(tokens, &source); + match parser.parse() { + Ok(program) => { + println!("{:#?}", program); + println!("\nParsed {} top-level items successfully.", program.items.len()); + } + Err(e) => { + eprintln!("{:?}", miette::Report::new(e)); + } + } + } + "run" => { + let mut parser = Parser::new(tokens, &source); + match parser.parse() { + Ok(program) => { + let mut interpreter = Interpreter::new(); + if let Err(e) = interpreter.run(&program) { + eprintln!("Runtime error: {}", e); + } + } + Err(e) => { + eprintln!("{:?}", miette::Report::new(e)); + } + } + } + _ => unreachable!(), + } + + Ok(()) +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..362ad28 --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,1291 @@ +use crate::ast::*; +use crate::lexer::{Spanned as LexSpanned, Token}; +use miette::{Diagnostic, SourceSpan}; +use thiserror::Error; + +#[derive(Error, Debug, Diagnostic)] +pub enum ParseError { + #[error("Unexpected token: expected {expected}, found {found}")] + #[diagnostic(code(wokelang::parser::unexpected_token))] + UnexpectedToken { + expected: String, + found: String, + #[source_code] + src: String, + #[label("here")] + span: SourceSpan, + }, + + #[error("Unexpected end of input")] + #[diagnostic(code(wokelang::parser::unexpected_eof))] + UnexpectedEof, + + #[error("{message}")] + #[diagnostic(code(wokelang::parser::general))] + General { + message: String, + #[source_code] + src: String, + #[label("here")] + span: SourceSpan, + }, +} + +pub struct Parser<'src> { + tokens: Vec>, + pos: usize, + source: &'src str, +} + +impl<'src> Parser<'src> { + pub fn new(tokens: Vec>, source: &'src str) -> Self { + Self { + tokens, + pos: 0, + source, + } + } + + pub fn parse(&mut self) -> Result { + let mut items = Vec::new(); + while !self.is_at_end() { + items.push(self.parse_top_level_item()?); + } + Ok(Program { items }) + } + + fn parse_top_level_item(&mut self) -> Result { + match self.peek() { + Some(Token::To) => Ok(TopLevelItem::Function(self.parse_function_def(None)?)), + Some(Token::At) => { + let emote = self.parse_emote_tag()?; + self.expect(Token::To)?; + Ok(TopLevelItem::Function(self.parse_function_def(Some(emote))?)) + } + Some(Token::Only) => Ok(TopLevelItem::ConsentBlock(self.parse_consent_block()?)), + Some(Token::Thanks) => Ok(TopLevelItem::GratitudeDecl(self.parse_gratitude_decl()?)), + Some(Token::Worker) => Ok(TopLevelItem::WorkerDef(self.parse_worker_def()?)), + Some(Token::Side) => Ok(TopLevelItem::SideQuestDef(self.parse_side_quest_def()?)), + Some(Token::Superpower) => { + Ok(TopLevelItem::SuperpowerDecl(self.parse_superpower_decl()?)) + } + Some(Token::Use) => Ok(TopLevelItem::ModuleImport(self.parse_module_import()?)), + Some(Token::Hash) => Ok(TopLevelItem::Pragma(self.parse_pragma()?)), + Some(Token::Type) => Ok(TopLevelItem::TypeDef(self.parse_type_def()?)), + Some(Token::Const) => Ok(TopLevelItem::ConstDef(self.parse_const_def()?)), + _ => Err(self.error("Expected top-level item")), + } + } + + // === Function Parsing === + + fn parse_function_def(&mut self, emote: Option) -> Result { + let start = self.current_span().start; + + // 'to' already consumed if emote was present, otherwise consume it + if emote.is_none() { + self.expect(Token::To)?; + } + + let name = self.expect_identifier()?; + self.expect(Token::LParen)?; + let params = self.parse_parameter_list()?; + self.expect(Token::RParen)?; + + let return_type = if self.check(&Token::Arrow) || self.check(&Token::AsciiArrow) { + self.advance(); + Some(self.parse_type()?) + } else { + None + }; + + self.expect(Token::LBrace)?; + + let hello = if self.check(&Token::Hello) { + self.advance(); + let msg = self.expect_string()?; + self.expect(Token::Semicolon)?; + Some(msg) + } else { + None + }; + + let mut body = Vec::new(); + while !self.check(&Token::Goodbye) && !self.check(&Token::RBrace) { + body.push(self.parse_statement()?); + } + + let goodbye = if self.check(&Token::Goodbye) { + self.advance(); + let msg = self.expect_string()?; + self.expect(Token::Semicolon)?; + Some(msg) + } else { + None + }; + + let end = self.current_span().end; + self.expect(Token::RBrace)?; + + Ok(FunctionDef { + emote, + name, + params, + return_type, + hello, + body, + goodbye, + span: start..end, + }) + } + + fn parse_parameter_list(&mut self) -> Result, ParseError> { + let mut params = Vec::new(); + if self.check(&Token::RParen) { + return Ok(params); + } + + params.push(self.parse_parameter()?); + while self.check(&Token::Comma) { + self.advance(); + params.push(self.parse_parameter()?); + } + + Ok(params) + } + + fn parse_parameter(&mut self) -> Result { + let start = self.current_span().start; + let name = self.expect_identifier()?; + let ty = if self.check(&Token::Colon) { + self.advance(); + Some(self.parse_type()?) + } else { + None + }; + let end = self.previous_span().end; + Ok(Parameter { + name, + ty, + span: start..end, + }) + } + + // === Consent Block === + + fn parse_consent_block(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Only)?; + self.expect(Token::If)?; + self.expect(Token::Okay)?; + let permission = self.expect_string()?; + self.expect(Token::LBrace)?; + let body = self.parse_statement_list()?; + let end = self.current_span().end; + self.expect(Token::RBrace)?; + + Ok(ConsentBlock { + permission, + body, + span: start..end, + }) + } + + // === Gratitude === + + fn parse_gratitude_decl(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Thanks)?; + self.expect(Token::To)?; + self.expect(Token::LBrace)?; + + let mut entries = Vec::new(); + while !self.check(&Token::RBrace) { + entries.push(self.parse_gratitude_entry()?); + } + + let end = self.current_span().end; + self.expect(Token::RBrace)?; + + Ok(GratitudeDecl { + entries, + span: start..end, + }) + } + + fn parse_gratitude_entry(&mut self) -> Result { + let start = self.current_span().start; + let recipient = self.expect_string()?; + if !self.check(&Token::Arrow) && !self.check(&Token::AsciiArrow) { + return Err(self.error("Expected → or ->")); + } + self.advance(); + let reason = self.expect_string()?; + let end = self.current_span().end; + self.expect(Token::Semicolon)?; + + Ok(GratitudeEntry { + recipient, + reason, + span: start..end, + }) + } + + // === Worker/Side Quest/Superpower === + + fn parse_worker_def(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Worker)?; + let name = self.expect_identifier()?; + self.expect(Token::LBrace)?; + let body = self.parse_statement_list()?; + let end = self.current_span().end; + self.expect(Token::RBrace)?; + + Ok(WorkerDef { + name, + body, + span: start..end, + }) + } + + fn parse_side_quest_def(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Side)?; + self.expect(Token::Quest)?; + let name = self.expect_identifier()?; + self.expect(Token::LBrace)?; + let body = self.parse_statement_list()?; + let end = self.current_span().end; + self.expect(Token::RBrace)?; + + Ok(SideQuestDef { + name, + body, + span: start..end, + }) + } + + fn parse_superpower_decl(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Superpower)?; + let name = self.expect_identifier()?; + self.expect(Token::LBrace)?; + let body = self.parse_statement_list()?; + let end = self.current_span().end; + self.expect(Token::RBrace)?; + + Ok(SuperpowerDecl { + name, + body, + span: start..end, + }) + } + + // === Module Import === + + fn parse_module_import(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Use)?; + let path = self.parse_qualified_name()?; + let rename = if self.check(&Token::Renamed) { + self.advance(); + Some(self.expect_identifier()?) + } else { + None + }; + let end = self.current_span().end; + self.expect(Token::Semicolon)?; + + Ok(ModuleImport { + path, + rename, + span: start..end, + }) + } + + fn parse_qualified_name(&mut self) -> Result { + let start = self.current_span().start; + let mut parts = vec![self.expect_identifier()?]; + while self.check(&Token::Dot) { + self.advance(); + parts.push(self.expect_identifier()?); + } + let end = self.previous_span().end; + Ok(QualifiedName { + parts, + span: start..end, + }) + } + + // === Pragma === + + fn parse_pragma(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Hash)?; + + let directive = match self.peek() { + Some(Token::Care) => { + self.advance(); + PragmaDirective::Care + } + Some(Token::Strict) => { + self.advance(); + PragmaDirective::Strict + } + Some(Token::Verbose) => { + self.advance(); + PragmaDirective::Verbose + } + _ => return Err(self.error("Expected pragma directive (care, strict, verbose)")), + }; + + let enabled = if self.check(&Token::Identifier(String::new())) { + match self.peek() { + Some(Token::Identifier(s)) if s == "on" => { + self.advance(); + true + } + Some(Token::Identifier(s)) if s == "off" => { + self.advance(); + false + } + _ => return Err(self.error("Expected 'on' or 'off'")), + } + } else { + return Err(self.error("Expected 'on' or 'off'")); + }; + + let end = self.current_span().end; + self.expect(Token::Semicolon)?; + + Ok(Pragma { + directive, + enabled, + span: start..end, + }) + } + + // === Type Definition === + + fn parse_type_def(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Type)?; + let name = self.expect_identifier()?; + self.expect(Token::Equal)?; + + let definition = if self.check(&Token::LBrace) { + self.advance(); + let fields = self.parse_field_list()?; + self.expect(Token::RBrace)?; + TypeVariant::Struct(fields) + } else { + // Check if it's an enum (has |) or an alias + let first_type = self.parse_type()?; + if self.check(&Token::Pipe) { + let mut variants = vec![Variant { + name: match &first_type { + Type::Basic(n) => n.clone(), + _ => return Err(self.error("Enum variant must be an identifier")), + }, + fields: vec![], + }]; + while self.check(&Token::Pipe) { + self.advance(); + variants.push(self.parse_variant()?); + } + TypeVariant::Enum(variants) + } else { + TypeVariant::Alias(first_type) + } + }; + + let end = self.current_span().end; + self.expect(Token::Semicolon)?; + + Ok(TypeDef { + name, + definition, + span: start..end, + }) + } + + fn parse_field_list(&mut self) -> Result, ParseError> { + let mut fields = Vec::new(); + if self.check(&Token::RBrace) { + return Ok(fields); + } + + fields.push(self.parse_field()?); + while self.check(&Token::Comma) { + self.advance(); + if self.check(&Token::RBrace) { + break; + } + fields.push(self.parse_field()?); + } + + Ok(fields) + } + + fn parse_field(&mut self) -> Result { + let name = self.expect_identifier()?; + self.expect(Token::Colon)?; + let ty = self.parse_type()?; + Ok(Field { name, ty }) + } + + fn parse_variant(&mut self) -> Result { + let name = self.expect_identifier()?; + let fields = if self.check(&Token::LParen) { + self.advance(); + let mut types = vec![self.parse_type()?]; + while self.check(&Token::Comma) { + self.advance(); + types.push(self.parse_type()?); + } + self.expect(Token::RParen)?; + types + } else { + vec![] + }; + Ok(Variant { name, fields }) + } + + // === Const Definition === + + fn parse_const_def(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Const)?; + let name = self.expect_identifier()?; + self.expect(Token::Colon)?; + let ty = self.parse_type()?; + self.expect(Token::Equal)?; + let value = self.parse_expression()?; + let end = self.current_span().end; + self.expect(Token::Semicolon)?; + + Ok(ConstDef { + name, + ty, + value, + span: start..end, + }) + } + + // === Type Parsing === + + fn parse_type(&mut self) -> Result { + if self.check(&Token::LBracket) { + self.advance(); + let inner = self.parse_type()?; + self.expect(Token::RBracket)?; + return Ok(Type::Array(Box::new(inner))); + } + + if self.check(&Token::Maybe) { + self.advance(); + let inner = self.parse_type()?; + return Ok(Type::Optional(Box::new(inner))); + } + + if self.check(&Token::Ampersand) { + self.advance(); + let inner = self.parse_type()?; + return Ok(Type::Reference(Box::new(inner))); + } + + match self.peek() { + Some(Token::TypeString) => { + self.advance(); + Ok(Type::Basic("String".to_string())) + } + Some(Token::TypeInt) => { + self.advance(); + Ok(Type::Basic("Int".to_string())) + } + Some(Token::TypeFloat) => { + self.advance(); + Ok(Type::Basic("Float".to_string())) + } + Some(Token::TypeBool) => { + self.advance(); + Ok(Type::Basic("Bool".to_string())) + } + Some(Token::Identifier(name)) => { + let name = name.clone(); + self.advance(); + Ok(Type::Basic(name)) + } + _ => Err(self.error("Expected type")), + } + } + + // === Statement Parsing === + + fn parse_statement_list(&mut self) -> Result, ParseError> { + let mut stmts = Vec::new(); + while !self.check(&Token::RBrace) && !self.is_at_end() { + stmts.push(self.parse_statement()?); + } + Ok(stmts) + } + + fn parse_statement(&mut self) -> Result { + // Check for emote-annotated statement + if self.check(&Token::At) { + let emote = self.parse_emote_tag()?; + let stmt = self.parse_statement()?; + let span = emote.span.start..self.previous_span().end; + return Ok(Statement::EmoteAnnotated(EmoteAnnotatedStmt { + emote, + statement: Box::new(stmt), + span, + })); + } + + match self.peek() { + Some(Token::Remember) => self.parse_var_decl(), + Some(Token::Give) => self.parse_return_stmt(), + Some(Token::When) => self.parse_conditional(), + Some(Token::Repeat) => self.parse_loop(), + Some(Token::Attempt) => self.parse_attempt_block(), + Some(Token::Only) => Ok(Statement::ConsentBlock(self.parse_consent_block()?)), + Some(Token::Spawn) => self.parse_worker_spawn(), + Some(Token::Complain) => self.parse_complain_stmt(), + Some(Token::Decide) => self.parse_decide_stmt(), + Some(Token::Identifier(_)) => { + // Could be assignment or expression + let start = self.current_span().start; + let expr = self.parse_expression()?; + + // Check if this is an assignment + if self.check(&Token::Equal) { + if let Expr::Identifier(name) = &expr.node { + let name = name.clone(); + self.advance(); // consume '=' + let value = self.parse_expression()?; + let end = self.current_span().end; + self.expect(Token::Semicolon)?; + return Ok(Statement::Assignment(Assignment { + target: name, + value, + span: start..end, + })); + } + } + + self.expect(Token::Semicolon)?; + Ok(Statement::Expression(expr)) + } + _ => { + let expr = self.parse_expression()?; + self.expect(Token::Semicolon)?; + Ok(Statement::Expression(expr)) + } + } + } + + fn parse_var_decl(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Remember)?; + let name = self.expect_identifier()?; + self.expect(Token::Equal)?; + let value = self.parse_expression()?; + + let unit = if self.check(&Token::Measured) { + self.advance(); + self.expect(Token::In)?; + Some(self.expect_identifier()?) + } else { + None + }; + + let end = self.current_span().end; + self.expect(Token::Semicolon)?; + + Ok(Statement::VarDecl(VarDecl { + name, + value, + unit, + span: start..end, + })) + } + + fn parse_return_stmt(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Give)?; + self.expect(Token::Back)?; + let value = self.parse_expression()?; + let end = self.current_span().end; + self.expect(Token::Semicolon)?; + + Ok(Statement::Return(ReturnStmt { + value, + span: start..end, + })) + } + + fn parse_conditional(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::When)?; + let condition = self.parse_expression()?; + self.expect(Token::LBrace)?; + let then_branch = self.parse_statement_list()?; + self.expect(Token::RBrace)?; + + let else_branch = if self.check(&Token::Otherwise) { + self.advance(); + self.expect(Token::LBrace)?; + let stmts = self.parse_statement_list()?; + self.expect(Token::RBrace)?; + Some(stmts) + } else { + None + }; + + let end = self.previous_span().end; + + Ok(Statement::Conditional(Conditional { + condition, + then_branch, + else_branch, + span: start..end, + })) + } + + fn parse_loop(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Repeat)?; + let count = self.parse_expression()?; + self.expect(Token::Times)?; + self.expect(Token::LBrace)?; + let body = self.parse_statement_list()?; + let end = self.current_span().end; + self.expect(Token::RBrace)?; + + Ok(Statement::Loop(Loop { + count, + body, + span: start..end, + })) + } + + fn parse_attempt_block(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Attempt)?; + self.expect(Token::Safely)?; + self.expect(Token::LBrace)?; + let body = self.parse_statement_list()?; + self.expect(Token::RBrace)?; + self.expect(Token::Or)?; + self.expect(Token::Reassure)?; + let reassurance = self.expect_string()?; + let end = self.current_span().end; + self.expect(Token::Semicolon)?; + + Ok(Statement::AttemptBlock(AttemptBlock { + body, + reassurance, + span: start..end, + })) + } + + fn parse_worker_spawn(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Spawn)?; + self.expect(Token::Worker)?; + let worker_name = self.expect_identifier()?; + let end = self.current_span().end; + self.expect(Token::Semicolon)?; + + Ok(Statement::WorkerSpawn(WorkerSpawn { + worker_name, + span: start..end, + })) + } + + fn parse_complain_stmt(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Complain)?; + let message = self.expect_string()?; + let end = self.current_span().end; + self.expect(Token::Semicolon)?; + + Ok(Statement::Complain(ComplainStmt { + message, + span: start..end, + })) + } + + fn parse_decide_stmt(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::Decide)?; + self.expect(Token::Based)?; + self.expect(Token::On)?; + let scrutinee = self.parse_expression()?; + self.expect(Token::LBrace)?; + + let mut arms = Vec::new(); + while !self.check(&Token::RBrace) { + arms.push(self.parse_match_arm()?); + } + + let end = self.current_span().end; + self.expect(Token::RBrace)?; + + Ok(Statement::Decide(DecideStmt { + scrutinee, + arms, + span: start..end, + })) + } + + fn parse_match_arm(&mut self) -> Result { + let start = self.current_span().start; + let pattern = self.parse_pattern()?; + + if !self.check(&Token::Arrow) && !self.check(&Token::AsciiArrow) { + return Err(self.error("Expected → or ->")); + } + self.advance(); + + self.expect(Token::LBrace)?; + let body = self.parse_statement_list()?; + let end = self.current_span().end; + self.expect(Token::RBrace)?; + + Ok(MatchArm { + pattern, + body, + span: start..end, + }) + } + + fn parse_pattern(&mut self) -> Result { + match self.peek() { + Some(Token::Underscore) => { + self.advance(); + Ok(Pattern::Wildcard) + } + Some(Token::Integer(n)) => { + let n = *n; + self.advance(); + Ok(Pattern::Literal(Literal::Integer(n))) + } + Some(Token::Float(n)) => { + let n = *n; + self.advance(); + Ok(Pattern::Literal(Literal::Float(n))) + } + Some(Token::String(s)) => { + let s = s.clone(); + self.advance(); + Ok(Pattern::Literal(Literal::String(s))) + } + Some(Token::True) => { + self.advance(); + Ok(Pattern::Literal(Literal::Bool(true))) + } + Some(Token::False) => { + self.advance(); + Ok(Pattern::Literal(Literal::Bool(false))) + } + Some(Token::Identifier(name)) => { + let name = name.clone(); + self.advance(); + Ok(Pattern::Identifier(name)) + } + _ => Err(self.error("Expected pattern")), + } + } + + // === Emote Tag === + + fn parse_emote_tag(&mut self) -> Result { + let start = self.current_span().start; + self.expect(Token::At)?; + let name = self.expect_identifier()?; + + let params = if self.check(&Token::LParen) { + self.advance(); + let mut params = Vec::new(); + if !self.check(&Token::RParen) { + params.push(self.parse_emote_param()?); + while self.check(&Token::Comma) { + self.advance(); + params.push(self.parse_emote_param()?); + } + } + self.expect(Token::RParen)?; + params + } else { + vec![] + }; + + let end = self.previous_span().end; + + Ok(EmoteTag { + name, + params, + span: start..end, + }) + } + + fn parse_emote_param(&mut self) -> Result { + let name = self.expect_identifier()?; + self.expect(Token::Equal)?; + + let value = match self.peek() { + Some(Token::Integer(n)) => { + let n = *n; + self.advance(); + EmoteValue::Number(n as f64) + } + Some(Token::Float(n)) => { + let n = *n; + self.advance(); + EmoteValue::Number(n) + } + Some(Token::String(s)) => { + let s = s.clone(); + self.advance(); + EmoteValue::String(s) + } + Some(Token::Identifier(s)) => { + let s = s.clone(); + self.advance(); + EmoteValue::Identifier(s) + } + _ => return Err(self.error("Expected emote parameter value")), + }; + + Ok(EmoteParam { name, value }) + } + + // === Expression Parsing (Pratt parser style) === + + fn parse_expression(&mut self) -> Result, ParseError> { + self.parse_or() + } + + fn parse_or(&mut self) -> Result, ParseError> { + let mut left = self.parse_and()?; + + while self.check(&Token::Or) { + self.advance(); + let right = self.parse_and()?; + let span = left.span.start..right.span.end; + left = Spanned::new( + Expr::Binary(BinaryOp::Or, Box::new(left), Box::new(right)), + span, + ); + } + + Ok(left) + } + + fn parse_and(&mut self) -> Result, ParseError> { + let mut left = self.parse_equality()?; + + while self.check(&Token::And) { + self.advance(); + let right = self.parse_equality()?; + let span = left.span.start..right.span.end; + left = Spanned::new( + Expr::Binary(BinaryOp::And, Box::new(left), Box::new(right)), + span, + ); + } + + Ok(left) + } + + fn parse_equality(&mut self) -> Result, ParseError> { + let mut left = self.parse_comparison()?; + + loop { + let op = match self.peek() { + Some(Token::EqualEqual) => BinaryOp::Eq, + Some(Token::BangEqual) => BinaryOp::NotEq, + _ => break, + }; + self.advance(); + let right = self.parse_comparison()?; + let span = left.span.start..right.span.end; + left = Spanned::new(Expr::Binary(op, Box::new(left), Box::new(right)), span); + } + + Ok(left) + } + + fn parse_comparison(&mut self) -> Result, ParseError> { + let mut left = self.parse_additive()?; + + loop { + let op = match self.peek() { + Some(Token::Less) => BinaryOp::Lt, + Some(Token::Greater) => BinaryOp::Gt, + Some(Token::LessEqual) => BinaryOp::LtEq, + Some(Token::GreaterEqual) => BinaryOp::GtEq, + _ => break, + }; + self.advance(); + let right = self.parse_additive()?; + let span = left.span.start..right.span.end; + left = Spanned::new(Expr::Binary(op, Box::new(left), Box::new(right)), span); + } + + Ok(left) + } + + fn parse_additive(&mut self) -> Result, ParseError> { + let mut left = self.parse_multiplicative()?; + + loop { + let op = match self.peek() { + Some(Token::Plus) => BinaryOp::Add, + Some(Token::Minus) => BinaryOp::Sub, + _ => break, + }; + self.advance(); + let right = self.parse_multiplicative()?; + let span = left.span.start..right.span.end; + left = Spanned::new(Expr::Binary(op, Box::new(left), Box::new(right)), span); + } + + Ok(left) + } + + fn parse_multiplicative(&mut self) -> Result, ParseError> { + let mut left = self.parse_unary()?; + + loop { + let op = match self.peek() { + Some(Token::Star) => BinaryOp::Mul, + Some(Token::Slash) => BinaryOp::Div, + Some(Token::Percent) => BinaryOp::Mod, + _ => break, + }; + self.advance(); + let right = self.parse_unary()?; + let span = left.span.start..right.span.end; + left = Spanned::new(Expr::Binary(op, Box::new(left), Box::new(right)), span); + } + + Ok(left) + } + + fn parse_unary(&mut self) -> Result, ParseError> { + match self.peek() { + Some(Token::Not) => { + let start = self.current_span().start; + self.advance(); + let expr = self.parse_unary()?; + let end = expr.span.end; + Ok(Spanned::new( + Expr::Unary(UnaryOp::Not, Box::new(expr)), + start..end, + )) + } + Some(Token::Minus) => { + let start = self.current_span().start; + self.advance(); + let expr = self.parse_unary()?; + let end = expr.span.end; + Ok(Spanned::new( + Expr::Unary(UnaryOp::Neg, Box::new(expr)), + start..end, + )) + } + _ => self.parse_postfix(), + } + } + + fn parse_postfix(&mut self) -> Result, ParseError> { + let mut expr = self.parse_primary()?; + + // Handle unit measurement: expr measured in unit + if self.check(&Token::Measured) { + self.advance(); + self.expect(Token::In)?; + let unit = self.expect_identifier()?; + let span = expr.span.start..self.previous_span().end; + expr = Spanned::new(Expr::UnitMeasurement(Box::new(expr), unit), span); + } + + Ok(expr) + } + + fn parse_primary(&mut self) -> Result, ParseError> { + let start = self.current_span().start; + + match self.peek().cloned() { + Some(Token::Integer(n)) => { + self.advance(); + let end = self.previous_span().end; + Ok(Spanned::new(Expr::Literal(Literal::Integer(n)), start..end)) + } + Some(Token::Float(n)) => { + self.advance(); + let end = self.previous_span().end; + Ok(Spanned::new(Expr::Literal(Literal::Float(n)), start..end)) + } + Some(Token::String(s)) => { + self.advance(); + let end = self.previous_span().end; + Ok(Spanned::new(Expr::Literal(Literal::String(s)), start..end)) + } + Some(Token::True) => { + self.advance(); + let end = self.previous_span().end; + Ok(Spanned::new(Expr::Literal(Literal::Bool(true)), start..end)) + } + Some(Token::False) => { + self.advance(); + let end = self.previous_span().end; + Ok(Spanned::new( + Expr::Literal(Literal::Bool(false)), + start..end, + )) + } + Some(Token::Thanks) => { + self.advance(); + self.expect(Token::LParen)?; + let name = self.expect_string()?; + self.expect(Token::RParen)?; + let end = self.previous_span().end; + Ok(Spanned::new(Expr::GratitudeLiteral(name), start..end)) + } + Some(Token::LBracket) => { + self.advance(); + let mut elements = Vec::new(); + if !self.check(&Token::RBracket) { + elements.push(self.parse_expression()?); + while self.check(&Token::Comma) { + self.advance(); + if self.check(&Token::RBracket) { + break; + } + elements.push(self.parse_expression()?); + } + } + self.expect(Token::RBracket)?; + let end = self.previous_span().end; + Ok(Spanned::new(Expr::Array(elements), start..end)) + } + Some(Token::LParen) => { + self.advance(); + let expr = self.parse_expression()?; + self.expect(Token::RParen)?; + Ok(expr) + } + Some(Token::Identifier(name)) => { + self.advance(); + if self.check(&Token::LParen) { + // Function call + self.advance(); + let mut args = Vec::new(); + if !self.check(&Token::RParen) { + args.push(self.parse_expression()?); + while self.check(&Token::Comma) { + self.advance(); + args.push(self.parse_expression()?); + } + } + self.expect(Token::RParen)?; + let end = self.previous_span().end; + Ok(Spanned::new(Expr::Call(name, args), start..end)) + } else { + let end = self.previous_span().end; + Ok(Spanned::new(Expr::Identifier(name), start..end)) + } + } + _ => Err(self.error("Expected expression")), + } + } + + // === Helper Methods === + + fn peek(&self) -> Option<&Token> { + self.tokens.get(self.pos).map(|t| &t.value) + } + + fn check(&self, token: &Token) -> bool { + match (self.peek(), token) { + (Some(Token::Identifier(_)), Token::Identifier(_)) => true, + (Some(a), b) => std::mem::discriminant(a) == std::mem::discriminant(b), + _ => false, + } + } + + fn advance(&mut self) -> Option<&Token> { + if !self.is_at_end() { + self.pos += 1; + } + self.tokens.get(self.pos - 1).map(|t| &t.value) + } + + fn expect(&mut self, token: Token) -> Result<(), ParseError> { + if self.check(&token) { + self.advance(); + Ok(()) + } else { + let found = self + .peek() + .map(|t| t.to_string()) + .unwrap_or_else(|| "EOF".to_string()); + Err(ParseError::UnexpectedToken { + expected: token.to_string(), + found, + src: self.source.to_string(), + span: self.current_span().into(), + }) + } + } + + fn expect_identifier(&mut self) -> Result { + match self.peek().cloned() { + Some(Token::Identifier(name)) => { + self.advance(); + Ok(name) + } + _ => { + let found = self + .peek() + .map(|t| t.to_string()) + .unwrap_or_else(|| "EOF".to_string()); + Err(ParseError::UnexpectedToken { + expected: "identifier".to_string(), + found, + src: self.source.to_string(), + span: self.current_span().into(), + }) + } + } + } + + fn expect_string(&mut self) -> Result { + match self.peek().cloned() { + Some(Token::String(s)) => { + self.advance(); + Ok(s) + } + _ => { + let found = self + .peek() + .map(|t| t.to_string()) + .unwrap_or_else(|| "EOF".to_string()); + Err(ParseError::UnexpectedToken { + expected: "string".to_string(), + found, + src: self.source.to_string(), + span: self.current_span().into(), + }) + } + } + } + + fn is_at_end(&self) -> bool { + matches!(self.peek(), Some(Token::Eof) | None) + } + + fn current_span(&self) -> std::ops::Range { + self.tokens + .get(self.pos) + .map(|t| t.span.clone()) + .unwrap_or(0..0) + } + + fn previous_span(&self) -> std::ops::Range { + if self.pos > 0 { + self.tokens + .get(self.pos - 1) + .map(|t| t.span.clone()) + .unwrap_or(0..0) + } else { + 0..0 + } + } + + fn error(&self, message: &str) -> ParseError { + ParseError::General { + message: message.to_string(), + src: self.source.to_string(), + span: self.current_span().into(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lexer::Lexer; + + fn parse(source: &str) -> Result { + let lexer = Lexer::new(source); + let tokens = lexer.tokenize().expect("Lexer failed"); + let mut parser = Parser::new(tokens, source); + parser.parse() + } + + #[test] + fn test_parse_simple_function() { + let source = r#"to greet() { + give back "Hello"; + }"#; + let program = parse(source).unwrap(); + assert_eq!(program.items.len(), 1); + assert!(matches!(program.items[0], TopLevelItem::Function(_))); + } + + #[test] + fn test_parse_function_with_params() { + let source = r#"to add(a: Int, b: Int) -> Int { + give back a + b; + }"#; + let program = parse(source).unwrap(); + if let TopLevelItem::Function(f) = &program.items[0] { + assert_eq!(f.name, "add"); + assert_eq!(f.params.len(), 2); + } + } + + #[test] + fn test_parse_gratitude() { + let source = r#"thanks to { + "Rust" -> "For being awesome"; + }"#; + let program = parse(source).unwrap(); + assert!(matches!(program.items[0], TopLevelItem::GratitudeDecl(_))); + } + + #[test] + fn test_parse_consent_block() { + let source = r#"only if okay "camera" { + remember x = 1; + }"#; + let program = parse(source).unwrap(); + assert!(matches!(program.items[0], TopLevelItem::ConsentBlock(_))); + } + + #[test] + fn test_parse_worker() { + let source = r#"worker background { + remember x = compute(); + }"#; + let program = parse(source).unwrap(); + assert!(matches!(program.items[0], TopLevelItem::WorkerDef(_))); + } + + #[test] + fn test_parse_expressions() { + let source = r#"to test() { + remember x = 1 + 2 * 3; + remember y = (1 + 2) * 3; + remember z = a and b or c; + }"#; + let program = parse(source).unwrap(); + assert!(matches!(program.items[0], TopLevelItem::Function(_))); + } +} From f016fd0ea409b8269051ceb77243c6470d75063b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Dec 2025 03:51:24 +0000 Subject: [PATCH 2/2] Add REPL, WASM compilation, and Zig FFI REPL (src/repl.rs): - Interactive command-line interface with rustyline - Commands: :help, :quit, :clear, :reset, :load, :ast - Expression evaluation with automatic result printing - Start with `woke` or `woke --repl` WASM Compilation (src/codegen/): - Compile WokeLang to WebAssembly binary format - Uses wasm-encoder for proper WASM generation - Supports functions, expressions, loops, conditionals - Pattern matching compilation - CLI: `woke -c input.woke` outputs input.wasm Zig FFI (src/ffi/, zig/, include/): - C-compatible API for embedding WokeLang - Interpreter lifecycle: woke_interpreter_new/free - Code execution: woke_exec, woke_eval - Value operations: type checking, conversion, creation - Static library (libwokelang.a) and shared library (.so) - C header (include/wokelang.h) - Zig bindings (zig/wokelang.zig) with idiomatic wrapper - Example Zig program and build.zig Other: - examples/math.woke - math functions for WASM demo - Cargo.toml updated for cdylib/staticlib targets --- Cargo.lock | 281 +++++++++++++++++++++++- Cargo.toml | 6 + examples/math.wasm | Bin 0 -> 253 bytes examples/math.woke | 36 ++++ include/wokelang.h | 213 ++++++++++++++++++ src/codegen/mod.rs | 3 + src/codegen/wasm.rs | 509 ++++++++++++++++++++++++++++++++++++++++++++ src/ffi/c_api.rs | 323 ++++++++++++++++++++++++++++ src/ffi/mod.rs | 8 + src/lib.rs | 8 + src/main.rs | 85 +++++++- src/repl.rs | 234 ++++++++++++++++++++ zig/build.zig | 46 ++++ zig/example.zig | 61 ++++++ zig/wokelang.zig | 229 ++++++++++++++++++++ 15 files changed, 2027 insertions(+), 15 deletions(-) create mode 100644 examples/math.wasm create mode 100644 examples/math.woke create mode 100644 include/wokelang.h create mode 100644 src/codegen/mod.rs create mode 100644 src/codegen/wasm.rs create mode 100644 src/ffi/c_api.rs create mode 100644 src/ffi/mod.rs create mode 100644 src/repl.rs create mode 100644 zig/build.zig create mode 100644 zig/example.zig create mode 100644 zig/wokelang.zig diff --git a/Cargo.lock b/Cargo.lock index 71b3283..6768fcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,12 +59,39 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.14" @@ -75,6 +102,23 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "fnv" version = "1.0.7" @@ -87,6 +131,31 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_ci" version = "1.2.0" @@ -99,6 +168,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" version = "0.2.178" @@ -111,6 +186,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "logos" version = "0.14.4" @@ -189,6 +270,27 @@ dependencies = [ "adler2", ] +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "object" version = "0.37.3" @@ -232,6 +334,16 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "regex-syntax" version = "0.8.8" @@ -257,6 +369,40 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustyline" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" +dependencies = [ + "bitflags", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "unicode-segmentation", + "unicode-width 0.1.14", + "utf8parse", + "windows-sys 0.52.0", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "supports-color" version = "3.0.2" @@ -341,6 +487,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.14" @@ -353,19 +505,64 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasm-encoder" +version = "0.221.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc8444fe4920de80a4fe5ab564fff2ae58b6b73166b89751f8c6c93509da32e5" +dependencies = [ + "leb128", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.221.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06bfa36ab3ac2be0dee563380147a5b81ba10dd8885d7fbbc9eb574be67d185" +dependencies = [ + "bitflags", + "indexmap", + "semver", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.5", ] [[package]] @@ -377,6 +574,22 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + [[package]] name = "windows-targets" version = "0.53.5" @@ -384,58 +597,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "windows_x86_64_msvc" version = "0.53.1" @@ -449,7 +710,9 @@ dependencies = [ "logos", "miette", "pretty_assertions", + "rustyline", "thiserror", + "wasm-encoder", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5172001..2263d7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/hyperpolymath/wokelang" [lib] name = "wokelang" path = "src/lib.rs" +crate-type = ["lib", "cdylib", "staticlib"] [[bin]] name = "woke" @@ -18,6 +19,11 @@ path = "src/main.rs" logos = "0.14" thiserror = "1.0" miette = { version = "7.0", features = ["fancy"] } +rustyline = "14.0" +wasm-encoder = "0.221" + +[features] +default = [] [dev-dependencies] pretty_assertions = "1.4" diff --git a/examples/math.wasm b/examples/math.wasm new file mode 100644 index 0000000000000000000000000000000000000000..eafd92c1fbb9211718f7fc647375cae3cbac681a GIT binary patch literal 253 zcmX|)!EVAp3`A$_qy@HBI9EL^SMI3SX#b!mj&vJ@6e%GUgaZ=YzuH?TT$LKPTVT?ky0QK0&fs(xn{ha_v4c!^?dPzMP18ar z$5X!0D1 hqojZZJr-i6QfuusVEs%_FBc Int { + give back a + b; +} + +to multiply(a: Int, b: Int) -> Int { + give back a * b; +} + +to factorial(n: Int) -> Int { + when n <= 1 { + give back 1; + } + give back n * factorial(n - 1); +} + +to fibonacci(n: Int) -> Int { + when n <= 0 { + give back 0; + } + when n == 1 { + give back 1; + } + give back fibonacci(n - 1) + fibonacci(n - 2); +} + +to sum_to_n(n: Int) -> Int { + remember total = 0; + remember i = n; + repeat n times { + total = total + i; + i = i - 1; + } + give back total; +} diff --git a/include/wokelang.h b/include/wokelang.h new file mode 100644 index 0000000..78bf1e4 --- /dev/null +++ b/include/wokelang.h @@ -0,0 +1,213 @@ +/** + * WokeLang C API + * + * This header provides the C-compatible interface for WokeLang. + * It can be used from C, C++, Zig, or any language supporting the C ABI. + * + * Example usage: + * + * #include "wokelang.h" + * + * int main() { + * WokeInterpreter* interp = woke_interpreter_new(); + * if (!interp) return 1; + * + * const char* code = + * "to greet(name: String) -> String {\n" + * " give back \"Hello, \" + name + \"!\";\n" + * "}\n"; + * + * WokeResult result = woke_exec(interp, code); + * if (result != WOKE_OK) { + * printf("Error: %d\n", result); + * } + * + * woke_interpreter_free(interp); + * return 0; + * } + */ + +#ifndef WOKELANG_H +#define WOKELANG_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Opaque types */ +typedef struct WokeInterpreter WokeInterpreter; +typedef struct WokeValue WokeValue; + +/* Result codes */ +typedef enum WokeResult { + WOKE_OK = 0, + WOKE_ERROR = 1, + WOKE_PARSE_ERROR = 2, + WOKE_RUNTIME_ERROR = 3, + WOKE_NULL_POINTER = 4 +} WokeResult; + +/* Value type tags */ +typedef enum WokeValueType { + WOKE_TYPE_INT = 0, + WOKE_TYPE_FLOAT = 1, + WOKE_TYPE_STRING = 2, + WOKE_TYPE_BOOL = 3, + WOKE_TYPE_ARRAY = 4, + WOKE_TYPE_UNIT = 5 +} WokeValueType; + +/* === Interpreter lifecycle === */ + +/** + * Create a new WokeLang interpreter. + * + * @return Pointer to the interpreter, or NULL on failure. + * The caller is responsible for freeing with woke_interpreter_free. + */ +WokeInterpreter* woke_interpreter_new(void); + +/** + * Free a WokeLang interpreter. + * + * @param interp The interpreter to free. May be NULL (no-op). + */ +void woke_interpreter_free(WokeInterpreter* interp); + +/** + * Execute WokeLang source code. + * + * @param interp The interpreter instance. + * @param source Null-terminated WokeLang source code. + * @return WOKE_OK on success, error code otherwise. + */ +WokeResult woke_exec(WokeInterpreter* interp, const char* source); + +/** + * Evaluate an expression and get the result. + * + * @param interp The interpreter instance. + * @param source Null-terminated expression to evaluate. + * @param out_value Pointer to receive the result value. + * @return WOKE_OK on success, error code otherwise. + */ +WokeResult woke_eval(WokeInterpreter* interp, const char* source, WokeValue** out_value); + +/* === Value operations === */ + +/** + * Free a WokeValue. + * + * @param value The value to free. May be NULL (no-op). + */ +void woke_value_free(WokeValue* value); + +/** + * Get the type of a WokeValue. + * + * @param value The value to inspect. + * @return The type tag. + */ +WokeValueType woke_value_type(const WokeValue* value); + +/** + * Get an integer from a WokeValue. + * + * @param value The value to read. + * @param out Pointer to receive the integer. + * @return WOKE_OK on success, WOKE_ERROR if type mismatch. + */ +WokeResult woke_value_as_int(const WokeValue* value, int64_t* out); + +/** + * Get a float from a WokeValue. + * + * @param value The value to read. + * @param out Pointer to receive the float. + * @return WOKE_OK on success, WOKE_ERROR if type mismatch. + */ +WokeResult woke_value_as_float(const WokeValue* value, double* out); + +/** + * Get a boolean from a WokeValue. + * + * @param value The value to read. + * @param out Pointer to receive the boolean (0 or 1). + * @return WOKE_OK on success, WOKE_ERROR if type mismatch. + */ +WokeResult woke_value_as_bool(const WokeValue* value, int* out); + +/** + * Get a string from a WokeValue. + * + * @param value The value to read. + * @return Newly allocated string, or NULL on error. + * Caller must free with woke_string_free. + */ +char* woke_value_as_string(const WokeValue* value); + +/** + * Free a string returned by woke_value_as_string. + * + * @param s The string to free. May be NULL (no-op). + */ +void woke_string_free(char* s); + +/* === Value creation === */ + +/** + * Create an integer WokeValue. + * + * @param n The integer value. + * @return New value, or NULL on allocation failure. + */ +WokeValue* woke_value_from_int(int64_t n); + +/** + * Create a float WokeValue. + * + * @param f The float value. + * @return New value, or NULL on allocation failure. + */ +WokeValue* woke_value_from_float(double f); + +/** + * Create a boolean WokeValue. + * + * @param b The boolean value (0 = false, nonzero = true). + * @return New value, or NULL on allocation failure. + */ +WokeValue* woke_value_from_bool(int b); + +/** + * Create a string WokeValue. + * + * @param s Null-terminated string to copy. + * @return New value, or NULL on allocation failure. + */ +WokeValue* woke_value_from_string(const char* s); + +/* === Utility === */ + +/** + * Get the WokeLang version string. + * + * @return Static version string (do not free). + */ +const char* woke_version(void); + +/** + * Get the last error message. + * + * @return Error message, or NULL if no error. + * Valid until the next woke_* call. + */ +const char* woke_last_error(void); + +#ifdef __cplusplus +} +#endif + +#endif /* WOKELANG_H */ diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs new file mode 100644 index 0000000..6e89c9c --- /dev/null +++ b/src/codegen/mod.rs @@ -0,0 +1,3 @@ +mod wasm; + +pub use wasm::WasmCompiler; diff --git a/src/codegen/wasm.rs b/src/codegen/wasm.rs new file mode 100644 index 0000000..1604086 --- /dev/null +++ b/src/codegen/wasm.rs @@ -0,0 +1,509 @@ +use crate::ast::*; +use std::collections::HashMap; +use thiserror::Error; +use wasm_encoder::{ + CodeSection, ExportKind, ExportSection, Function, FunctionSection, Instruction, Module, + TypeSection, ValType, +}; + +#[derive(Error, Debug)] +pub enum CompileError { + #[error("Unsupported feature: {0}")] + Unsupported(String), + + #[error("Undefined variable: {0}")] + UndefinedVariable(String), + + #[error("Undefined function: {0}")] + UndefinedFunction(String), + + #[error("Type error: {0}")] + TypeError(String), +} + +type Result = std::result::Result; + +/// Compiles WokeLang to WebAssembly +pub struct WasmCompiler { + /// Function name to index mapping + functions: HashMap, + /// Function signatures (param count, return count) + signatures: HashMap, Vec)>, + /// Local variable mappings per function + locals: HashMap, + /// Current local index + local_index: u32, +} + +impl WasmCompiler { + pub fn new() -> Self { + Self { + functions: HashMap::new(), + signatures: HashMap::new(), + locals: HashMap::new(), + local_index: 0, + } + } + + /// Compile a WokeLang program to WASM binary + pub fn compile(&mut self, program: &Program) -> Result> { + let mut module = Module::new(); + + // Collect function definitions first + let mut func_defs: Vec<&FunctionDef> = Vec::new(); + for item in &program.items { + if let TopLevelItem::Function(f) = item { + func_defs.push(f); + } + } + + // Build type section (function signatures) + let mut types = TypeSection::new(); + for (idx, func) in func_defs.iter().enumerate() { + let params: Vec = func.params.iter().map(|_| ValType::I64).collect(); + let results: Vec = if func.return_type.is_some() { + vec![ValType::I64] + } else { + vec![] + }; + + types.ty().function(params.clone(), results.clone()); + self.functions.insert(func.name.clone(), idx as u32); + self.signatures + .insert(func.name.clone(), (params, results)); + } + module.section(&types); + + // Build function section (type indices) + let mut functions = FunctionSection::new(); + for idx in 0..func_defs.len() { + functions.function(idx as u32); + } + module.section(&functions); + + // Build export section + let mut exports = ExportSection::new(); + for (name, idx) in &self.functions { + exports.export(name, ExportKind::Func, *idx); + } + module.section(&exports); + + // Build code section + let mut codes = CodeSection::new(); + for func in &func_defs { + let wasm_func = self.compile_function(func)?; + codes.function(&wasm_func); + } + module.section(&codes); + + Ok(module.finish()) + } + + fn compile_function(&mut self, func: &FunctionDef) -> Result { + self.locals.clear(); + self.local_index = 0; + + // Register parameters as locals + for param in &func.params { + self.locals.insert(param.name.clone(), self.local_index); + self.local_index += 1; + } + + // Count additional locals needed + let additional_locals = self.count_locals(&func.body); + + let mut wasm_func = Function::new(vec![(additional_locals, ValType::I64)]); + + // Compile function body + for stmt in &func.body { + self.compile_statement(stmt, &mut wasm_func)?; + } + + // Add implicit return if no explicit return + if func.return_type.is_none() { + wasm_func.instruction(&Instruction::End); + } else { + // If there's a return type but no return statement, push 0 + wasm_func.instruction(&Instruction::End); + } + + Ok(wasm_func) + } + + fn count_locals(&self, stmts: &[Statement]) -> u32 { + let mut count = 0; + for stmt in stmts { + match stmt { + Statement::VarDecl(_) => count += 1, + Statement::Conditional(c) => { + count += self.count_locals(&c.then_branch); + if let Some(else_branch) = &c.else_branch { + count += self.count_locals(else_branch); + } + } + Statement::Loop(l) => { + count += self.count_locals(&l.body); + } + Statement::AttemptBlock(a) => { + count += self.count_locals(&a.body); + } + _ => {} + } + } + count + } + + fn compile_statement(&mut self, stmt: &Statement, func: &mut Function) -> Result<()> { + match stmt { + Statement::VarDecl(decl) => { + // Compile the value expression + self.compile_expr(&decl.value, func)?; + + // Store in local + let local_idx = self.local_index; + self.locals.insert(decl.name.clone(), local_idx); + self.local_index += 1; + + func.instruction(&Instruction::LocalSet(local_idx)); + } + + Statement::Assignment(assign) => { + // Compile the value expression + self.compile_expr(&assign.value, func)?; + + // Store in local + let local_idx = *self + .locals + .get(&assign.target) + .ok_or_else(|| CompileError::UndefinedVariable(assign.target.clone()))?; + + func.instruction(&Instruction::LocalSet(local_idx)); + } + + Statement::Return(ret) => { + self.compile_expr(&ret.value, func)?; + func.instruction(&Instruction::Return); + } + + Statement::Conditional(cond) => { + // Compile condition + self.compile_expr(&cond.condition, func)?; + + // If-else block + func.instruction(&Instruction::If(wasm_encoder::BlockType::Empty)); + + for s in &cond.then_branch { + self.compile_statement(s, func)?; + } + + if let Some(else_branch) = &cond.else_branch { + func.instruction(&Instruction::Else); + for s in else_branch { + self.compile_statement(s, func)?; + } + } + + func.instruction(&Instruction::End); + } + + Statement::Loop(loop_stmt) => { + // Compile loop count + self.compile_expr(&loop_stmt.count, func)?; + + // Store count in a local + let count_local = self.local_index; + self.local_index += 1; + func.instruction(&Instruction::LocalSet(count_local)); + + // Loop structure + func.instruction(&Instruction::Block(wasm_encoder::BlockType::Empty)); + func.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty)); + + // Check if count > 0 + func.instruction(&Instruction::LocalGet(count_local)); + func.instruction(&Instruction::I64Const(0)); + func.instruction(&Instruction::I64LeS); + func.instruction(&Instruction::BrIf(1)); // Break out if count <= 0 + + // Execute body + for s in &loop_stmt.body { + self.compile_statement(s, func)?; + } + + // Decrement counter + func.instruction(&Instruction::LocalGet(count_local)); + func.instruction(&Instruction::I64Const(1)); + func.instruction(&Instruction::I64Sub); + func.instruction(&Instruction::LocalSet(count_local)); + + // Continue loop + func.instruction(&Instruction::Br(0)); + + func.instruction(&Instruction::End); // End loop + func.instruction(&Instruction::End); // End block + } + + Statement::Expression(expr) => { + self.compile_expr(expr, func)?; + func.instruction(&Instruction::Drop); // Discard result + } + + Statement::ConsentBlock(_) => { + // Consent blocks are runtime-only, skip in WASM + // Could be implemented with host imports + } + + Statement::AttemptBlock(attempt) => { + // Try-catch can be implemented with WASM exception handling + // For now, just compile the body + for s in &attempt.body { + self.compile_statement(s, func)?; + } + } + + Statement::Complain(_) => { + // Would need host import for console output + } + + Statement::WorkerSpawn(_) => { + return Err(CompileError::Unsupported( + "Workers not supported in WASM".into(), + )); + } + + Statement::EmoteAnnotated(annotated) => { + // Emote tags are metadata, compile the inner statement + self.compile_statement(&annotated.statement, func)?; + } + + Statement::Decide(decide) => { + // Pattern matching - simplified to if-else chain + self.compile_expr(&decide.scrutinee, func)?; + let scrutinee_local = self.local_index; + self.local_index += 1; + func.instruction(&Instruction::LocalSet(scrutinee_local)); + + for (i, arm) in decide.arms.iter().enumerate() { + let is_last = i == decide.arms.len() - 1; + + match &arm.pattern { + Pattern::Wildcard => { + // Wildcard always matches + for s in &arm.body { + self.compile_statement(s, func)?; + } + break; + } + Pattern::Literal(lit) => { + // Compare with literal + func.instruction(&Instruction::LocalGet(scrutinee_local)); + self.compile_literal(lit, func)?; + func.instruction(&Instruction::I64Eq); + + func.instruction(&Instruction::If(wasm_encoder::BlockType::Empty)); + for s in &arm.body { + self.compile_statement(s, func)?; + } + + if !is_last { + func.instruction(&Instruction::Else); + } + } + Pattern::Identifier(name) => { + // Bind the value to the identifier + func.instruction(&Instruction::LocalGet(scrutinee_local)); + let bind_local = self.local_index; + self.locals.insert(name.clone(), bind_local); + self.local_index += 1; + func.instruction(&Instruction::LocalSet(bind_local)); + + for s in &arm.body { + self.compile_statement(s, func)?; + } + break; + } + } + } + + // Close all if blocks + for arm in &decide.arms { + if !matches!(arm.pattern, Pattern::Wildcard | Pattern::Identifier(_)) { + func.instruction(&Instruction::End); + } + } + } + } + + Ok(()) + } + + fn compile_expr(&mut self, expr: &Spanned, func: &mut Function) -> Result<()> { + match &expr.node { + Expr::Literal(lit) => { + self.compile_literal(lit, func)?; + } + + Expr::Identifier(name) => { + let local_idx = *self + .locals + .get(name) + .ok_or_else(|| CompileError::UndefinedVariable(name.clone()))?; + func.instruction(&Instruction::LocalGet(local_idx)); + } + + Expr::Binary(op, left, right) => { + self.compile_expr(left, func)?; + self.compile_expr(right, func)?; + + match op { + BinaryOp::Add => func.instruction(&Instruction::I64Add), + BinaryOp::Sub => func.instruction(&Instruction::I64Sub), + BinaryOp::Mul => func.instruction(&Instruction::I64Mul), + BinaryOp::Div => func.instruction(&Instruction::I64DivS), + BinaryOp::Mod => func.instruction(&Instruction::I64RemS), + BinaryOp::Eq => func.instruction(&Instruction::I64Eq), + BinaryOp::NotEq => func.instruction(&Instruction::I64Ne), + BinaryOp::Lt => func.instruction(&Instruction::I64LtS), + BinaryOp::Gt => func.instruction(&Instruction::I64GtS), + BinaryOp::LtEq => func.instruction(&Instruction::I64LeS), + BinaryOp::GtEq => func.instruction(&Instruction::I64GeS), + BinaryOp::And => func.instruction(&Instruction::I64And), + BinaryOp::Or => func.instruction(&Instruction::I64Or), + }; + } + + Expr::Unary(op, operand) => { + match op { + UnaryOp::Neg => { + func.instruction(&Instruction::I64Const(0)); + self.compile_expr(operand, func)?; + func.instruction(&Instruction::I64Sub); + } + UnaryOp::Not => { + self.compile_expr(operand, func)?; + func.instruction(&Instruction::I64Eqz); + } + }; + } + + Expr::Call(name, args) => { + // Compile arguments + for arg in args { + self.compile_expr(arg, func)?; + } + + // Call function + let func_idx = *self + .functions + .get(name) + .ok_or_else(|| CompileError::UndefinedFunction(name.clone()))?; + func.instruction(&Instruction::Call(func_idx)); + } + + Expr::Array(_) => { + return Err(CompileError::Unsupported( + "Arrays not yet supported in WASM compilation".into(), + )); + } + + Expr::UnitMeasurement(inner, _) => { + // Just compile the inner expression, ignore units + self.compile_expr(inner, func)?; + } + + Expr::GratitudeLiteral(_) => { + // Push 0 as placeholder + func.instruction(&Instruction::I64Const(0)); + } + } + + Ok(()) + } + + fn compile_literal(&self, lit: &Literal, func: &mut Function) -> Result<()> { + match lit { + Literal::Integer(n) => { + func.instruction(&Instruction::I64Const(*n)); + } + Literal::Float(f) => { + // Convert to i64 bits for now (simplified) + func.instruction(&Instruction::I64Const(f.to_bits() as i64)); + } + Literal::Bool(b) => { + func.instruction(&Instruction::I64Const(if *b { 1 } else { 0 })); + } + Literal::String(_) => { + // Strings would need memory allocation + // For now, push 0 as placeholder + func.instruction(&Instruction::I64Const(0)); + } + } + Ok(()) + } +} + +impl Default for WasmCompiler { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lexer::Lexer; + use crate::parser::Parser; + + fn compile(source: &str) -> Result> { + let lexer = Lexer::new(source); + let tokens = lexer.tokenize().expect("Lexer failed"); + let mut parser = Parser::new(tokens, source); + let program = parser.parse().expect("Parser failed"); + let mut compiler = WasmCompiler::new(); + compiler.compile(&program) + } + + #[test] + fn test_compile_simple_function() { + let source = r#" + to add(a: Int, b: Int) -> Int { + give back a + b; + } + "#; + let wasm = compile(source).unwrap(); + assert!(!wasm.is_empty()); + // WASM magic number + assert_eq!(&wasm[0..4], b"\0asm"); + } + + #[test] + fn test_compile_factorial() { + let source = r#" + to factorial(n: Int) -> Int { + when n <= 1 { + give back 1; + } + give back n * factorial(n - 1); + } + "#; + let wasm = compile(source).unwrap(); + assert!(!wasm.is_empty()); + } + + #[test] + fn test_compile_loop() { + let source = r#" + to sum_to_n(n: Int) -> Int { + remember total = 0; + remember i = n; + repeat n times { + total = total + i; + i = i - 1; + } + give back total; + } + "#; + let wasm = compile(source).unwrap(); + assert!(!wasm.is_empty()); + } +} diff --git a/src/ffi/c_api.rs b/src/ffi/c_api.rs new file mode 100644 index 0000000..3a4e4ef --- /dev/null +++ b/src/ffi/c_api.rs @@ -0,0 +1,323 @@ +//! C-compatible API for WokeLang +//! +//! This module provides extern "C" functions that can be called from Zig, C, +//! or any language supporting the C ABI. + +use crate::interpreter::{Interpreter, Value}; +use crate::lexer::Lexer; +use crate::parser::Parser; +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_double, c_int, c_longlong}; +use std::ptr; + +/// Opaque handle to a WokeLang interpreter instance +pub struct WokeInterpreter { + inner: Interpreter, +} + +/// Opaque handle to a WokeLang value +pub struct WokeValue { + inner: Value, +} + +/// Result code for FFI operations +#[repr(C)] +pub enum WokeResult { + Ok = 0, + Error = 1, + ParseError = 2, + RuntimeError = 3, + NullPointer = 4, +} + +/// Value type tag for FFI +#[repr(C)] +pub enum WokeValueType { + Int = 0, + Float = 1, + String = 2, + Bool = 3, + Array = 4, + Unit = 5, +} + +// === Interpreter lifecycle === + +/// Create a new WokeLang interpreter +/// +/// Returns a pointer to the interpreter, or null on failure. +/// The caller is responsible for freeing with `woke_interpreter_free`. +#[no_mangle] +pub extern "C" fn woke_interpreter_new() -> *mut WokeInterpreter { + Box::into_raw(Box::new(WokeInterpreter { + inner: Interpreter::new(), + })) +} + +/// Free a WokeLang interpreter +/// +/// # Safety +/// The pointer must be valid and not null. +#[no_mangle] +pub unsafe extern "C" fn woke_interpreter_free(interp: *mut WokeInterpreter) { + if !interp.is_null() { + drop(Box::from_raw(interp)); + } +} + +/// Execute WokeLang source code +/// +/// # Safety +/// - `interp` must be a valid pointer from `woke_interpreter_new` +/// - `source` must be a valid null-terminated C string +#[no_mangle] +pub unsafe extern "C" fn woke_exec(interp: *mut WokeInterpreter, source: *const c_char) -> WokeResult { + if interp.is_null() || source.is_null() { + return WokeResult::NullPointer; + } + + let interp = &mut *interp; + let source = match CStr::from_ptr(source).to_str() { + Ok(s) => s, + Err(_) => return WokeResult::Error, + }; + + let lexer = Lexer::new(source); + let tokens = match lexer.tokenize() { + Ok(t) => t, + Err(_) => return WokeResult::ParseError, + }; + + let mut parser = Parser::new(tokens, source); + let program = match parser.parse() { + Ok(p) => p, + Err(_) => return WokeResult::ParseError, + }; + + match interp.inner.run(&program) { + Ok(_) => WokeResult::Ok, + Err(_) => WokeResult::RuntimeError, + } +} + +/// Execute WokeLang source and get return value +/// +/// # Safety +/// - All pointers must be valid +/// - The returned WokeValue must be freed with `woke_value_free` +#[no_mangle] +pub unsafe extern "C" fn woke_eval( + interp: *mut WokeInterpreter, + source: *const c_char, + out_value: *mut *mut WokeValue, +) -> WokeResult { + if interp.is_null() || source.is_null() || out_value.is_null() { + return WokeResult::NullPointer; + } + + let interp = &mut *interp; + let source = match CStr::from_ptr(source).to_str() { + Ok(s) => s, + Err(_) => return WokeResult::Error, + }; + + // Wrap the expression in a function that returns it + let wrapped = format!( + "to __ffi_eval__() {{ give back {}; }} to main() {{ __ffi_eval__(); }}", + source.trim_end_matches(';') + ); + + let lexer = Lexer::new(&wrapped); + let tokens = match lexer.tokenize() { + Ok(t) => t, + Err(_) => return WokeResult::ParseError, + }; + + let mut parser = Parser::new(tokens, &wrapped); + let program = match parser.parse() { + Ok(p) => p, + Err(_) => return WokeResult::ParseError, + }; + + match interp.inner.run(&program) { + Ok(_) => { + // Return unit value for now (full implementation would capture return value) + *out_value = Box::into_raw(Box::new(WokeValue { inner: Value::Unit })); + WokeResult::Ok + } + Err(_) => WokeResult::RuntimeError, + } +} + +// === Value operations === + +/// Free a WokeValue +/// +/// # Safety +/// The pointer must be valid and from a woke_* function. +#[no_mangle] +pub unsafe extern "C" fn woke_value_free(value: *mut WokeValue) { + if !value.is_null() { + drop(Box::from_raw(value)); + } +} + +/// Get the type of a WokeValue +#[no_mangle] +pub unsafe extern "C" fn woke_value_type(value: *const WokeValue) -> WokeValueType { + if value.is_null() { + return WokeValueType::Unit; + } + + match &(*value).inner { + Value::Int(_) => WokeValueType::Int, + Value::Float(_) => WokeValueType::Float, + Value::String(_) => WokeValueType::String, + Value::Bool(_) => WokeValueType::Bool, + Value::Array(_) => WokeValueType::Array, + Value::Unit => WokeValueType::Unit, + } +} + +/// Get an integer from a WokeValue +#[no_mangle] +pub unsafe extern "C" fn woke_value_as_int(value: *const WokeValue, out: *mut c_longlong) -> WokeResult { + if value.is_null() || out.is_null() { + return WokeResult::NullPointer; + } + + match &(*value).inner { + Value::Int(n) => { + *out = *n; + WokeResult::Ok + } + _ => WokeResult::Error, + } +} + +/// Get a float from a WokeValue +#[no_mangle] +pub unsafe extern "C" fn woke_value_as_float(value: *const WokeValue, out: *mut c_double) -> WokeResult { + if value.is_null() || out.is_null() { + return WokeResult::NullPointer; + } + + match &(*value).inner { + Value::Float(f) => { + *out = *f; + WokeResult::Ok + } + Value::Int(n) => { + *out = *n as c_double; + WokeResult::Ok + } + _ => WokeResult::Error, + } +} + +/// Get a boolean from a WokeValue +#[no_mangle] +pub unsafe extern "C" fn woke_value_as_bool(value: *const WokeValue, out: *mut c_int) -> WokeResult { + if value.is_null() || out.is_null() { + return WokeResult::NullPointer; + } + + match &(*value).inner { + Value::Bool(b) => { + *out = if *b { 1 } else { 0 }; + WokeResult::Ok + } + _ => WokeResult::Error, + } +} + +/// Get a string from a WokeValue +/// +/// The returned string must be freed with `woke_string_free`. +#[no_mangle] +pub unsafe extern "C" fn woke_value_as_string(value: *const WokeValue) -> *mut c_char { + if value.is_null() { + return ptr::null_mut(); + } + + match &(*value).inner { + Value::String(s) => match CString::new(s.as_str()) { + Ok(cs) => cs.into_raw(), + Err(_) => ptr::null_mut(), + }, + other => match CString::new(other.to_string()) { + Ok(cs) => cs.into_raw(), + Err(_) => ptr::null_mut(), + }, + } +} + +/// Free a string returned by woke_value_as_string +#[no_mangle] +pub unsafe extern "C" fn woke_string_free(s: *mut c_char) { + if !s.is_null() { + drop(CString::from_raw(s)); + } +} + +// === Value creation === + +/// Create an integer WokeValue +#[no_mangle] +pub extern "C" fn woke_value_from_int(n: c_longlong) -> *mut WokeValue { + Box::into_raw(Box::new(WokeValue { + inner: Value::Int(n), + })) +} + +/// Create a float WokeValue +#[no_mangle] +pub extern "C" fn woke_value_from_float(f: c_double) -> *mut WokeValue { + Box::into_raw(Box::new(WokeValue { + inner: Value::Float(f), + })) +} + +/// Create a boolean WokeValue +#[no_mangle] +pub extern "C" fn woke_value_from_bool(b: c_int) -> *mut WokeValue { + Box::into_raw(Box::new(WokeValue { + inner: Value::Bool(b != 0), + })) +} + +/// Create a string WokeValue +/// +/// # Safety +/// `s` must be a valid null-terminated C string. +#[no_mangle] +pub unsafe extern "C" fn woke_value_from_string(s: *const c_char) -> *mut WokeValue { + if s.is_null() { + return ptr::null_mut(); + } + + match CStr::from_ptr(s).to_str() { + Ok(str) => Box::into_raw(Box::new(WokeValue { + inner: Value::String(str.to_string()), + })), + Err(_) => ptr::null_mut(), + } +} + +// === Utility === + +/// Get the WokeLang version string +#[no_mangle] +pub extern "C" fn woke_version() -> *const c_char { + static VERSION: &[u8] = b"0.1.0\0"; + VERSION.as_ptr() as *const c_char +} + +/// Get the last error message (if any) +/// +/// Returns null if no error. The returned string is valid until the next woke_* call. +#[no_mangle] +pub extern "C" fn woke_last_error() -> *const c_char { + // TODO: Implement thread-local error storage + ptr::null() +} diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs new file mode 100644 index 0000000..a3b09e6 --- /dev/null +++ b/src/ffi/mod.rs @@ -0,0 +1,8 @@ +//! Foreign Function Interface for WokeLang +//! +//! This module provides a C-compatible API that can be used from Zig, C, or any +//! language that supports the C ABI. + +mod c_api; + +pub use c_api::*; diff --git a/src/lib.rs b/src/lib.rs index b28e53e..db9f7c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,17 @@ pub mod ast; +pub mod codegen; +pub mod ffi; pub mod interpreter; pub mod lexer; pub mod parser; +pub mod repl; pub use ast::Program; +pub use codegen::WasmCompiler; pub use interpreter::Interpreter; pub use lexer::Lexer; pub use parser::Parser; +pub use repl::Repl; + +// Re-export FFI for cdylib +pub use ffi::*; diff --git a/src/main.rs b/src/main.rs index 76ce4cf..54a0ef6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,36 @@ use miette::Result; use std::env; use std::fs; -use wokelang::{Interpreter, Lexer, Parser}; +use std::path::Path; +use wokelang::{Interpreter, Lexer, Parser, Repl, WasmCompiler}; fn main() -> Result<()> { let args: Vec = env::args().collect(); if args.len() < 2 { - println!("WokeLang v0.1.0 - A human-centered, consent-driven programming language"); - println!(); - println!("Usage: woke Run a WokeLang program"); - println!(" woke --tokenize Show lexer tokens"); - println!(" woke --parse Show parsed AST"); + // No arguments - start REPL + let mut repl = Repl::new().expect("Failed to start REPL"); + repl.run().expect("REPL error"); return Ok(()); } let (mode, file_path) = match args.get(1).map(|s| s.as_str()) { + Some("--repl" | "-i") => { + let mut repl = Repl::new().expect("Failed to start REPL"); + repl.run().expect("REPL error"); + return Ok(()); + } + Some("--help" | "-h") => { + print_help(); + return Ok(()); + } + Some("--version" | "-v") => { + println!("WokeLang v0.1.0"); + return Ok(()); + } Some("--tokenize") => ("tokenize", args.get(2)), Some("--parse") => ("parse", args.get(2)), + Some("--compile-wasm" | "-c") => ("compile-wasm", args.get(2)), Some(_) => ("run", Some(&args[1])), None => { eprintln!("Expected file path"); @@ -63,6 +76,42 @@ fn main() -> Result<()> { } } } + "compile-wasm" => { + let mut parser = Parser::new(tokens, &source); + match parser.parse() { + Ok(program) => { + let mut compiler = WasmCompiler::new(); + match compiler.compile(&program) { + Ok(wasm_bytes) => { + // Determine output path + let input_path = Path::new(file_path); + let output_path = args + .get(3) + .map(|s| s.to_string()) + .unwrap_or_else(|| { + input_path + .with_extension("wasm") + .to_string_lossy() + .to_string() + }); + + fs::write(&output_path, &wasm_bytes).expect("Failed to write WASM file"); + println!( + "Compiled to {} ({} bytes)", + output_path, + wasm_bytes.len() + ); + } + Err(e) => { + eprintln!("Compile error: {}", e); + } + } + } + Err(e) => { + eprintln!("{:?}", miette::Report::new(e)); + } + } + } "run" => { let mut parser = Parser::new(tokens, &source); match parser.parse() { @@ -82,3 +131,27 @@ fn main() -> Result<()> { Ok(()) } + +fn print_help() { + println!("WokeLang v0.1.0 - A human-centered, consent-driven programming language"); + println!(); + println!("USAGE:"); + println!(" woke Start interactive REPL"); + println!(" woke Run a WokeLang program"); + println!(" woke --repl, -i Start interactive REPL"); + println!(" woke --compile-wasm, -c Compile to WebAssembly"); + println!(" woke --tokenize Show lexer tokens"); + println!(" woke --parse Show parsed AST"); + println!(" woke --help, -h Show this help"); + println!(" woke --version, -v Show version"); + println!(); + println!("REPL COMMANDS:"); + println!(" :help Show REPL commands"); + println!(" :quit Exit the REPL"); + println!(" :load Load a file"); + println!(" :reset Reset interpreter state"); + println!(); + println!("WASM COMPILATION:"); + println!(" woke -c input.woke Output to input.wasm"); + println!(" woke -c input.woke out.wasm Output to out.wasm"); +} diff --git a/src/repl.rs b/src/repl.rs new file mode 100644 index 0000000..bb910bc --- /dev/null +++ b/src/repl.rs @@ -0,0 +1,234 @@ +use crate::ast::TopLevelItem; +use crate::interpreter::Interpreter; +use crate::lexer::Lexer; +use crate::parser::Parser; +use rustyline::error::ReadlineError; +use rustyline::DefaultEditor; + +const BANNER: &str = r#" + __ __ _ _ + \ \ / /__ | | _____| | __ _ _ __ __ _ + \ \ /\ / / _ \| |/ / _ \ | / _` | '_ \ / _` | + \ V V / (_) | < __/ |__| (_| | | | | (_| | + \_/\_/ \___/|_|\_\___|_____\__,_|_| |_|\__, | + |___/ +"#; + +const HELP: &str = r#" +WokeLang REPL Commands: + :help, :h Show this help message + :quit, :q Exit the REPL + :clear, :c Clear the screen + :reset, :r Reset interpreter state + :load Load and run a file + :ast Show AST for an expression + :env Show current environment variables + +Examples: + remember x = 42; + print(x + 8); + to double(n) { give back n * 2; } + double(21) +"#; + +pub struct Repl { + interpreter: Interpreter, + editor: DefaultEditor, +} + +impl Repl { + pub fn new() -> Result> { + let editor = DefaultEditor::new()?; + Ok(Self { + interpreter: Interpreter::new(), + editor, + }) + } + + pub fn run(&mut self) -> Result<(), Box> { + println!("{}", BANNER); + println!("WokeLang v0.1.0 - Interactive REPL"); + println!("Type :help for commands, :quit to exit\n"); + + loop { + let readline = self.editor.readline("woke> "); + + match readline { + Ok(line) => { + let line = line.trim(); + if line.is_empty() { + continue; + } + + let _ = self.editor.add_history_entry(line); + + if line.starts_with(':') { + if self.handle_command(line)? { + break; + } + } else { + self.eval_input(line); + } + } + Err(ReadlineError::Interrupted) => { + println!("^C"); + continue; + } + Err(ReadlineError::Eof) => { + println!("\nGoodbye!"); + break; + } + Err(err) => { + eprintln!("Error: {:?}", err); + break; + } + } + } + + Ok(()) + } + + fn handle_command(&mut self, line: &str) -> Result> { + let parts: Vec<&str> = line.splitn(2, ' ').collect(); + let cmd = parts[0]; + let arg = parts.get(1).map(|s| s.trim()); + + match cmd { + ":quit" | ":q" => { + println!("Goodbye!"); + return Ok(true); + } + ":help" | ":h" => { + println!("{}", HELP); + } + ":clear" | ":c" => { + print!("\x1B[2J\x1B[1;1H"); + } + ":reset" | ":r" => { + self.interpreter = Interpreter::new(); + println!("Interpreter state reset."); + } + ":load" | ":l" => { + if let Some(path) = arg { + self.load_file(path); + } else { + println!("Usage: :load "); + } + } + ":ast" => { + if let Some(code) = arg { + self.show_ast(code); + } else { + println!("Usage: :ast "); + } + } + ":env" => { + println!("(Environment inspection not yet implemented)"); + } + _ => { + println!("Unknown command: {}. Type :help for available commands.", cmd); + } + } + + Ok(false) + } + + fn eval_input(&mut self, input: &str) { + // Try to parse as a program (statements/definitions) + let lexer = Lexer::new(input); + let tokens = match lexer.tokenize() { + Ok(t) => t, + Err(e) => { + eprintln!("Lexer error: {:?}", e); + return; + } + }; + + let mut parser = Parser::new(tokens.clone(), input); + + // First, try parsing as a full program + match parser.parse() { + Ok(program) => { + if let Err(e) = self.interpreter.run(&program) { + eprintln!("Runtime error: {}", e); + } else { + // Check if the last item was a simple expression, print its value + if let Some(TopLevelItem::Function(f)) = program.items.last() { + if f.name == "__repl_expr__" { + // This was a wrapped expression, value already printed + } + } + } + } + Err(_) => { + // Try wrapping as an expression in a function and evaluating + let wrapped = format!( + "to __repl_expr__() {{ remember __result__ = {}; print(__result__); }} + to main() {{ __repl_expr__(); }}", + input.trim_end_matches(';') + ); + + let lexer = Lexer::new(&wrapped); + if let Ok(tokens) = lexer.tokenize() { + let mut parser = Parser::new(tokens, &wrapped); + if let Ok(program) = parser.parse() { + if let Err(e) = self.interpreter.run(&program) { + // If that also fails, show original parse error + eprintln!("Error: {}", e); + } + } else { + eprintln!("Parse error in input"); + } + } + } + } + } + + fn load_file(&mut self, path: &str) { + match std::fs::read_to_string(path) { + Ok(source) => { + println!("Loading {}...", path); + let lexer = Lexer::new(&source); + match lexer.tokenize() { + Ok(tokens) => { + let mut parser = Parser::new(tokens, &source); + match parser.parse() { + Ok(program) => { + if let Err(e) = self.interpreter.run(&program) { + eprintln!("Runtime error: {}", e); + } else { + println!("Loaded successfully."); + } + } + Err(e) => eprintln!("Parse error: {:?}", e), + } + } + Err(e) => eprintln!("Lexer error: {:?}", e), + } + } + Err(e) => eprintln!("Could not read file: {}", e), + } + } + + fn show_ast(&self, code: &str) { + let lexer = Lexer::new(code); + match lexer.tokenize() { + Ok(tokens) => { + let mut parser = Parser::new(tokens, code); + match parser.parse() { + Ok(program) => { + println!("{:#?}", program); + } + Err(e) => eprintln!("Parse error: {:?}", e), + } + } + Err(e) => eprintln!("Lexer error: {:?}", e), + } + } +} + +impl Default for Repl { + fn default() -> Self { + Self::new().expect("Failed to create REPL") + } +} diff --git a/zig/build.zig b/zig/build.zig new file mode 100644 index 0000000..60af38b --- /dev/null +++ b/zig/build.zig @@ -0,0 +1,46 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // Example executable using WokeLang FFI + const exe = b.addExecutable(.{ + .name = "wokelang-zig-example", + .root_source_file = b.path("example.zig"), + .target = target, + .optimize = optimize, + }); + + // Link against the WokeLang library + exe.addLibraryPath(.{ .cwd_relative = "../target/release" }); + exe.linkSystemLibrary("wokelang"); + exe.linkLibC(); + + // Add include path for header + exe.addIncludePath(.{ .cwd_relative = "../include" }); + + b.installArtifact(exe); + + // Run command + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + const run_step = b.step("run", "Run the example"); + run_step.dependOn(&run_cmd.step); + + // Tests + const unit_tests = b.addTest(.{ + .root_source_file = b.path("wokelang.zig"), + .target = target, + .optimize = optimize, + }); + + unit_tests.addLibraryPath(.{ .cwd_relative = "../target/release" }); + unit_tests.linkSystemLibrary("wokelang"); + unit_tests.linkLibC(); + + const run_unit_tests = b.addRunArtifact(unit_tests); + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_unit_tests.step); +} diff --git a/zig/example.zig b/zig/example.zig new file mode 100644 index 0000000..41ec454 --- /dev/null +++ b/zig/example.zig @@ -0,0 +1,61 @@ +//! Example of using WokeLang from Zig +//! +//! Build with: zig build +//! Run with: zig build run + +const std = @import("std"); +const woke = @import("wokelang.zig"); + +pub fn main() !void { + const stdout = std.io.getStdOut().writer(); + + // Print version + try stdout.print("WokeLang version: {s}\n\n", .{woke.version()}); + + // Create interpreter + var interp = woke.Interpreter.init() orelse { + try stdout.print("Failed to create interpreter\n", .{}); + return; + }; + defer interp.deinit(); + + // Define some WokeLang functions + const woke_code = + \\// Math functions defined in WokeLang + \\to add(a: Int, b: Int) -> Int { + \\ give back a + b; + \\} + \\ + \\to multiply(a: Int, b: Int) -> Int { + \\ give back a * b; + \\} + \\ + \\to factorial(n: Int) -> Int { + \\ when n <= 1 { + \\ give back 1; + \\ } + \\ give back n * factorial(n - 1); + \\} + \\ + \\to greet(name: String) { + \\ print("Hello, " + name + "!"); + \\} + \\ + \\to main() { + \\ greet("Zig"); + \\ print("5! = " + toString(factorial(5))); + \\ print("3 + 4 = " + toString(add(3, 4))); + \\} + ; + + try stdout.print("Executing WokeLang code...\n", .{}); + try stdout.print("---\n", .{}); + + interp.exec(woke_code) catch |err| { + try stdout.print("Error: {}\n", .{err}); + return; + }; + + try stdout.print("---\n", .{}); + try stdout.print("Done!\n", .{}); +} diff --git a/zig/wokelang.zig b/zig/wokelang.zig new file mode 100644 index 0000000..121c98d --- /dev/null +++ b/zig/wokelang.zig @@ -0,0 +1,229 @@ +//! WokeLang FFI bindings for Zig +//! +//! This module provides Zig-native bindings to the WokeLang interpreter. +//! +//! Example usage: +//! ```zig +//! const woke = @import("wokelang.zig"); +//! +//! pub fn main() !void { +//! var interp = woke.Interpreter.init() orelse return error.InitFailed; +//! defer interp.deinit(); +//! +//! try interp.exec( +//! \\to greet(name: String) -> String { +//! \\ give back "Hello, " + name + "!"; +//! \\} +//! ); +//! } +//! ``` + +const std = @import("std"); + +/// Result codes from WokeLang FFI operations +pub const Result = enum(c_int) { + ok = 0, + @"error" = 1, + parse_error = 2, + runtime_error = 3, + null_pointer = 4, + + pub fn isOk(self: Result) bool { + return self == .ok; + } + + pub fn toError(self: Result) ?Error { + return switch (self) { + .ok => null, + .@"error" => Error.GenericError, + .parse_error => Error.ParseError, + .runtime_error => Error.RuntimeError, + .null_pointer => Error.NullPointer, + }; + } +}; + +/// Value type tags +pub const ValueType = enum(c_int) { + int = 0, + float = 1, + string = 2, + bool = 3, + array = 4, + unit = 5, +}; + +/// Errors that can occur when using the WokeLang FFI +pub const Error = error{ + GenericError, + ParseError, + RuntimeError, + NullPointer, + InitFailed, +}; + +// === External C API declarations === + +const WokeInterpreter = opaque {}; +const WokeValue = opaque {}; + +extern fn woke_interpreter_new() ?*WokeInterpreter; +extern fn woke_interpreter_free(interp: *WokeInterpreter) void; +extern fn woke_exec(interp: *WokeInterpreter, source: [*:0]const u8) Result; +extern fn woke_eval(interp: *WokeInterpreter, source: [*:0]const u8, out_value: *?*WokeValue) Result; + +extern fn woke_value_free(value: *WokeValue) void; +extern fn woke_value_type(value: *const WokeValue) ValueType; +extern fn woke_value_as_int(value: *const WokeValue, out: *i64) Result; +extern fn woke_value_as_float(value: *const WokeValue, out: *f64) Result; +extern fn woke_value_as_bool(value: *const WokeValue, out: *c_int) Result; +extern fn woke_value_as_string(value: *const WokeValue) ?[*:0]u8; +extern fn woke_string_free(s: [*:0]u8) void; + +extern fn woke_value_from_int(n: i64) ?*WokeValue; +extern fn woke_value_from_float(f: f64) ?*WokeValue; +extern fn woke_value_from_bool(b: c_int) ?*WokeValue; +extern fn woke_value_from_string(s: [*:0]const u8) ?*WokeValue; + +extern fn woke_version() [*:0]const u8; +extern fn woke_last_error() ?[*:0]const u8; + +// === High-level Zig API === + +/// A WokeLang interpreter instance +pub const Interpreter = struct { + handle: *WokeInterpreter, + + /// Initialize a new WokeLang interpreter + pub fn init() ?Interpreter { + const handle = woke_interpreter_new() orelse return null; + return .{ .handle = handle }; + } + + /// Clean up the interpreter + pub fn deinit(self: *Interpreter) void { + woke_interpreter_free(self.handle); + } + + /// Execute WokeLang source code + pub fn exec(self: *Interpreter, source: [:0]const u8) Error!void { + const result = woke_exec(self.handle, source.ptr); + if (result.toError()) |err| { + return err; + } + } + + /// Execute WokeLang source code (string literal version) + pub fn execLiteral(self: *Interpreter, comptime source: [:0]const u8) Error!void { + return self.exec(source); + } + + /// Evaluate an expression and get the result + pub fn eval(self: *Interpreter, source: [:0]const u8) Error!Value { + var out_value: ?*WokeValue = null; + const result = woke_eval(self.handle, source.ptr, &out_value); + if (result.toError()) |err| { + return err; + } + return Value{ .handle = out_value.? }; + } +}; + +/// A WokeLang value +pub const Value = struct { + handle: *WokeValue, + + /// Free the value + pub fn deinit(self: *Value) void { + woke_value_free(self.handle); + } + + /// Get the type of the value + pub fn getType(self: Value) ValueType { + return woke_value_type(self.handle); + } + + /// Get as integer + pub fn asInt(self: Value) Error!i64 { + var out: i64 = 0; + const result = woke_value_as_int(self.handle, &out); + if (result.toError()) |err| { + return err; + } + return out; + } + + /// Get as float + pub fn asFloat(self: Value) Error!f64 { + var out: f64 = 0; + const result = woke_value_as_float(self.handle, &out); + if (result.toError()) |err| { + return err; + } + return out; + } + + /// Get as boolean + pub fn asBool(self: Value) Error!bool { + var out: c_int = 0; + const result = woke_value_as_bool(self.handle, &out); + if (result.toError()) |err| { + return err; + } + return out != 0; + } + + /// Get as string (allocates) + pub fn asString(self: Value, allocator: std.mem.Allocator) Error![]u8 { + const c_str = woke_value_as_string(self.handle) orelse return Error.NullPointer; + defer woke_string_free(c_str); + + const len = std.mem.len(c_str); + const buf = allocator.alloc(u8, len) catch return Error.GenericError; + @memcpy(buf, c_str[0..len]); + return buf; + } + + /// Create from integer + pub fn fromInt(n: i64) ?Value { + const handle = woke_value_from_int(n) orelse return null; + return .{ .handle = handle }; + } + + /// Create from float + pub fn fromFloat(f: f64) ?Value { + const handle = woke_value_from_float(f) orelse return null; + return .{ .handle = handle }; + } + + /// Create from boolean + pub fn fromBool(b: bool) ?Value { + const handle = woke_value_from_bool(if (b) 1 else 0) orelse return null; + return .{ .handle = handle }; + } + + /// Create from string + pub fn fromString(s: [:0]const u8) ?Value { + const handle = woke_value_from_string(s.ptr) orelse return null; + return .{ .handle = handle }; + } +}; + +/// Get the WokeLang version string +pub fn version() []const u8 { + const ver = woke_version(); + return std.mem.span(ver); +} + +/// Get the last error message (if any) +pub fn lastError() ?[]const u8 { + const err = woke_last_error() orelse return null; + return std.mem.span(err); +} + +// === Tests === + +test "version" { + const ver = version(); + try std.testing.expectEqualStrings("0.1.0", ver); +}