From 97542b17008090f0cc085ecde95c76ef04141568 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Dec 2025 03:37:40 +0000 Subject: [PATCH] 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(_))); + } +}