diff --git a/src/main.rs b/src/main.rs index 9474433..60e615b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,6 +42,8 @@ Commands: :quit, :q Exit the REPL :type Show the type of an expression :info Show info about a binding + :doc Show documentation for a function/type + :browse List exports of a module (List, String, Option, etc.) :env Show user-defined bindings :clear Clear the environment :load Load and execute a file @@ -1500,7 +1502,7 @@ impl LuxHelper { let commands = vec![ ":help", ":h", ":quit", ":q", ":type", ":t", ":clear", ":load", ":l", - ":trace", ":traces", ":info", ":i", ":env", + ":trace", ":traces", ":info", ":i", ":env", ":doc", ":d", ":browse", ":b", ] .into_iter() .map(String::from) @@ -1588,6 +1590,100 @@ impl Highlighter for LuxHelper { fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { Cow::Owned(format!("\x1b[90m{}\x1b[0m", hint)) } + + fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { + let mut result = String::with_capacity(line.len() * 2); + let mut chars = line.char_indices().peekable(); + + while let Some((i, c)) = chars.next() { + if c == '"' { + // String literal - highlight in green + result.push_str("\x1b[32m\""); + let mut escaped = false; + for (_, ch) in chars.by_ref() { + result.push(ch); + if escaped { + escaped = false; + } else if ch == '\\' { + escaped = true; + } else if ch == '"' { + break; + } + } + result.push_str("\x1b[0m"); + } else if c == ':' && i == 0 { + // Command - highlight in cyan + result.push_str("\x1b[36m:"); + for (_, ch) in chars.by_ref() { + if ch.is_whitespace() { + result.push_str("\x1b[0m"); + result.push(ch); + break; + } + result.push(ch); + } + // Continue with the rest + for (_, ch) in chars.by_ref() { + result.push(ch); + } + } else if c.is_ascii_digit() || (c == '-' && chars.peek().map(|(_, ch)| ch.is_ascii_digit()).unwrap_or(false)) { + // Number - highlight in yellow + result.push_str("\x1b[33m"); + result.push(c); + while let Some(&(_, ch)) = chars.peek() { + if ch.is_ascii_digit() || ch == '.' { + result.push(ch); + chars.next(); + } else { + break; + } + } + result.push_str("\x1b[0m"); + } else if c.is_alphabetic() || c == '_' { + // Identifier or keyword + let start = i; + let mut end = i + c.len_utf8(); + while let Some(&(j, ch)) = chars.peek() { + if ch.is_alphanumeric() || ch == '_' { + end = j + ch.len_utf8(); + chars.next(); + } else { + break; + } + } + let word = &line[start..end]; + + // Check if it's a keyword + if self.keywords.contains(word) { + result.push_str("\x1b[35m"); // Magenta for keywords + result.push_str(word); + result.push_str("\x1b[0m"); + } else if word.starts_with(char::is_uppercase) { + result.push_str("\x1b[34m"); // Blue for types/constructors + result.push_str(word); + result.push_str("\x1b[0m"); + } else { + result.push_str(word); + } + } else if c == '/' && chars.peek().map(|(_, ch)| *ch == '/').unwrap_or(false) { + // Comment - highlight in gray + result.push_str("\x1b[90m"); + result.push(c); + for (_, ch) in chars.by_ref() { + result.push(ch); + } + result.push_str("\x1b[0m"); + } else { + result.push(c); + } + } + + Cow::Owned(result) + } + + fn highlight_char(&self, _line: &str, _pos: usize, _forced: bool) -> bool { + true + } } impl Validator for LuxHelper {} @@ -1789,6 +1885,21 @@ fn handle_command( interp.print_traces(); } } + ":doc" | ":d" => { + if let Some(name) = arg { + show_doc(name); + } else { + println!("Usage: :doc "); + } + } + ":browse" | ":b" => { + if let Some(module) = arg { + browse_module(module); + } else { + println!("Usage: :browse "); + println!("Available modules: List, String, Option, Result, Console, Random, File, Http"); + } + } _ => { println!("Unknown command: {}", cmd); println!("Type :help for help"); @@ -1820,6 +1931,218 @@ fn show_environment(checker: &TypeChecker, helper: &LuxHelper) { } } +fn show_doc(name: &str) { + // Built-in documentation for common functions + let doc = match name { + // List functions + "List.length" => Some(("List.length : List -> Int", "Returns the number of elements in a list.")), + "List.head" => Some(("List.head : List -> Option", "Returns the first element of a list, or None if empty.")), + "List.tail" => Some(("List.tail : List -> Option>", "Returns all elements except the first, or None if empty.")), + "List.map" => Some(("List.map : (List, fn(A) -> B) -> List", "Applies a function to each element, returning a new list.")), + "List.filter" => Some(("List.filter : (List, fn(T) -> Bool) -> List", "Returns elements for which the predicate returns true.")), + "List.fold" => Some(("List.fold : (List, U, fn(U, T) -> U) -> U", "Reduces a list to a single value using an accumulator function.")), + "List.reverse" => Some(("List.reverse : List -> List", "Returns a new list with elements in reverse order.")), + "List.concat" => Some(("List.concat : (List, List) -> List", "Concatenates two lists.")), + "List.take" => Some(("List.take : (List, Int) -> List", "Returns the first n elements.")), + "List.drop" => Some(("List.drop : (List, Int) -> List", "Returns all elements after the first n.")), + "List.get" => Some(("List.get : (List, Int) -> Option", "Returns the element at index n, or None if out of bounds.")), + "List.contains" => Some(("List.contains : (List, T) -> Bool", "Returns true if the list contains the element.")), + "List.all" => Some(("List.all : (List, fn(T) -> Bool) -> Bool", "Returns true if all elements satisfy the predicate.")), + "List.any" => Some(("List.any : (List, fn(T) -> Bool) -> Bool", "Returns true if any element satisfies the predicate.")), + "List.find" => Some(("List.find : (List, fn(T) -> Bool) -> Option", "Returns the first element satisfying the predicate.")), + "List.sort" => Some(("List.sort : List -> List", "Sorts a list of integers in ascending order.")), + "List.range" => Some(("List.range : (Int, Int) -> List", "Creates a list of integers from start to end (exclusive).")), + + // String functions + "String.length" => Some(("String.length : String -> Int", "Returns the number of characters in a string.")), + "String.concat" => Some(("String.concat : (String, String) -> String", "Concatenates two strings.")), + "String.substring" => Some(("String.substring : (String, Int, Int) -> String", "Returns a substring from start to end index.")), + "String.split" => Some(("String.split : (String, String) -> List", "Splits a string by a delimiter.")), + "String.join" => Some(("String.join : (List, String) -> String", "Joins a list of strings with a delimiter.")), + "String.trim" => Some(("String.trim : String -> String", "Removes leading and trailing whitespace.")), + "String.contains" => Some(("String.contains : (String, String) -> Bool", "Returns true if the string contains the substring.")), + "String.replace" => Some(("String.replace : (String, String, String) -> String", "Replaces all occurrences of a pattern.")), + "String.startsWith" => Some(("String.startsWith : (String, String) -> Bool", "Returns true if the string starts with the prefix.")), + "String.endsWith" => Some(("String.endsWith : (String, String) -> Bool", "Returns true if the string ends with the suffix.")), + "String.toUpper" => Some(("String.toUpper : String -> String", "Converts to uppercase.")), + "String.toLower" => Some(("String.toLower : String -> String", "Converts to lowercase.")), + + // Option functions + "Option.map" => Some(("Option.map : (Option, fn(A) -> B) -> Option", "Applies a function to the value inside Some, or returns None.")), + "Option.flatMap" => Some(("Option.flatMap : (Option, fn(A) -> Option) -> Option", "Like map, but the function returns an Option.")), + "Option.getOrElse" => Some(("Option.getOrElse : (Option, T) -> T", "Returns the value inside Some, or the default if None.")), + "Option.isSome" => Some(("Option.isSome : Option -> Bool", "Returns true if the value is Some.")), + "Option.isNone" => Some(("Option.isNone : Option -> Bool", "Returns true if the value is None.")), + + // Result functions + "Result.map" => Some(("Result.map : (Result, fn(A) -> B) -> Result", "Applies a function to the Ok value.")), + "Result.flatMap" => Some(("Result.flatMap : (Result, fn(A) -> Result) -> Result", "Like map, but the function returns a Result.")), + "Result.getOrElse" => Some(("Result.getOrElse : (Result, T) -> T", "Returns the Ok value, or the default if Err.")), + "Result.isOk" => Some(("Result.isOk : Result -> Bool", "Returns true if the value is Ok.")), + "Result.isErr" => Some(("Result.isErr : Result -> Bool", "Returns true if the value is Err.")), + + // Effects + "Console.print" => Some(("Console.print : String -> Unit", "Prints a string to the console.")), + "Console.readLine" => Some(("Console.readLine : () -> String", "Reads a line from standard input.")), + "Random.int" => Some(("Random.int : (Int, Int) -> Int", "Generates a random integer in the range [min, max].")), + "Random.float" => Some(("Random.float : () -> Float", "Generates a random float in [0, 1).")), + "Random.bool" => Some(("Random.bool : () -> Bool", "Generates a random boolean.")), + "File.read" => Some(("File.read : String -> String", "Reads the contents of a file.")), + "File.write" => Some(("File.write : (String, String) -> Unit", "Writes content to a file.")), + "File.exists" => Some(("File.exists : String -> Bool", "Returns true if the file exists.")), + "Http.get" => Some(("Http.get : String -> String", "Performs an HTTP GET request.")), + "Http.post" => Some(("Http.post : (String, String) -> String", "Performs an HTTP POST request with a body.")), + "Time.now" => Some(("Time.now : () -> Int", "Returns the current Unix timestamp in milliseconds.")), + "Sql.open" => Some(("Sql.open : String -> SqlConn", "Opens a SQLite database file.")), + "Sql.openMemory" => Some(("Sql.openMemory : () -> SqlConn", "Opens an in-memory SQLite database.")), + "Sql.query" => Some(("Sql.query : (SqlConn, String) -> List", "Executes a SQL query and returns all rows.")), + "Sql.execute" => Some(("Sql.execute : (SqlConn, String) -> Int", "Executes a SQL statement and returns affected rows.")), + "Postgres.connect" => Some(("Postgres.connect : String -> Int", "Connects to a PostgreSQL database.")), + "Postgres.query" => Some(("Postgres.query : (Int, String) -> List", "Executes a SQL query on PostgreSQL.")), + "Postgres.execute" => Some(("Postgres.execute : (Int, String) -> Int", "Executes a SQL statement on PostgreSQL.")), + + // Language constructs + "fn" => Some(("fn name(args): Type = body", "Defines a function.")), + "let" => Some(("let name = value", "Binds a value to a name.")), + "if" => Some(("if condition then expr1 else expr2", "Conditional expression.")), + "match" => Some(("match value { pattern => expr, ... }", "Pattern matching expression.")), + "effect" => Some(("effect Name { fn op(...): Type }", "Defines an effect with operations.")), + "handler" => Some(("handler { op => body }", "Defines an effect handler.")), + "with" => Some(("fn f(): T with {Effect1, Effect2}", "Declares effects a function may perform.")), + "type" => Some(("type Name = ...", "Defines a type alias.")), + "run" => Some(("run expr with { handler }", "Executes an expression with effect handlers.")), + + _ => None, + }; + + match doc { + Some((sig, desc)) => { + println!("\x1b[1m{}\x1b[0m", sig); + println!(); + println!(" {}", desc); + } + None => { + println!("No documentation for '{}'", name); + println!("Try :doc List.map or :browse List"); + } + } +} + +fn browse_module(module: &str) { + let exports: Vec<(&str, &str)> = match module { + "List" => vec![ + ("length", "List -> Int"), + ("head", "List -> Option"), + ("tail", "List -> Option>"), + ("get", "(List, Int) -> Option"), + ("map", "(List, fn(A) -> B) -> List"), + ("filter", "(List, fn(T) -> Bool) -> List"), + ("fold", "(List, U, fn(U, T) -> U) -> U"), + ("reverse", "List -> List"), + ("concat", "(List, List) -> List"), + ("take", "(List, Int) -> List"), + ("drop", "(List, Int) -> List"), + ("contains", "(List, T) -> Bool"), + ("all", "(List, fn(T) -> Bool) -> Bool"), + ("any", "(List, fn(T) -> Bool) -> Bool"), + ("find", "(List, fn(T) -> Bool) -> Option"), + ("sort", "List -> List"), + ("range", "(Int, Int) -> List"), + ("forEach", "(List, fn(T) -> Unit) -> Unit"), + ], + "String" => vec![ + ("length", "String -> Int"), + ("concat", "(String, String) -> String"), + ("substring", "(String, Int, Int) -> String"), + ("split", "(String, String) -> List"), + ("join", "(List, String) -> String"), + ("trim", "String -> String"), + ("contains", "(String, String) -> Bool"), + ("replace", "(String, String, String) -> String"), + ("startsWith", "(String, String) -> Bool"), + ("endsWith", "(String, String) -> Bool"), + ("toUpper", "String -> String"), + ("toLower", "String -> String"), + ("lines", "String -> List"), + ], + "Option" => vec![ + ("map", "(Option, fn(A) -> B) -> Option"), + ("flatMap", "(Option, fn(A) -> Option) -> Option"), + ("getOrElse", "(Option, T) -> T"), + ("isSome", "Option -> Bool"), + ("isNone", "Option -> Bool"), + ], + "Result" => vec![ + ("map", "(Result, fn(A) -> B) -> Result"), + ("flatMap", "(Result, fn(A) -> Result) -> Result"), + ("getOrElse", "(Result, T) -> T"), + ("isOk", "Result -> Bool"), + ("isErr", "Result -> Bool"), + ], + "Console" => vec![ + ("print", "String -> Unit"), + ("readLine", "() -> String"), + ], + "Random" => vec![ + ("int", "(Int, Int) -> Int"), + ("float", "() -> Float"), + ("bool", "() -> Bool"), + ], + "File" => vec![ + ("read", "String -> String"), + ("write", "(String, String) -> Unit"), + ("exists", "String -> Bool"), + ("delete", "String -> Unit"), + ], + "Http" => vec![ + ("get", "String -> String"), + ("post", "(String, String) -> String"), + ], + "Time" => vec![ + ("now", "() -> Int"), + ], + "Sql" => vec![ + ("open", "String -> SqlConn"), + ("openMemory", "() -> SqlConn"), + ("close", "SqlConn -> Unit"), + ("query", "(SqlConn, String) -> List"), + ("queryOne", "(SqlConn, String) -> Option"), + ("execute", "(SqlConn, String) -> Int"), + ("beginTx", "SqlConn -> Unit"), + ("commit", "SqlConn -> Unit"), + ("rollback", "SqlConn -> Unit"), + ], + "Postgres" => vec![ + ("connect", "String -> Int"), + ("close", "Int -> Unit"), + ("query", "(Int, String) -> List"), + ("queryOne", "(Int, String) -> Option"), + ("execute", "(Int, String) -> Int"), + ("beginTx", "Int -> Unit"), + ("commit", "Int -> Unit"), + ("rollback", "Int -> Unit"), + ], + "Test" => vec![ + ("assert", "(Bool, String) -> Unit"), + ("assertEqual", "(T, T) -> Unit"), + ("assertTrue", "Bool -> Unit"), + ("assertFalse", "Bool -> Unit"), + ("fail", "String -> Unit"), + ], + _ => { + println!("Unknown module: {}", module); + println!("Available modules: List, String, Option, Result, Console, Random, File, Http, Time, Sql, Postgres, Test"); + return; + } + }; + + println!("\x1b[1mmodule {}\x1b[0m", module); + println!(); + for (name, sig) in exports { + println!(" \x1b[34m{}.{}\x1b[0m : {}", module, name, sig); + } +} + fn show_type(expr_str: &str, checker: &mut TypeChecker) { // Wrap expression in a let to parse it let wrapped = format!("let _expr_ = {}", expr_str);