feat: add extern fn declarations for JS FFI
Adds `extern fn` syntax for declaring external JavaScript functions: extern fn getElementById(id: String): Element extern fn getContext(el: Element, kind: String): CanvasCtx = "getContext" pub extern fn alert(msg: String): Unit Changes across 11 files: - Lexer: `extern` keyword - AST: `ExternFnDecl` struct + `Declaration::ExternFn` variant - Parser: parse `extern fn` with optional `= "jsName"` override - Typechecker: register extern fn type signatures - Interpreter: ExternFn value with clear error on call - JS backend: emit extern fn calls using JS name (no _lux suffix) - C backend: silently skips extern fns - Formatter, linter, modules, symbol_table: handle new variant Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -176,6 +176,11 @@ pub enum Value {
|
||||
},
|
||||
/// JSON value (for JSON parsing/manipulation)
|
||||
Json(serde_json::Value),
|
||||
/// Extern function (FFI — only callable from JS backend)
|
||||
ExternFn {
|
||||
name: String,
|
||||
arity: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl Value {
|
||||
@@ -197,6 +202,7 @@ impl Value {
|
||||
Value::Constructor { .. } => "Constructor",
|
||||
Value::Versioned { .. } => "Versioned",
|
||||
Value::Json(_) => "Json",
|
||||
Value::ExternFn { .. } => "ExternFn",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,6 +413,7 @@ impl fmt::Display for Value {
|
||||
write!(f, "{} @v{}", value, version)
|
||||
}
|
||||
Value::Json(json) => write!(f, "{}", json),
|
||||
Value::ExternFn { name, .. } => write!(f, "<extern fn {}>", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1405,6 +1412,25 @@ impl Interpreter {
|
||||
Ok(Value::Unit)
|
||||
}
|
||||
|
||||
Declaration::ExternFn(ext) => {
|
||||
// Register a placeholder that errors at runtime
|
||||
let name = ext.name.name.clone();
|
||||
let arity = ext.params.len();
|
||||
// Create a closure that produces a clear error
|
||||
let closure = Closure {
|
||||
params: ext.params.iter().map(|p| p.name.name.clone()).collect(),
|
||||
body: Expr::Literal(crate::ast::Literal {
|
||||
kind: crate::ast::LiteralKind::Unit,
|
||||
span: ext.span,
|
||||
}),
|
||||
env: self.global_env.clone(),
|
||||
};
|
||||
// We store an ExternFn marker value
|
||||
self.global_env
|
||||
.define(&name, Value::ExternFn { name: name.clone(), arity });
|
||||
Ok(Value::Unit)
|
||||
}
|
||||
|
||||
Declaration::Effect(_) | Declaration::Trait(_) | Declaration::Impl(_) => {
|
||||
// These are compile-time only
|
||||
Ok(Value::Unit)
|
||||
@@ -1924,6 +1950,13 @@ impl Interpreter {
|
||||
}))
|
||||
}
|
||||
Value::Builtin(builtin) => self.eval_builtin(builtin, args, span),
|
||||
Value::ExternFn { name, .. } => Err(RuntimeError {
|
||||
message: format!(
|
||||
"Extern function '{}' can only be called when compiled to JavaScript (use `lux build --target js`)",
|
||||
name
|
||||
),
|
||||
span: Some(span),
|
||||
}),
|
||||
v => Err(RuntimeError {
|
||||
message: format!("Cannot call {}", v.type_name()),
|
||||
span: Some(span),
|
||||
|
||||
Reference in New Issue
Block a user