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:
@@ -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 = ¶ms[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 = ¶ms[0];
|
||||
let p2 = ¶ms[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"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user