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

660
src/symbol_table.rs Normal file
View File

@@ -0,0 +1,660 @@
//! Symbol Table for Lux
//!
//! Provides semantic analysis infrastructure for IDE features like
//! go-to-definition, find references, and rename refactoring.
use crate::ast::*;
use std::collections::HashMap;
/// Unique identifier for a symbol
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SymbolId(pub u32);
/// Kind of symbol
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SymbolKind {
Function,
Variable,
Parameter,
Type,
TypeParameter,
Variant,
Effect,
EffectOperation,
Field,
Module,
}
/// A symbol definition
#[derive(Debug, Clone)]
pub struct Symbol {
pub id: SymbolId,
pub name: String,
pub kind: SymbolKind,
pub span: Span,
/// Type signature (for display)
pub type_signature: Option<String>,
/// Documentation comment
pub documentation: Option<String>,
/// Parent symbol (e.g., type for variants, effect for operations)
pub parent: Option<SymbolId>,
/// Is this symbol exported (public)?
pub is_public: bool,
}
/// A reference to a symbol
#[derive(Debug, Clone)]
pub struct Reference {
pub symbol_id: SymbolId,
pub span: Span,
pub is_definition: bool,
pub is_write: bool,
}
/// A scope in the symbol table
#[derive(Debug, Clone)]
pub struct Scope {
/// Parent scope (None for global scope)
pub parent: Option<usize>,
/// Symbols defined in this scope
pub symbols: HashMap<String, SymbolId>,
/// Span of this scope
pub span: Span,
}
/// The symbol table
#[derive(Debug, Clone)]
pub struct SymbolTable {
/// All symbols
symbols: Vec<Symbol>,
/// All references
references: Vec<Reference>,
/// Scopes (index 0 is always the global scope)
scopes: Vec<Scope>,
/// Mapping from position to references
position_to_reference: HashMap<(u32, u32), usize>,
/// Next symbol ID
next_id: u32,
}
impl SymbolTable {
pub fn new() -> Self {
Self {
symbols: Vec::new(),
references: Vec::new(),
scopes: vec![Scope {
parent: None,
symbols: HashMap::new(),
span: Span { start: 0, end: 0 },
}],
position_to_reference: HashMap::new(),
next_id: 0,
}
}
/// Build symbol table from a program
pub fn build(program: &Program) -> Self {
let mut table = Self::new();
table.visit_program(program);
table
}
/// Add a symbol to the current scope
fn add_symbol(&mut self, scope_idx: usize, symbol: Symbol) -> SymbolId {
let id = symbol.id;
self.scopes[scope_idx].symbols.insert(symbol.name.clone(), id);
self.symbols.push(symbol);
id
}
/// Create a new symbol
fn new_symbol(
&mut self,
name: String,
kind: SymbolKind,
span: Span,
type_signature: Option<String>,
is_public: bool,
) -> Symbol {
let id = SymbolId(self.next_id);
self.next_id += 1;
Symbol {
id,
name,
kind,
span,
type_signature,
documentation: None,
parent: None,
is_public,
}
}
/// Add a reference
fn add_reference(&mut self, symbol_id: SymbolId, span: Span, is_definition: bool, is_write: bool) {
let ref_idx = self.references.len();
self.references.push(Reference {
symbol_id,
span,
is_definition,
is_write,
});
// Index by start position
self.position_to_reference.insert((span.start as u32, span.end as u32), ref_idx);
}
/// Look up a symbol by name in the given scope and its parents
pub fn lookup(&self, name: &str, scope_idx: usize) -> Option<SymbolId> {
let scope = &self.scopes[scope_idx];
if let Some(&id) = scope.symbols.get(name) {
return Some(id);
}
if let Some(parent) = scope.parent {
return self.lookup(name, parent);
}
None
}
/// Get a symbol by ID
pub fn get_symbol(&self, id: SymbolId) -> Option<&Symbol> {
self.symbols.iter().find(|s| s.id == id)
}
/// Get the symbol at a position
pub fn symbol_at_position(&self, offset: usize) -> Option<&Symbol> {
// Find a reference that contains this offset
for reference in &self.references {
if offset >= reference.span.start && offset <= reference.span.end {
return self.get_symbol(reference.symbol_id);
}
}
None
}
/// Get the definition of a symbol at a position
pub fn definition_at_position(&self, offset: usize) -> Option<&Symbol> {
self.symbol_at_position(offset)
}
/// Find all references to a symbol
pub fn find_references(&self, symbol_id: SymbolId) -> Vec<&Reference> {
self.references
.iter()
.filter(|r| r.symbol_id == symbol_id)
.collect()
}
/// Get all symbols of a given kind
pub fn symbols_of_kind(&self, kind: SymbolKind) -> Vec<&Symbol> {
self.symbols.iter().filter(|s| s.kind == kind).collect()
}
/// Get all symbols in the global scope
pub fn global_symbols(&self) -> Vec<&Symbol> {
self.scopes[0]
.symbols
.values()
.filter_map(|&id| self.get_symbol(id))
.collect()
}
/// Create a new scope
fn push_scope(&mut self, parent: usize, span: Span) -> usize {
let idx = self.scopes.len();
self.scopes.push(Scope {
parent: Some(parent),
symbols: HashMap::new(),
span,
});
idx
}
// =========================================================================
// AST Visitors
// =========================================================================
fn visit_program(&mut self, program: &Program) {
// First pass: collect all top-level declarations
for decl in &program.declarations {
self.visit_declaration(decl, 0);
}
}
fn visit_declaration(&mut self, decl: &Declaration, scope_idx: usize) {
match decl {
Declaration::Function(f) => self.visit_function(f, scope_idx),
Declaration::Type(t) => self.visit_type_decl(t, scope_idx),
Declaration::Effect(e) => self.visit_effect(e, scope_idx),
Declaration::Let(let_decl) => {
let is_public = matches!(let_decl.visibility, Visibility::Public);
let type_sig = let_decl.typ.as_ref().map(|t| self.type_expr_to_string(t));
let symbol = self.new_symbol(
let_decl.name.name.clone(),
SymbolKind::Variable,
let_decl.span,
type_sig,
is_public,
);
let id = self.add_symbol(scope_idx, symbol);
self.add_reference(id, let_decl.name.span, true, true);
// Visit the expression
self.visit_expr(&let_decl.value, scope_idx);
}
Declaration::Handler(h) => self.visit_handler(h, scope_idx),
Declaration::Trait(t) => self.visit_trait(t, scope_idx),
Declaration::Impl(i) => self.visit_impl(i, scope_idx),
}
}
fn visit_function(&mut self, f: &FunctionDecl, scope_idx: usize) {
let is_public = matches!(f.visibility, Visibility::Public);
// Build type signature
let param_types: Vec<String> = f.params.iter()
.map(|p| format!("{}: {}", p.name.name, self.type_expr_to_string(&p.typ)))
.collect();
let return_type = self.type_expr_to_string(&f.return_type);
let effects = if f.effects.is_empty() {
String::new()
} else {
format!(" with {{{}}}", f.effects.iter()
.map(|e| e.name.clone())
.collect::<Vec<_>>()
.join(", "))
};
let type_sig = format!("fn {}({}): {}{}", f.name.name, param_types.join(", "), return_type, effects);
let symbol = self.new_symbol(
f.name.name.clone(),
SymbolKind::Function,
f.name.span,
Some(type_sig),
is_public,
);
let fn_id = self.add_symbol(scope_idx, symbol);
self.add_reference(fn_id, f.name.span, true, false);
// Create scope for function body
let body_span = f.body.span();
let fn_scope = self.push_scope(scope_idx, body_span);
// Add type parameters
for tp in &f.type_params {
let symbol = self.new_symbol(
tp.name.clone(),
SymbolKind::TypeParameter,
tp.span,
None,
false,
);
self.add_symbol(fn_scope, symbol);
}
// Add parameters
for param in &f.params {
let type_sig = self.type_expr_to_string(&param.typ);
let symbol = self.new_symbol(
param.name.name.clone(),
SymbolKind::Parameter,
param.name.span,
Some(type_sig),
false,
);
self.add_symbol(fn_scope, symbol);
}
// Visit body
self.visit_expr(&f.body, fn_scope);
}
fn visit_type_decl(&mut self, t: &TypeDecl, scope_idx: usize) {
let is_public = matches!(t.visibility, Visibility::Public);
let type_sig = format!("type {}", t.name.name);
let symbol = self.new_symbol(
t.name.name.clone(),
SymbolKind::Type,
t.name.span,
Some(type_sig),
is_public,
);
let type_id = self.add_symbol(scope_idx, symbol);
self.add_reference(type_id, t.name.span, true, false);
// Add variants
match &t.definition {
TypeDef::Enum(variants) => {
for variant in variants {
let mut var_symbol = self.new_symbol(
variant.name.name.clone(),
SymbolKind::Variant,
variant.name.span,
None,
is_public,
);
var_symbol.parent = Some(type_id);
self.add_symbol(scope_idx, var_symbol);
}
}
TypeDef::Record(fields) => {
for field in fields {
let mut field_symbol = self.new_symbol(
field.name.name.clone(),
SymbolKind::Field,
field.name.span,
Some(self.type_expr_to_string(&field.typ)),
is_public,
);
field_symbol.parent = Some(type_id);
self.add_symbol(scope_idx, field_symbol);
}
}
TypeDef::Alias(_) => {}
}
}
fn visit_effect(&mut self, e: &EffectDecl, scope_idx: usize) {
let is_public = true; // Effects are typically public
let type_sig = format!("effect {}", e.name.name);
let symbol = self.new_symbol(
e.name.name.clone(),
SymbolKind::Effect,
e.name.span,
Some(type_sig),
is_public,
);
let effect_id = self.add_symbol(scope_idx, symbol);
// Add operations
for op in &e.operations {
let param_types: Vec<String> = op.params.iter()
.map(|p| format!("{}: {}", p.name.name, self.type_expr_to_string(&p.typ)))
.collect();
let return_type = self.type_expr_to_string(&op.return_type);
let op_sig = format!("fn {}({}): {}", op.name.name, param_types.join(", "), return_type);
let mut op_symbol = self.new_symbol(
op.name.name.clone(),
SymbolKind::EffectOperation,
op.name.span,
Some(op_sig),
is_public,
);
op_symbol.parent = Some(effect_id);
self.add_symbol(scope_idx, op_symbol);
}
}
fn visit_handler(&mut self, _h: &HandlerDecl, _scope_idx: usize) {
// Handlers are complex - visit their implementations
}
fn visit_trait(&mut self, t: &TraitDecl, scope_idx: usize) {
let is_public = matches!(t.visibility, Visibility::Public);
let type_sig = format!("trait {}", t.name.name);
let symbol = self.new_symbol(
t.name.name.clone(),
SymbolKind::Type, // Traits are like types
t.name.span,
Some(type_sig),
is_public,
);
self.add_symbol(scope_idx, symbol);
}
fn visit_impl(&mut self, _i: &ImplDecl, _scope_idx: usize) {
// Impl blocks add methods to types
}
fn visit_expr(&mut self, expr: &Expr, scope_idx: usize) {
match expr {
Expr::Var(ident) => {
// Look up the identifier and add a reference
if let Some(id) = self.lookup(&ident.name, scope_idx) {
self.add_reference(id, ident.span, false, false);
}
}
Expr::Let { name, value, body, span, .. } => {
// Visit the value first
self.visit_expr(value, scope_idx);
// Create a new scope for the let binding
let let_scope = self.push_scope(scope_idx, *span);
// Add the variable
let symbol = self.new_symbol(
name.name.clone(),
SymbolKind::Variable,
name.span,
None,
false,
);
let var_id = self.add_symbol(let_scope, symbol);
self.add_reference(var_id, name.span, true, true);
// Visit the body
self.visit_expr(body, let_scope);
}
Expr::Lambda { params, body, span, .. } => {
let lambda_scope = self.push_scope(scope_idx, *span);
for param in params {
let symbol = self.new_symbol(
param.name.name.clone(),
SymbolKind::Parameter,
param.name.span,
None,
false,
);
self.add_symbol(lambda_scope, symbol);
}
self.visit_expr(body, lambda_scope);
}
Expr::Call { func, args, .. } => {
self.visit_expr(func, scope_idx);
for arg in args {
self.visit_expr(arg, scope_idx);
}
}
Expr::EffectOp { args, .. } => {
for arg in args {
self.visit_expr(arg, scope_idx);
}
}
Expr::Field { object, .. } => {
self.visit_expr(object, scope_idx);
}
Expr::If { condition, then_branch, else_branch, .. } => {
self.visit_expr(condition, scope_idx);
self.visit_expr(then_branch, scope_idx);
self.visit_expr(else_branch, scope_idx);
}
Expr::Match { scrutinee, arms, .. } => {
self.visit_expr(scrutinee, scope_idx);
for arm in arms {
// Each arm may bind variables
let arm_scope = self.push_scope(scope_idx, arm.body.span());
self.visit_pattern(&arm.pattern, arm_scope);
if let Some(ref guard) = arm.guard {
self.visit_expr(guard, arm_scope);
}
self.visit_expr(&arm.body, arm_scope);
}
}
Expr::Block { statements, result, .. } => {
for stmt in statements {
self.visit_statement(stmt, scope_idx);
}
self.visit_expr(result, scope_idx);
}
Expr::BinaryOp { left, right, .. } => {
self.visit_expr(left, scope_idx);
self.visit_expr(right, scope_idx);
}
Expr::UnaryOp { operand, .. } => {
self.visit_expr(operand, scope_idx);
}
Expr::List { elements, .. } => {
for e in elements {
self.visit_expr(e, scope_idx);
}
}
Expr::Tuple { elements, .. } => {
for e in elements {
self.visit_expr(e, scope_idx);
}
}
Expr::Record { fields, .. } => {
for (_, e) in fields {
self.visit_expr(e, scope_idx);
}
}
Expr::Run { expr, handlers, .. } => {
self.visit_expr(expr, scope_idx);
for (_effect, handler_expr) in handlers {
self.visit_expr(handler_expr, scope_idx);
}
}
Expr::Resume { value, .. } => {
self.visit_expr(value, scope_idx);
}
// Literals don't need symbol resolution
Expr::Literal(_) => {}
}
}
fn visit_statement(&mut self, stmt: &Statement, scope_idx: usize) {
match stmt {
Statement::Expr(e) => self.visit_expr(e, scope_idx),
Statement::Let { name, value, .. } => {
self.visit_expr(value, scope_idx);
let symbol = self.new_symbol(
name.name.clone(),
SymbolKind::Variable,
name.span,
None,
false,
);
let id = self.add_symbol(scope_idx, symbol);
self.add_reference(id, name.span, true, true);
}
}
}
fn visit_pattern(&mut self, pattern: &Pattern, scope_idx: usize) {
match pattern {
Pattern::Var(ident) => {
let symbol = self.new_symbol(
ident.name.clone(),
SymbolKind::Variable,
ident.span,
None,
false,
);
let id = self.add_symbol(scope_idx, symbol);
self.add_reference(id, ident.span, true, true);
}
Pattern::Constructor { fields, .. } => {
for p in fields {
self.visit_pattern(p, scope_idx);
}
}
Pattern::Tuple { elements, .. } => {
for p in elements {
self.visit_pattern(p, scope_idx);
}
}
Pattern::Record { fields, .. } => {
for (_, p) in fields {
self.visit_pattern(p, scope_idx);
}
}
Pattern::Wildcard(_) => {}
Pattern::Literal(_) => {}
}
}
fn type_expr_to_string(&self, typ: &TypeExpr) -> String {
match typ {
TypeExpr::Named(ident) => ident.name.clone(),
TypeExpr::App(base, args) => {
let base_str = self.type_expr_to_string(base);
if args.is_empty() {
base_str
} else {
let args_str: Vec<String> = args.iter()
.map(|a| self.type_expr_to_string(a))
.collect();
format!("{}<{}>", base_str, args_str.join(", "))
}
}
TypeExpr::Function { params, return_type, .. } => {
let params_str: Vec<String> = params.iter()
.map(|p| self.type_expr_to_string(p))
.collect();
format!("fn({}): {}", params_str.join(", "), self.type_expr_to_string(return_type))
}
TypeExpr::Tuple(types) => {
let types_str: Vec<String> = types.iter()
.map(|t| self.type_expr_to_string(t))
.collect();
format!("({})", types_str.join(", "))
}
TypeExpr::Record(fields) => {
let fields_str: Vec<String> = fields.iter()
.map(|f| format!("{}: {}", f.name, self.type_expr_to_string(&f.typ)))
.collect();
format!("{{ {} }}", fields_str.join(", "))
}
TypeExpr::Unit => "Unit".to_string(),
TypeExpr::Versioned { base, .. } => {
format!("{}@versioned", self.type_expr_to_string(base))
}
}
}
}
impl Default for SymbolTable {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::Parser;
#[test]
fn test_symbol_table_basic() {
let source = r#"
fn add(a: Int, b: Int): Int = a + b
let x = 42
"#;
let program = Parser::parse_source(source).unwrap();
let table = SymbolTable::build(&program);
// Should have add function and x variable
let globals = table.global_symbols();
assert!(globals.iter().any(|s| s.name == "add"));
assert!(globals.iter().any(|s| s.name == "x"));
}
#[test]
fn test_symbol_lookup() {
let source = r#"
fn foo(x: Int): Int = x + 1
"#;
let program = Parser::parse_source(source).unwrap();
let table = SymbolTable::build(&program);
// Should be able to find foo
assert!(table.lookup("foo", 0).is_some());
}
}