feat: rebuild website with full learning funnel

Website rebuilt from scratch based on analysis of 11 beloved language
websites (Elm, Zig, Gleam, Swift, Kotlin, Haskell, OCaml, Crystal, Roc,
Rust, Go).

New website structure:
- Homepage with hero, playground, three pillars, install guide
- Language Tour with interactive lessons (hello world, types, effects)
- Examples cookbook with categorized sidebar
- API documentation index
- Installation guide (Nix and source)
- Sleek/noble design (black/gold, serif typography)

Also includes:
- New stdlib/json.lux module for JSON serialization
- Enhanced stdlib/http.lux with middleware and routing
- New string functions (charAt, indexOf, lastIndexOf, repeat)
- LSP improvements (rename, signature help, formatting)
- Package manager transitive dependency resolution
- Updated documentation for effects and stdlib
- New showcase example (task_manager.lux)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 23:05:35 -05:00
parent 5a853702d1
commit 7e76acab18
44 changed files with 12468 additions and 3354 deletions

View File

@@ -80,6 +80,16 @@ impl std::fmt::Display for CGenError {
impl std::error::Error for CGenError {}
/// Behavioral properties for a function
#[derive(Debug, Clone, Default)]
struct FunctionBehavior {
is_pure: bool,
is_total: bool,
is_idempotent: bool,
is_deterministic: bool,
is_commutative: bool,
}
/// The C backend code generator
pub struct CBackend {
/// Generated C code
@@ -125,6 +135,10 @@ pub struct CBackend {
adt_with_pointers: HashSet<String>,
/// Variable types for type inference (variable name -> C type)
var_types: HashMap<String, String>,
/// Behavioral properties for functions (for optimization)
function_behaviors: HashMap<String, FunctionBehavior>,
/// Whether to enable behavioral type optimizations
enable_behavioral_optimizations: bool,
}
impl CBackend {
@@ -151,6 +165,28 @@ impl CBackend {
next_adt_tag: 100, // ADT tags start at 100
adt_with_pointers: HashSet::new(),
var_types: HashMap::new(),
function_behaviors: HashMap::new(),
enable_behavioral_optimizations: true,
}
}
/// Collect behavioral properties from function declaration
fn collect_behavioral_properties(&mut self, f: &FunctionDecl) {
let mut behavior = FunctionBehavior::default();
for prop in &f.properties {
match prop {
BehavioralProperty::Pure => behavior.is_pure = true,
BehavioralProperty::Total => behavior.is_total = true,
BehavioralProperty::Idempotent => behavior.is_idempotent = true,
BehavioralProperty::Deterministic => behavior.is_deterministic = true,
BehavioralProperty::Commutative => behavior.is_commutative = true,
}
}
if behavior.is_pure || behavior.is_total || behavior.is_idempotent
|| behavior.is_deterministic || behavior.is_commutative {
self.function_behaviors.insert(f.name.name.clone(), behavior);
}
}
@@ -182,6 +218,8 @@ impl CBackend {
if !f.effects.is_empty() {
self.effectful_functions.insert(f.name.name.clone());
}
// Collect behavioral properties for optimization
self.collect_behavioral_properties(f);
}
Declaration::Type(t) => {
self.collect_type(t)?;
@@ -587,6 +625,21 @@ impl CBackend {
self.writeln(" return strcmp(a, b) == 0;");
self.writeln("}");
self.writeln("");
self.writeln("// Alias for memoization key comparison");
self.writeln("static inline LuxBool lux_string_equals(LuxString a, LuxString b) {");
self.writeln(" return strcmp(a, b) == 0;");
self.writeln("}");
self.writeln("");
self.writeln("// String hash for memoization (djb2 algorithm)");
self.writeln("static inline size_t lux_string_hash(LuxString s) {");
self.writeln(" size_t hash = 5381;");
self.writeln(" unsigned char c;");
self.writeln(" while ((c = (unsigned char)*s++)) {");
self.writeln(" hash = ((hash << 5) + hash) + c;");
self.writeln(" }");
self.writeln(" return hash;");
self.writeln("}");
self.writeln("");
self.writeln("static LuxBool lux_string_contains(LuxString haystack, LuxString needle) {");
self.writeln(" return strstr(haystack, needle) != NULL;");
self.writeln("}");
@@ -2062,12 +2115,51 @@ impl CBackend {
format!("LuxEvidence* ev, {}", params)
}
} else {
params
params.clone()
};
// Check for behavioral optimizations
let behavior = self.function_behaviors.get(&func.name.name).cloned();
let use_memoization = self.enable_behavioral_optimizations
&& behavior.as_ref().map_or(false, |b| b.is_pure)
&& !func.params.is_empty()
&& ret_type != "void"
&& ret_type != "LuxUnit";
let use_idempotent = self.enable_behavioral_optimizations
&& behavior.as_ref().map_or(false, |b| b.is_idempotent && !b.is_pure)
&& ret_type != "void"
&& ret_type != "LuxUnit";
let use_commutative = self.enable_behavioral_optimizations
&& behavior.as_ref().map_or(false, |b| b.is_commutative)
&& func.params.len() == 2;
let is_deterministic = behavior.as_ref().map_or(false, |b| b.is_deterministic);
self.writeln(&format!("{} {}({}) {{", ret_type, mangled, full_params));
self.indent += 1;
// Emit deterministic attribute hint
if is_deterministic {
self.emit_deterministic_attribute(&func.name.name);
}
// Emit commutative optimization (normalize argument order for better CSE)
if use_commutative {
self.emit_commutative_optimization(&func.name.name, &func.params)?;
}
// Emit memoization check for pure functions
if use_memoization {
self.emit_memoization_lookup(&func.name.name, &func.params, &ret_type)?;
}
// Emit idempotent check (for non-pure idempotent functions)
if use_idempotent {
self.emit_idempotent_check(&func.name.name, &func.params, &ret_type)?;
}
// Set evidence availability for expression generation
let prev_has_evidence = self.has_evidence;
if is_effectful {
@@ -2123,6 +2215,12 @@ impl CBackend {
if let Some(ref var_name) = skip_var {
// Result is a local variable or RC temp - skip decref'ing it and just return
self.pop_rc_scope_except(Some(var_name));
if use_memoization {
self.emit_memoization_store(&func.name.name, &func.params, &result)?;
}
if use_idempotent {
self.emit_idempotent_store(&func.name.name, &func.params, &result)?;
}
self.writeln(&format!("return {};", result));
} else if is_rc_result && has_rc_locals {
// Result is from a call or complex expression - use incref/decref pattern
@@ -2130,10 +2228,22 @@ impl CBackend {
self.writeln("lux_incref(_result);");
self.pop_rc_scope(); // Emit decrefs for all local RC vars
self.writeln("lux_decref(_result); // Balance the incref");
if use_memoization {
self.emit_memoization_store(&func.name.name, &func.params, "_result")?;
}
if use_idempotent {
self.emit_idempotent_store(&func.name.name, &func.params, "_result")?;
}
self.writeln("return _result;");
} else {
// No RC locals or non-RC result - simple cleanup
self.pop_rc_scope();
if use_memoization {
self.emit_memoization_store(&func.name.name, &func.params, &result)?;
}
if use_idempotent {
self.emit_idempotent_store(&func.name.name, &func.params, &result)?;
}
self.writeln(&format!("return {};", result));
}
} else {
@@ -4637,6 +4747,196 @@ impl CBackend {
}
}
/// Emit memoization lookup code for pure functions
///
/// For pure functions, we generate a static memo table that caches results.
/// This is a simple linear cache for now - a hash table would be more efficient
/// for functions called with many different arguments.
fn emit_memoization_lookup(&mut self, func_name: &str, params: &[Parameter], ret_type: &str) -> Result<(), CGenError> {
let mangled = self.mangle_name(func_name);
let memo_size = 64; // Fixed size memo table
// Generate a hash expression from parameters
let hash_expr = if params.len() == 1 {
let p = &params[0];
let c_type = self.type_expr_to_c(&p.typ)?;
match c_type.as_str() {
"LuxInt" => format!("((size_t){} & {})", self.escape_c_keyword(&p.name.name), memo_size - 1),
"LuxString" => format!("(lux_string_hash({}) & {})", self.escape_c_keyword(&p.name.name), memo_size - 1),
"LuxBool" => format!("((size_t){} & {})", self.escape_c_keyword(&p.name.name), memo_size - 1),
_ => format!("((size_t)(uintptr_t){} & {})", self.escape_c_keyword(&p.name.name), memo_size - 1),
}
} else {
// For multiple params, combine hashes
let mut parts = Vec::new();
for (i, p) in params.iter().enumerate() {
let c_type = self.type_expr_to_c(&p.typ)?;
let hash = match c_type.as_str() {
"LuxInt" => format!("(size_t){}", self.escape_c_keyword(&p.name.name)),
"LuxString" => format!("lux_string_hash({})", self.escape_c_keyword(&p.name.name)),
"LuxBool" => format!("(size_t){}", self.escape_c_keyword(&p.name.name)),
_ => format!("(size_t)(uintptr_t){}", self.escape_c_keyword(&p.name.name)),
};
// Mix with prime numbers for better distribution
let prime = [31, 37, 41, 43, 47, 53, 59, 61][i % 8];
parts.push(format!("({} * {})", hash, prime));
}
format!("(({}) & {})", parts.join(" ^ "), memo_size - 1)
};
// Emit static memo table
self.writeln(&format!("// Memoization for pure function {}", func_name));
self.writeln(&format!("static struct {{ bool valid; {} result; {} key; }} _memo_{}[{}];",
ret_type, self.generate_key_type(params)?, mangled, memo_size));
self.writeln(&format!("size_t _memo_idx = {};", hash_expr));
// Check if cached
self.writeln(&format!("if (_memo_{}[_memo_idx].valid && {}) {{",
mangled, self.generate_key_compare(params, &format!("_memo_{}[_memo_idx]", mangled))?));
self.indent += 1;
self.writeln(&format!("return _memo_{}[_memo_idx].result;", mangled));
self.indent -= 1;
self.writeln("}");
Ok(())
}
/// Emit memoization store code for pure functions
fn emit_memoization_store(&mut self, func_name: &str, params: &[Parameter], result_expr: &str) -> Result<(), CGenError> {
let mangled = self.mangle_name(func_name);
// Store result in memo table
self.writeln(&format!("_memo_{}[_memo_idx].valid = true;", mangled));
self.writeln(&format!("_memo_{}[_memo_idx].result = {};", mangled, result_expr));
for p in params {
let name = self.escape_c_keyword(&p.name.name);
self.writeln(&format!("_memo_{}[_memo_idx].key_{} = {};", mangled, name, name));
}
Ok(())
}
/// Generate the key type for memoization (stores all parameter values)
fn generate_key_type(&self, params: &[Parameter]) -> Result<String, CGenError> {
let mut fields = Vec::new();
for p in params {
let c_type = self.type_expr_to_c(&p.typ)?;
let name = self.escape_c_keyword(&p.name.name);
fields.push(format!("{} key_{}", c_type, name));
}
Ok(fields.join("; "))
}
/// Generate key comparison expression for memoization lookup
fn generate_key_compare(&self, params: &[Parameter], memo_entry: &str) -> Result<String, CGenError> {
let mut comparisons = Vec::new();
for p in params {
let c_type = self.type_expr_to_c(&p.typ)?;
let name = self.escape_c_keyword(&p.name.name);
let cmp = match c_type.as_str() {
"LuxString" => format!("lux_string_equals({}.key_{}, {})", memo_entry, name, name),
_ => format!("{}.key_{} == {}", memo_entry, name, name),
};
comparisons.push(cmp);
}
Ok(comparisons.join(" && "))
}
/// Emit idempotent function optimization
///
/// For idempotent functions (f(f(x)) = f(x)), we track if the function
/// has already been called with the same arguments to avoid redundant computation.
/// This is useful for initialization functions, setters with same value, etc.
fn emit_idempotent_check(&mut self, func_name: &str, params: &[Parameter], ret_type: &str) -> Result<(), CGenError> {
let mangled = self.mangle_name(func_name);
self.writeln(&format!("// Idempotent optimization for {}", func_name));
self.writeln(&format!("static bool _idem_{}_called = false;", mangled));
self.writeln(&format!("static {} _idem_{}_result;", ret_type, mangled));
// For idempotent functions with no params, just return the cached result
if params.is_empty() {
self.writeln(&format!("if (_idem_{}_called) {{ return _idem_{}_result; }}", mangled, mangled));
} else {
// For params, we still use memoization-like caching
self.writeln(&format!("static {} _idem_{}_key;", self.generate_key_type(params)?, mangled));
let key_compare = self.generate_key_compare_inline(params, &format!("_idem_{}_key", mangled))?;
self.writeln(&format!("if (_idem_{}_called && {}) {{ return _idem_{}_result; }}", mangled, key_compare, mangled));
}
Ok(())
}
/// Emit idempotent function store
fn emit_idempotent_store(&mut self, func_name: &str, params: &[Parameter], result_expr: &str) -> Result<(), CGenError> {
let mangled = self.mangle_name(func_name);
self.writeln(&format!("_idem_{}_called = true;", mangled));
self.writeln(&format!("_idem_{}_result = {};", mangled, result_expr));
for p in params {
let name = self.escape_c_keyword(&p.name.name);
self.writeln(&format!("_idem_{}_key_{} = {};", mangled, name, name));
}
Ok(())
}
/// Generate key comparison expression inline (for idempotent check)
fn generate_key_compare_inline(&self, params: &[Parameter], prefix: &str) -> Result<String, CGenError> {
let mut comparisons = Vec::new();
for p in params {
let c_type = self.type_expr_to_c(&p.typ)?;
let name = self.escape_c_keyword(&p.name.name);
let cmp = match c_type.as_str() {
"LuxString" => format!("lux_string_equals({}.{}, {})", prefix, name, name),
_ => format!("{}_{} == {}", prefix, name, name),
};
comparisons.push(cmp);
}
Ok(comparisons.join(" && "))
}
/// Emit deterministic function hint as a comment
///
/// Deterministic functions always produce the same output for the same inputs.
/// This hint helps C compilers (GCC/Clang) with optimization.
fn emit_deterministic_attribute(&mut self, func_name: &str) {
// GCC and Clang support __attribute__((const)) for pure functions without side effects
// that only depend on their arguments (not even global state)
self.writeln(&format!("// OPTIMIZATION: {} is deterministic - output depends only on inputs", func_name));
}
/// Emit commutative function hint
///
/// For commutative functions (f(a, b) = f(b, a)), we can normalize argument order
/// to improve common subexpression elimination (CSE).
fn emit_commutative_optimization(&mut self, func_name: &str, params: &[Parameter]) -> Result<(), CGenError> {
if params.len() != 2 {
return Ok(()); // Commutativity only makes sense for binary functions
}
let p1 = &params[0];
let p2 = &params[1];
let c_type1 = self.type_expr_to_c(&p1.typ)?;
let c_type2 = self.type_expr_to_c(&p2.typ)?;
// Only do swapping for comparable types
if c_type1 == c_type2 && matches!(c_type1.as_str(), "LuxInt" | "LuxFloat") {
let name1 = self.escape_c_keyword(&p1.name.name);
let name2 = self.escape_c_keyword(&p2.name.name);
self.writeln(&format!("// Commutative optimization for {}: normalize argument order", func_name));
self.writeln(&format!("if ({} > {}) {{", name1, name2));
self.indent += 1;
self.writeln(&format!("{} _swap_tmp = {}; {} = {}; {} = _swap_tmp;",
c_type1, name1, name1, name2, name2));
self.indent -= 1;
self.writeln("}");
}
Ok(())
}
fn writeln(&mut self, line: &str) {
let indent = " ".repeat(self.indent);
writeln!(self.output, "{}{}", indent, line).unwrap();
@@ -4746,4 +5046,33 @@ mod tests {
assert!(c_code.contains("->fn_ptr"));
assert!(c_code.contains("->env"));
}
#[test]
fn test_pure_function_memoization() {
let source = r#"
fn fib(n: Int): Int is pure =
if n <= 1 then n else fib(n - 1) + fib(n - 2)
"#;
let c_code = generate(source).unwrap();
// Pure function should have memoization infrastructure
assert!(c_code.contains("// Memoization for pure function fib"));
assert!(c_code.contains("_memo_fib_lux"));
assert!(c_code.contains("_memo_idx"));
// Should check cache before computation
assert!(c_code.contains(".valid &&"));
// Should store result in cache
assert!(c_code.contains(".valid = true"));
assert!(c_code.contains(".result ="));
}
#[test]
fn test_non_pure_function_no_memoization() {
let source = r#"
fn add(a: Int, b: Int): Int = a + b
"#;
let c_code = generate(source).unwrap();
// Non-pure function should NOT have memoization
assert!(!c_code.contains("// Memoization for pure function add"));
assert!(!c_code.contains("_memo_add_lux"));
}
}