use crate::parser::{Expr, InterpolPart};
use crate::types::{Action, ImplicitMutability, Module, Typing, Value};
use std::collections::{HashMap, HashSet};
use std::ffi::{CStr, CString};
use std::io::{self, Write};
pub type ErrorHandler = Box<dyn Fn(&LangError) -> String + Send + Sync>;
#[derive(Debug, Clone)]
pub struct LangError {
pub kind: ErrorKind,
pub message: String,
pub line: Option<usize>,
pub hint: Option<String>,
}
#[derive(Debug, Clone)]
pub enum ErrorKind {
UndefinedVariable,
UndefinedFunction,
TypeError,
DivisionByZero,
Runtime,
Custom(String),
}
impl LangError {
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
line: None,
hint: None,
}
}
pub fn with_line(mut self, line: usize) -> Self {
self.line = Some(line);
self
}
pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
self.hint = Some(hint.into());
self
}
}
impl std::fmt::Display for LangError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(line) = self.line {
write!(f, "Line {}: ", line)?;
}
write!(f, "{}", self.message)?;
if let Some(hint) = &self.hint {
write!(f, "\n hint: {}", hint)?;
}
Ok(())
}
}
fn default_error_handler(e: &LangError) -> String {
format!("{}", e)
}
pub enum Signal {
Return(Value),
Break,
}
pub struct Interpreter {
pub scopes: Vec<HashMap<String, Value>>,
pub immutables: Vec<HashSet<String>>,
pub soft_immutables: Vec<HashSet<String>>,
pub functions: HashMap<String, (Vec<String>, Vec<Expr>)>,
pub bc_functions: HashMap<String, (Vec<String>, Vec<Instr>)>,
pub actions: HashMap<String, Action>,
pub modules: HashMap<String, Module>,
pub loaded_modules: HashSet<String>,
pub ffi_libs: HashMap<String, String>,
pub ffi_handles: Vec<libloading::Library>,
pub var_types: Vec<HashMap<String, TypeTag>>,
pub typing: Typing,
pub implicit_mutability: ImplicitMutability,
pub debug: bool,
pub error_handler: ErrorHandler,
}
impl Default for Interpreter {
fn default() -> Self {
Self::new()
}
}
impl Interpreter {
pub fn new() -> Self {
let mut interp = Self {
scopes: vec![HashMap::new()],
immutables: vec![HashSet::new()],
soft_immutables: vec![HashSet::new()],
functions: HashMap::new(),
bc_functions: HashMap::new(),
actions: HashMap::new(),
modules: HashMap::new(),
loaded_modules: HashSet::new(),
ffi_libs: HashMap::new(),
ffi_handles: Vec::new(),
var_types: vec![HashMap::new()],
typing: Typing::Dynamic,
implicit_mutability: ImplicitMutability::Mutable,
debug: false,
error_handler: Box::new(default_error_handler),
};
interp.register_builtins();
interp
}
pub fn set_error_handler(&mut self, handler: ErrorHandler) {
self.error_handler = handler;
}
pub fn set_typing(&mut self, typing: Typing) {
self.typing = typing;
}
pub fn set_implicit_mutability(&mut self, mode: ImplicitMutability) {
self.implicit_mutability = mode;
}
pub fn register_action(&mut self, name: &str, action: Action) {
self.actions.insert(name.to_string(), action);
}
pub fn register_module(&mut self, module: Module) {
self.modules.insert(module.name.clone(), module);
}
pub fn register_ffi_libs(&mut self, libs: Vec<String>) {
for lib in libs {
if let Some(name) = std::path::Path::new(&lib)
.file_stem()
.and_then(|s| s.to_str())
{
self.ffi_libs.insert(name.to_string(), lib.clone());
}
self.ffi_libs.insert(lib.clone(), lib);
}
}
fn push_scope(&mut self) {
self.scopes.push(HashMap::new());
self.immutables.push(HashSet::new());
self.soft_immutables.push(HashSet::new());
self.var_types.push(HashMap::new());
}
fn pop_scope(&mut self) {
self.scopes.pop();
self.immutables.pop();
self.soft_immutables.pop();
self.var_types.pop();
if self.scopes.is_empty() {
self.scopes.push(HashMap::new());
self.immutables.push(HashSet::new());
self.soft_immutables.push(HashSet::new());
self.var_types.push(HashMap::new());
}
}
fn current_scope_index(&self) -> usize {
self.scopes.len().saturating_sub(1)
}
fn find_scope_index(&self, name: &str) -> Option<usize> {
self.scopes
.iter()
.enumerate()
.rev()
.find(|(_, scope)| scope.contains_key(name))
.map(|(i, _)| i)
}
fn get_var(&self, name: &str) -> Option<Value> {
for scope in self.scopes.iter().rev() {
if let Some(val) = scope.get(name) {
return Some(val.clone());
}
}
None
}
fn define_var(&mut self, name: &str, value: Value, is_mut: bool) {
let idx = self.current_scope_index();
if let Some(scope) = self.scopes.get_mut(idx) {
scope.insert(name.to_string(), value);
}
if !is_mut {
self.immutables[idx].insert(name.to_string());
self.soft_immutables[idx].remove(name);
} else {
self.immutables[idx].remove(name);
self.soft_immutables[idx].remove(name);
}
}
fn assign_var(&mut self, name: &str, value: Value) -> Result<(), String> {
if let Some(idx) = self.find_scope_index(name) {
if self.immutables[idx].contains(name) {
let e = LangError::new(
ErrorKind::Runtime,
format!("Cannot reassign immutable variable '{}'", name),
)
.with_hint("Declare it as mutable to allow reassignment");
return Err((self.error_handler)(&e));
}
if self.soft_immutables[idx].remove(name) {
}
if self.typing != Typing::Dynamic {
if let Some(expected) = self.var_types[idx].get(name) {
self.ensure_type(name, expected, &value)?;
}
}
if let Some(scope) = self.scopes.get_mut(idx) {
scope.insert(name.to_string(), value);
}
return Ok(());
}
if self.typing == Typing::Static {
let e = LangError::new(
ErrorKind::TypeError,
format!(
"Static typing requires an explicit declaration for '{}'",
name
),
)
.with_hint("Use a declaration with a type, e.g. let x: int = 1");
return Err((self.error_handler)(&e));
}
let idx = self.current_scope_index();
if let Some(scope) = self.scopes.get_mut(idx) {
scope.insert(name.to_string(), value);
}
match self.implicit_mutability {
ImplicitMutability::Mutable => {}
ImplicitMutability::Immutable => {
self.immutables[idx].insert(name.to_string());
}
ImplicitMutability::Infer => {
self.soft_immutables[idx].insert(name.to_string());
}
}
Ok(())
}
pub fn snapshot_vars(&self) -> HashMap<String, Value> {
let mut out = HashMap::new();
for scope in &self.scopes {
for (k, v) in scope {
out.insert(k.clone(), v.clone());
}
}
out
}
fn register_builtins(&mut self) {
self.actions.insert(
"len".to_string(),
Box::new(|args| match args.first() {
Some(Value::Str(s)) => Value::Int(s.len() as i64),
Some(Value::Array(a)) => Value::Int(a.len() as i64),
_ => Value::Int(0),
}),
);
self.actions.insert(
"type".to_string(),
Box::new(|args| {
Value::Str(
match args.first() {
Some(Value::Int(_)) => "int",
Some(Value::Float(_)) => "float",
Some(Value::Str(_)) => "string",
Some(Value::Bool(_)) => "bool",
Some(Value::Array(_)) => "array",
_ => "null",
}
.to_string(),
)
}),
);
self.actions.insert(
"to_int".to_string(),
Box::new(|args| match args.first() {
Some(Value::Float(f)) => Value::Int(*f as i64),
Some(Value::Int(i)) => Value::Int(*i),
Some(Value::Str(s)) => s.parse::<i64>().map(Value::Int).unwrap_or(Value::Null),
Some(Value::Bool(b)) => Value::Int(if *b { 1 } else { 0 }),
_ => Value::Null,
}),
);
self.actions.insert(
"to_float".to_string(),
Box::new(|args| match args.first() {
Some(Value::Int(i)) => Value::Float(*i as f64),
Some(Value::Float(f)) => Value::Float(*f),
Some(Value::Str(s)) => s.parse::<f64>().map(Value::Float).unwrap_or(Value::Null),
_ => Value::Null,
}),
);
self.actions.insert(
"to_str".to_string(),
Box::new(|args| Value::Str(args.first().map(|v| v.to_string()).unwrap_or_default())),
);
self.actions.insert(
"input".to_string(),
Box::new(|args| {
if let Some(Value::Str(p)) = args.first() {
print!("{}", p);
io::stdout().flush().ok();
}
let mut line = String::new();
io::stdin().read_line(&mut line).ok();
Value::Str(line.trim().to_string())
}),
);
self.actions.insert(
"push".to_string(),
Box::new(|args| {
if let (Some(Value::Array(mut arr)), Some(val)) =
(args.first().cloned(), args.get(1).cloned())
{
arr.push(val);
Value::Array(arr)
} else {
Value::Null
}
}),
);
self.actions.insert(
"pop".to_string(),
Box::new(|args| {
if let Some(Value::Array(mut arr)) = args.first().cloned() {
arr.pop();
Value::Array(arr)
} else {
Value::Null
}
}),
);
self.actions.insert(
"map".to_string(),
Box::new(|args| {
if let (Some(Value::Array(arr)), Some(Value::Str(_fn_name))) =
(args.first(), args.get(1))
{
Value::Array(arr.iter().map(|_| Value::Null).collect())
} else {
Value::Null
}
}),
);
self.actions.insert(
"filter".to_string(),
Box::new(|args| {
if let Some(Value::Array(arr)) = args.first() {
Value::Array(arr.clone())
} else {
Value::Null
}
}),
);
self.actions.insert(
"find".to_string(),
Box::new(|args| {
if let (Some(Value::Array(arr)), Some(val)) = (args.first(), args.get(1)) {
arr.iter()
.position(|v| v == val)
.map(|i| Value::Int(i as i64))
.unwrap_or(Value::Int(-1))
} else {
Value::Int(-1)
}
}),
);
self.actions.insert(
"join".to_string(),
Box::new(|args| {
if let (Some(Value::Array(arr)), Some(Value::Str(sep))) =
(args.first(), args.get(1))
{
let s: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
Value::Str(s.join(sep))
} else {
Value::Null
}
}),
);
self.actions.insert(
"slice".to_string(),
Box::new(|args| match (args.first(), args.get(1), args.get(2)) {
(Some(Value::Array(arr)), Some(Value::Int(start)), Some(Value::Int(end))) => {
let start = (*start as usize).min(arr.len());
let end = (*end as usize).min(arr.len());
Value::Array(arr[start..end].to_vec())
}
(Some(Value::Str(s)), Some(Value::Int(start)), Some(Value::Int(end))) => {
let start = (*start as usize).min(s.len());
let end = (*end as usize).min(s.len());
Value::Str(s[start..end].to_string())
}
_ => Value::Null,
}),
);
self.actions.insert(
"split".to_string(),
Box::new(|args| {
if let (Some(Value::Str(s)), Some(Value::Str(sep))) = (args.first(), args.get(1)) {
Value::Array(s.split(sep).map(|p| Value::Str(p.to_string())).collect())
} else if let Some(Value::Str(s)) = args.first() {
Value::Array(
s.split_whitespace()
.map(|p| Value::Str(p.to_string()))
.collect(),
)
} else {
Value::Null
}
}),
);
self.actions.insert(
"trim".to_string(),
Box::new(|args| match args.first() {
Some(Value::Str(s)) => Value::Str(s.trim().to_string()),
_ => Value::Null,
}),
);
self.actions.insert(
"upper".to_string(),
Box::new(|args| match args.first() {
Some(Value::Str(s)) => Value::Str(s.to_uppercase()),
_ => Value::Null,
}),
);
self.actions.insert(
"lower".to_string(),
Box::new(|args| match args.first() {
Some(Value::Str(s)) => Value::Str(s.to_lowercase()),
_ => Value::Null,
}),
);
self.actions.insert(
"contains".to_string(),
Box::new(|args| match (args.first(), args.get(1)) {
(Some(Value::Str(s)), Some(Value::Str(sub))) => Value::Bool(s.contains(sub)),
_ => Value::Bool(false),
}),
);
self.actions.insert(
"replace".to_string(),
Box::new(|args| match (args.first(), args.get(1), args.get(2)) {
(Some(Value::Str(s)), Some(Value::Str(from)), Some(Value::Str(to))) => {
Value::Str(s.replace(from, to))
}
_ => Value::Null,
}),
);
self.actions.insert(
"starts_with".to_string(),
Box::new(|args| match (args.first(), args.get(1)) {
(Some(Value::Str(s)), Some(Value::Str(prefix))) => {
Value::Bool(s.starts_with(prefix))
}
_ => Value::Bool(false),
}),
);
self.actions.insert(
"ends_with".to_string(),
Box::new(|args| match (args.first(), args.get(1)) {
(Some(Value::Str(s)), Some(Value::Str(suffix))) => Value::Bool(s.ends_with(suffix)),
_ => Value::Bool(false),
}),
);
self.actions.insert(
"sin".to_string(),
Box::new(|args| match args.first() {
Some(Value::Float(f)) => Value::Float(f.sin()),
Some(Value::Int(i)) => Value::Float((*i as f64).sin()),
_ => Value::Null,
}),
);
self.actions.insert(
"cos".to_string(),
Box::new(|args| match args.first() {
Some(Value::Float(f)) => Value::Float(f.cos()),
Some(Value::Int(i)) => Value::Float((*i as f64).cos()),
_ => Value::Null,
}),
);
self.actions.insert(
"sqrt".to_string(),
Box::new(|args| match args.first() {
Some(Value::Float(f)) => Value::Float(f.sqrt()),
Some(Value::Int(i)) => Value::Float((*i as f64).sqrt()),
_ => Value::Null,
}),
);
self.actions.insert(
"abs".to_string(),
Box::new(|args| match args.first() {
Some(Value::Int(i)) => Value::Int(i.abs()),
Some(Value::Float(f)) => Value::Float(f.abs()),
_ => Value::Null,
}),
);
self.actions.insert(
"min".to_string(),
Box::new(|args| match (args.first(), args.get(1)) {
(Some(Value::Int(a)), Some(Value::Int(b))) => Value::Int((*a).min(*b)),
(Some(Value::Float(a)), Some(Value::Float(b))) => Value::Float(a.min(*b)),
_ => Value::Null,
}),
);
self.actions.insert(
"max".to_string(),
Box::new(|args| match (args.first(), args.get(1)) {
(Some(Value::Int(a)), Some(Value::Int(b))) => Value::Int((*a).max(*b)),
(Some(Value::Float(a)), Some(Value::Float(b))) => Value::Float(a.max(*b)),
_ => Value::Null,
}),
);
self.actions.insert(
"random".to_string(),
Box::new(|_args| {
use std::time::{SystemTime, UNIX_EPOCH};
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let val = (seed.wrapping_mul(1103515245).wrapping_add(12345)) >> 16;
Value::Int((val % 32768) as i64)
}),
);
self.actions.insert(
"floor".to_string(),
Box::new(|args| match args.first() {
Some(Value::Float(f)) => Value::Int(f.floor() as i64),
Some(Value::Int(i)) => Value::Int(*i),
_ => Value::Null,
}),
);
self.actions.insert(
"ceil".to_string(),
Box::new(|args| match args.first() {
Some(Value::Float(f)) => Value::Int(f.ceil() as i64),
Some(Value::Int(i)) => Value::Int(*i),
_ => Value::Null,
}),
);
self.actions.insert(
"round".to_string(),
Box::new(|args| match args.first() {
Some(Value::Float(f)) => Value::Int(f.round() as i64),
Some(Value::Int(i)) => Value::Int(*i),
_ => Value::Null,
}),
);
self.actions.insert(
"read_file".to_string(),
Box::new(|args| match args.first() {
Some(Value::Str(path)) => match std::fs::read_to_string(path) {
Ok(content) => Value::Str(content),
Err(e) => Value::Str(format!("Error: {}", e)),
},
_ => Value::Null,
}),
);
self.actions.insert(
"write_file".to_string(),
Box::new(|args| match (args.first(), args.get(1)) {
(Some(Value::Str(path)), Some(Value::Str(content))) => {
match std::fs::write(path, content) {
Ok(()) => Value::Bool(true),
Err(e) => {
eprintln!("Write error: {}", e);
Value::Bool(false)
}
}
}
_ => Value::Bool(false),
}),
);
self.actions.insert(
"file_exists".to_string(),
Box::new(|args| match args.first() {
Some(Value::Str(path)) => Value::Bool(std::path::Path::new(path).exists()),
_ => Value::Bool(false),
}),
);
self.actions.insert(
"delete_file".to_string(),
Box::new(|args| match args.first() {
Some(Value::Str(path)) => match std::fs::remove_file(path) {
Ok(()) => Value::Bool(true),
Err(_) => Value::Bool(false),
},
_ => Value::Bool(false),
}),
);
}
pub fn eval_block(&mut self, stmts: &[Expr]) -> Result<Signal, String> {
let mut last = Value::Null;
for stmt in stmts {
match self.eval_inner(stmt)? {
Ok(Signal::Return(v)) => return Ok(Signal::Return(v)),
Ok(Signal::Break) => return Ok(Signal::Break),
Err(v) => last = v,
}
}
Ok(Signal::Return(last))
}
pub fn eval_block_scoped(&mut self, stmts: &[Expr]) -> Result<Signal, String> {
self.push_scope();
let result = self.eval_block(stmts);
self.pop_scope();
result
}
fn eval_inner(&mut self, expr: &Expr) -> Result<Result<Signal, Value>, String> {
match expr {
Expr::Return(val) => {
let v = self.eval(val)?;
Ok(Ok(Signal::Return(v)))
}
Expr::Break => Ok(Ok(Signal::Break)),
Expr::If {
cond,
then_block,
else_block,
} => {
let c = self.eval(cond)?;
if self.is_truthy(&c) {
match self.eval_block_scoped(then_block) {
Ok(signal) => Ok(Ok(signal)),
Err(e) => Err(e),
}
} else if let Some(eb) = else_block {
match self.eval_block_scoped(eb) {
Ok(signal) => Ok(Ok(signal)),
Err(e) => Err(e),
}
} else {
Ok(Err(Value::Null)) }
}
_ => Ok(Err(self.eval(expr)?)),
}
}
pub fn eval(&mut self, expr: &Expr) -> Result<Value, String> {
match expr {
Expr::Literal(v) => Ok(v.clone()),
Expr::Ident(name) => self.get_var(name).ok_or_else(|| {
let e = LangError::new(
ErrorKind::UndefinedVariable,
format!("'{}' is not defined", name),
)
.with_hint(format!(
"Declare or assign it first (e.g. let {} = ... or {} = ...)",
name, name
));
(self.error_handler)(&e)
}),
Expr::Array(items) => {
let vals: Result<Vec<_>, _> = items.iter().map(|e| self.eval(e)).collect();
Ok(Value::Array(vals?))
}
Expr::Range(start, end) => {
let s = self.eval_as_int(start)?;
let e = self.eval_as_int(end)?;
Ok(Value::Array((s..e).map(Value::Int).collect()))
}
Expr::BinOp { left, op, right } => {
let l = self.eval(left)?;
let r = self.eval(right)?;
self.eval_binop(l, op, r)
}
Expr::Assign { name, value } => {
let val = self.eval(value)?;
self.assign_var(name, val.clone())?;
Ok(val)
}
Expr::VarDecl {
name,
value,
is_mut,
type_hint,
} => {
let val = self.eval(value)?;
if self.typing == Typing::Static && type_hint.is_none() {
let e = LangError::new(
ErrorKind::TypeError,
format!("Static typing requires a type for '{}'", name),
)
.with_hint("Add a type hint, e.g. let x: int = 1");
return Err((self.error_handler)(&e));
}
if self.typing != Typing::Dynamic {
if let Some(hint) = type_hint {
let tag = parse_type_hint(hint).ok_or_else(|| {
let e = LangError::new(
ErrorKind::TypeError,
format!("Unknown type '{}'", hint),
)
.with_hint("Use int, float, string, bool, array, or null");
(self.error_handler)(&e)
})?;
self.ensure_type(name, &tag, &val)?;
let idx = self.current_scope_index();
self.var_types[idx].insert(name.clone(), tag);
} else if self.typing == Typing::Optional {
let idx = self.current_scope_index();
self.var_types[idx].remove(name);
}
} else {
let idx = self.current_scope_index();
self.var_types[idx].remove(name);
}
self.define_var(name, val.clone(), *is_mut);
Ok(val)
}
Expr::Call { keyword, args } => {
let vals: Result<Vec<_>, _> = args.iter().map(|a| self.eval(a)).collect();
let vals = vals?;
if self.actions.contains_key(keyword.as_str()) {
return Ok((self.actions[keyword.as_str()])(vals));
}
if let Some((params, body)) = self.functions.get(keyword).cloned() {
self.push_scope();
for (i, param) in params.iter().enumerate() {
let val = vals.get(i).cloned().unwrap_or(Value::Null);
self.define_var(param, val, true);
}
let result = self.eval_block(&body)?;
self.pop_scope();
return match result {
Signal::Return(v) => Ok(v),
Signal::Break => Ok(Value::Null),
};
}
let e = LangError::new(
ErrorKind::UndefinedFunction,
format!("'{}' is not defined", keyword),
)
.with_hint("Check for typos or declare the function before use");
Err((self.error_handler)(&e))
}
Expr::If {
cond,
then_block,
else_block,
} => {
let c = self.eval(cond)?;
let result = if self.is_truthy(&c) {
self.eval_block_scoped(then_block)
} else if let Some(eb) = else_block {
self.eval_block_scoped(eb)
} else {
Ok(Signal::Return(Value::Null))
};
match result {
Ok(Signal::Return(v)) => Ok(v),
Ok(Signal::Break) => Ok(Value::Null),
Err(e) => Err(e),
}
}
Expr::Loop { body } => loop {
self.push_scope();
for stmt in body {
match self.eval_inner(stmt)? {
Ok(Signal::Return(v)) => {
self.pop_scope();
return Ok(v);
}
Ok(Signal::Break) => {
self.pop_scope();
return Ok(Value::Null);
}
Err(_) => {}
}
}
self.pop_scope();
},
Expr::While { cond, body } => {
loop {
let c = self.eval(cond)?;
if !self.is_truthy(&c) {
break;
}
self.push_scope();
for stmt in body {
match self.eval_inner(stmt)? {
Ok(Signal::Return(v)) => {
self.pop_scope();
return Ok(v);
}
Ok(Signal::Break) => {
self.pop_scope();
return Ok(Value::Null);
}
Err(_) => {}
}
}
self.pop_scope();
}
Ok(Value::Null)
}
Expr::For { var, iter, body } => {
let iterable = self.eval(iter)?;
let items = match iterable {
Value::Array(arr) => arr,
other => {
let e = LangError::new(
ErrorKind::TypeError,
format!("'{}' is not iterable", other),
)
.with_hint("for..in requires an array or range");
return Err((self.error_handler)(&e));
}
};
for item in items {
self.push_scope();
self.define_var(var, item, true);
for stmt in body {
match self.eval_inner(stmt)? {
Ok(Signal::Return(v)) => {
self.pop_scope();
return Ok(v);
}
Ok(Signal::Break) => {
self.pop_scope();
return Ok(Value::Null);
}
Err(_) => {}
}
}
self.pop_scope();
}
Ok(Value::Null)
}
Expr::FnDef { name, params, body } => {
self.functions
.insert(name.clone(), (params.clone(), body.clone()));
Ok(Value::Null)
}
Expr::Return(val) => self.eval(val),
Expr::Break => Ok(Value::Null),
Expr::Switch {
value,
cases,
default,
} => {
let v = self.eval(value)?;
for (case_val, body) in cases {
let cv = self.eval(case_val)?;
if self.values_equal(&v, &cv) {
return match self.eval_block_scoped(body)? {
Signal::Return(val) => Ok(val),
Signal::Break => Ok(Value::Null),
};
}
}
if let Some(body) = default {
match self.eval_block_scoped(body)? {
Signal::Return(val) => Ok(val),
Signal::Break => Ok(Value::Null),
}
} else {
Ok(Value::Null)
}
}
Expr::Lambda { params, body } => {
let id = format!("lambda_{}", self.functions.len());
self.functions
.insert(id.clone(), (params.clone(), body.clone()));
Ok(Value::Str(id)) }
Expr::Try { body, catch_body } => match self.eval_block_scoped(body) {
Ok(Signal::Return(v)) => Ok(v),
Ok(Signal::Break) => Ok(Value::Null),
Err(e) => {
self.push_scope();
self.define_var("error", Value::Str(e), false);
let result = match self.eval_block(catch_body) {
Ok(Signal::Return(v)) => Ok(v),
Ok(Signal::Break) => Ok(Value::Null),
Err(e2) => Err(e2),
};
self.pop_scope();
result
}
},
Expr::Import(path) => {
if self.debug {
eprintln!("[langkit] import: {}", path);
}
self.load_module(path)?;
Ok(Value::Null)
}
Expr::Interpolated(parts) => {
let mut result = String::new();
for part in parts {
match part {
InterpolPart::Text(t) => result.push_str(t),
InterpolPart::Expr(e) => result.push_str(&self.eval(e)?.to_string()),
}
}
Ok(Value::Str(result))
}
}
}
fn eval_binop(&self, l: Value, op: &str, r: Value) -> Result<Value, String> {
match op {
"+" => match (l, r) {
(Value::Int(a), Value::Int(b)) => Ok(Value::Int(a + b)),
(Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)),
(Value::Int(a), Value::Float(b)) => Ok(Value::Float(a as f64 + b)),
(Value::Float(a), Value::Int(b)) => Ok(Value::Float(a + b as f64)),
(Value::Str(a), Value::Str(b)) => Ok(Value::Str(a + &b)),
(Value::Str(a), b) => Ok(Value::Str(a + &b.to_string())),
(a, b) => Err(self.type_error("+", &a, &b)),
},
"-" => match (l, r) {
(Value::Int(a), Value::Int(b)) => Ok(Value::Int(a - b)),
(Value::Float(a), Value::Float(b)) => Ok(Value::Float(a - b)),
(Value::Int(a), Value::Float(b)) => Ok(Value::Float(a as f64 - b)),
(Value::Float(a), Value::Int(b)) => Ok(Value::Float(a - b as f64)),
(a, b) => Err(self.type_error("-", &a, &b)),
},
"*" => match (l, r) {
(Value::Int(a), Value::Int(b)) => Ok(Value::Int(a * b)),
(Value::Float(a), Value::Float(b)) => Ok(Value::Float(a * b)),
(Value::Int(a), Value::Float(b)) => Ok(Value::Float(a as f64 * b)),
(Value::Float(a), Value::Int(b)) => Ok(Value::Float(a * b as f64)),
(a, b) => Err(self.type_error("*", &a, &b)),
},
"/" => match (l, r) {
(Value::Int(a), Value::Int(b)) if b != 0 => Ok(Value::Int(a / b)),
(Value::Float(a), Value::Float(b)) if b != 0.0 => Ok(Value::Float(a / b)),
(Value::Int(a), Value::Float(b)) if b != 0.0 => Ok(Value::Float(a as f64 / b)),
(Value::Float(a), Value::Int(b)) if b != 0 => Ok(Value::Float(a / b as f64)),
_ => Err((self.error_handler)(&LangError::new(
ErrorKind::DivisionByZero,
"division by zero",
))),
},
"%" => match (l, r) {
(Value::Int(a), Value::Int(b)) if b != 0 => Ok(Value::Int(a % b)),
(a, b) => Err(self.type_error("%", &a, &b)),
},
"==" => Ok(Value::Bool(self.values_equal(&l, &r))),
"!=" => Ok(Value::Bool(!self.values_equal(&l, &r))),
"<" => self.cmp(&l, &r).map(|c| Value::Bool(c < 0)),
">" => self.cmp(&l, &r).map(|c| Value::Bool(c > 0)),
"<=" => self.cmp(&l, &r).map(|c| Value::Bool(c <= 0)),
">=" => self.cmp(&l, &r).map(|c| Value::Bool(c >= 0)),
op => Err(format!("Unknown operator: '{}'", op)),
}
}
fn type_error(&self, op: &str, a: &Value, b: &Value) -> String {
let e = LangError::new(
ErrorKind::TypeError,
format!(
"Cannot use '{}' with {} and {}",
op,
value_type_name(a),
value_type_name(b)
),
)
.with_hint("Try converting with to_int() or to_str()");
(self.error_handler)(&e)
}
fn values_equal(&self, a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Int(x), Value::Int(y)) => x == y,
(Value::Float(x), Value::Float(y)) => x == y,
(Value::Int(x), Value::Float(y)) => (*x as f64) == *y,
(Value::Float(x), Value::Int(y)) => *x == (*y as f64),
(Value::Str(x), Value::Str(y)) => x == y,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::Null, Value::Null) => true,
_ => false,
}
}
fn cmp(&self, a: &Value, b: &Value) -> Result<i32, String> {
match (a, b) {
(Value::Int(x), Value::Int(y)) => Ok(x.cmp(y) as i32),
(Value::Float(x), Value::Float(y)) => {
Ok(x.partial_cmp(y).map(|o| o as i32).unwrap_or(0))
}
(Value::Int(x), Value::Float(y)) => {
Ok((*x as f64).partial_cmp(y).map(|o| o as i32).unwrap_or(0))
}
(Value::Float(x), Value::Int(y)) => {
Ok(x.partial_cmp(&(*y as f64)).map(|o| o as i32).unwrap_or(0))
}
_ => Err(self.type_error("</>", a, b)),
}
}
fn is_truthy(&self, v: &Value) -> bool {
match v {
Value::Bool(b) => *b,
Value::Null => false,
Value::Int(0) => false,
Value::Str(s) if s.is_empty() => false,
_ => true,
}
}
fn eval_as_int(&mut self, expr: &Expr) -> Result<i64, String> {
match self.eval(expr)? {
Value::Int(n) => Ok(n),
Value::Float(n) => Ok(n as i64),
v => Err((self.error_handler)(&LangError::new(
ErrorKind::TypeError,
format!("Expected number, got {}", value_type_name(&v)),
))),
}
}
fn run_vm_block_outcome(&mut self, instrs: &[Instr]) -> Result<VmOutcome, String> {
let mut stack: Vec<Value> = Vec::new();
for instr in instrs {
match instr {
Instr::Push(v) => stack.push(v.clone()),
Instr::Load(name) => {
let val = self.get_var(name).ok_or_else(|| {
let e = LangError::new(
ErrorKind::UndefinedVariable,
format!("'{}' is not defined", name),
);
(self.error_handler)(&e)
})?;
stack.push(val);
}
Instr::Store(name) => {
let val = stack.pop().unwrap_or(Value::Null);
self.assign_var(name, val.clone())?;
stack.push(val);
}
Instr::Declare {
name,
is_mut,
type_hint,
} => {
let val = stack.pop().unwrap_or(Value::Null);
if self.typing == Typing::Static && type_hint.is_none() {
let e = LangError::new(
ErrorKind::TypeError,
format!("Static typing requires a type for '{}'", name),
)
.with_hint("Add a type hint, e.g. let x: int = 1");
return Err((self.error_handler)(&e));
}
if self.typing != Typing::Dynamic {
if let Some(hint) = type_hint {
let tag = parse_type_hint(hint).ok_or_else(|| {
let e = LangError::new(
ErrorKind::TypeError,
format!("Unknown type '{}'", hint),
)
.with_hint("Use int, float, string, bool, array, or null");
(self.error_handler)(&e)
})?;
self.ensure_type(name, &tag, &val)?;
let idx = self.current_scope_index();
self.var_types[idx].insert(name.clone(), tag);
} else if self.typing == Typing::Optional {
let idx = self.current_scope_index();
self.var_types[idx].remove(name);
}
} else {
let idx = self.current_scope_index();
self.var_types[idx].remove(name);
}
self.define_var(name, val.clone(), *is_mut);
stack.push(val);
}
Instr::Pop => {
stack.pop();
}
Instr::BinOp(op) => {
let r = stack.pop().unwrap_or(Value::Null);
let l = stack.pop().unwrap_or(Value::Null);
let v = self.eval_binop(l, op, r)?;
stack.push(v);
}
Instr::MakeArray(count) => {
let mut items = Vec::with_capacity(*count);
for _ in 0..*count {
items.push(stack.pop().unwrap_or(Value::Null));
}
items.reverse();
stack.push(Value::Array(items));
}
Instr::Range => {
let end = stack.pop().unwrap_or(Value::Null);
let start = stack.pop().unwrap_or(Value::Null);
let s = self.value_as_int(&start)?;
let e = self.value_as_int(&end)?;
stack.push(Value::Array((s..e).map(Value::Int).collect()));
}
Instr::Call { name, argc } => {
let mut args = Vec::with_capacity(*argc);
for _ in 0..*argc {
args.push(stack.pop().unwrap_or(Value::Null));
}
args.reverse();
let val = self.call_vm_function(name, args)?;
stack.push(val);
}
Instr::Import(path) => {
self.load_module(path)?;
stack.push(Value::Null);
}
Instr::If {
cond,
then_block,
else_block,
} => {
let cond_val = match self.run_vm_block_outcome(cond)? {
VmOutcome::Value(v) | VmOutcome::Return(v) => v,
VmOutcome::Break => Value::Null,
};
let result = if self.is_truthy(&cond_val) {
self.run_vm_scoped_outcome(then_block)?
} else if !else_block.is_empty() {
self.run_vm_scoped_outcome(else_block)?
} else {
VmOutcome::Value(Value::Null)
};
match result {
VmOutcome::Value(v) => stack.push(v),
VmOutcome::Return(v) => return Ok(VmOutcome::Return(v)),
VmOutcome::Break => return Ok(VmOutcome::Break),
}
}
Instr::Loop { body } => loop {
match self.run_vm_scoped_outcome(body)? {
VmOutcome::Return(v) => return Ok(VmOutcome::Return(v)),
VmOutcome::Break => break,
VmOutcome::Value(_) => {}
}
},
Instr::While { cond, body } => {
loop {
let cond_val = match self.run_vm_block_outcome(cond)? {
VmOutcome::Value(v) | VmOutcome::Return(v) => v,
VmOutcome::Break => Value::Null,
};
if !self.is_truthy(&cond_val) {
break;
}
match self.run_vm_scoped_outcome(body)? {
VmOutcome::Return(v) => return Ok(VmOutcome::Return(v)),
VmOutcome::Break => break,
VmOutcome::Value(_) => {}
}
}
stack.push(Value::Null);
}
Instr::For { var, iter, body } => {
let iter_val = match self.run_vm_block_outcome(iter)? {
VmOutcome::Value(v) | VmOutcome::Return(v) => v,
VmOutcome::Break => Value::Null,
};
let items = match iter_val {
Value::Array(arr) => arr,
other => {
let e = LangError::new(
ErrorKind::TypeError,
format!("'{}' is not iterable", other),
)
.with_hint("for..in requires an array or range");
return Err((self.error_handler)(&e));
}
};
for item in items {
self.push_scope();
self.define_var(var, item, true);
match self.run_vm_block_outcome(body)? {
VmOutcome::Return(v) => {
self.pop_scope();
return Ok(VmOutcome::Return(v));
}
VmOutcome::Break => {
self.pop_scope();
break;
}
VmOutcome::Value(_) => {}
}
self.pop_scope();
}
stack.push(Value::Null);
}
Instr::FnDef { name, params, body } => {
self.bc_functions
.insert(name.clone(), (params.clone(), body.clone()));
stack.push(Value::Null);
}
Instr::Return => {
let val = stack.pop().unwrap_or(Value::Null);
return Ok(VmOutcome::Return(val));
}
Instr::Break => return Ok(VmOutcome::Break),
Instr::Switch {
value,
cases,
default,
} => {
let v = match self.run_vm_block_outcome(value)? {
VmOutcome::Value(v) | VmOutcome::Return(v) => v,
VmOutcome::Break => Value::Null,
};
let mut matched = false;
for (case_val, body) in cases {
let cv = match self.run_vm_block_outcome(case_val)? {
VmOutcome::Value(v) | VmOutcome::Return(v) => v,
VmOutcome::Break => Value::Null,
};
if self.values_equal(&v, &cv) {
let res = self.run_vm_scoped_outcome(body)?;
matched = true;
match res {
VmOutcome::Value(val) => stack.push(val),
VmOutcome::Return(val) => return Ok(VmOutcome::Return(val)),
VmOutcome::Break => return Ok(VmOutcome::Break),
}
break;
}
}
if !matched {
if let Some(body) = default {
match self.run_vm_scoped_outcome(body)? {
VmOutcome::Value(val) => stack.push(val),
VmOutcome::Return(val) => return Ok(VmOutcome::Return(val)),
VmOutcome::Break => return Ok(VmOutcome::Break),
}
} else {
stack.push(Value::Null);
}
}
}
Instr::Try { body, catch_body } => match self.run_vm_scoped_outcome(body) {
Ok(VmOutcome::Value(v)) => stack.push(v),
Ok(VmOutcome::Return(v)) => return Ok(VmOutcome::Return(v)),
Ok(VmOutcome::Break) => return Ok(VmOutcome::Break),
Err(e) => {
self.push_scope();
self.define_var("error", Value::Str(e), false);
let res = self.run_vm_block_outcome(catch_body);
self.pop_scope();
match res? {
VmOutcome::Value(v) => stack.push(v),
VmOutcome::Return(v) => return Ok(VmOutcome::Return(v)),
VmOutcome::Break => return Ok(VmOutcome::Break),
}
}
},
Instr::Interpolated(parts) => {
let mut s = String::new();
for part in parts {
match part {
InstrPart::Text(t) => s.push_str(t),
InstrPart::Expr(code) => {
let v = match self.run_vm_block_outcome(code)? {
VmOutcome::Value(v) | VmOutcome::Return(v) => v,
VmOutcome::Break => Value::Null,
};
s.push_str(&v.to_string());
}
}
}
stack.push(Value::Str(s));
}
}
}
let last = stack.pop().unwrap_or(Value::Null);
Ok(VmOutcome::Value(last))
}
pub fn run_vm_block(&mut self, instrs: &[Instr]) -> Result<Signal, String> {
match self.run_vm_block_outcome(instrs)? {
VmOutcome::Value(v) | VmOutcome::Return(v) => Ok(Signal::Return(v)),
VmOutcome::Break => Ok(Signal::Break),
}
}
fn run_vm_scoped_outcome(&mut self, instrs: &[Instr]) -> Result<VmOutcome, String> {
self.push_scope();
let result = self.run_vm_block_outcome(instrs);
self.pop_scope();
result
}
pub fn run_vm_scoped(&mut self, instrs: &[Instr]) -> Result<Signal, String> {
match self.run_vm_scoped_outcome(instrs)? {
VmOutcome::Value(v) | VmOutcome::Return(v) => Ok(Signal::Return(v)),
VmOutcome::Break => Ok(Signal::Break),
}
}
fn call_vm_function(&mut self, name: &str, args: Vec<Value>) -> Result<Value, String> {
if self.actions.contains_key(name) {
return Ok((self.actions[name])(args));
}
if let Some((params, body)) = self.bc_functions.get(name).cloned() {
self.push_scope();
for (i, param) in params.iter().enumerate() {
let val = args.get(i).cloned().unwrap_or(Value::Null);
self.define_var(param, val, true);
}
let result = self.run_vm_block(&body)?;
self.pop_scope();
return match result {
Signal::Return(v) => Ok(v),
Signal::Break => Ok(Value::Null),
};
}
if let Some((params, body)) = self.functions.get(name).cloned() {
self.push_scope();
for (i, param) in params.iter().enumerate() {
let val = args.get(i).cloned().unwrap_or(Value::Null);
self.define_var(param, val, true);
}
let result = self.eval_block(&body)?;
self.pop_scope();
return match result {
Signal::Return(v) => Ok(v),
Signal::Break => Ok(Value::Null),
};
}
let e = LangError::new(
ErrorKind::UndefinedFunction,
format!("'{}' is not defined", name),
)
.with_hint("Check for typos or declare the function before use");
Err((self.error_handler)(&e))
}
fn value_as_int(&self, value: &Value) -> Result<i64, String> {
match value {
Value::Int(n) => Ok(*n),
Value::Float(n) => Ok(*n as i64),
v => Err((self.error_handler)(&LangError::new(
ErrorKind::TypeError,
format!("Expected number, got {}", value_type_name(v)),
))),
}
}
fn load_module(&mut self, name: &str) -> Result<(), String> {
if self.loaded_modules.contains(name) {
return Ok(());
}
if let Some(module) = self.modules.remove(name) {
self.install_module(module);
self.loaded_modules.insert(name.to_string());
return Ok(());
}
if let Some(path) = self.ffi_libs.get(name).cloned() {
self.load_ffi_module(name, &path)?;
self.loaded_modules.insert(name.to_string());
return Ok(());
}
Err(format!("Unknown module '{}'", name))
}
fn install_module(&mut self, module: Module) {
for (name, action) in module.actions {
let full = format!("{}.{}", module.name, name);
if !self.actions.contains_key(&full) {
self.actions.insert(full, action);
}
}
}
fn load_ffi_module(&mut self, name: &str, path: &str) -> Result<(), String> {
let lib = unsafe { libloading::Library::new(path) }
.map_err(|e| format!("FFI load failed for '{}': {}", path, e))?;
let register: libloading::Symbol<FfiRegister> = unsafe { lib.get(b"langkit_register") }
.map_err(|e| format!("FFI missing langkit_register in '{}': {}", path, e))?;
let mut ctx = FfiRegistryCtx { functions: vec![] };
let mut registry = FfiRegistry {
register: ffi_register,
ctx: &mut ctx as *mut FfiRegistryCtx,
};
unsafe { register(&mut registry) };
let mut module = Module::new(name);
for (fname, fptr) in ctx.functions {
let action = Box::new(move |args: Vec<Value>| -> Value {
let mut temp = Vec::new();
let ffi_args: Vec<FfiValue> =
args.iter().map(|v| to_ffi_value(v, &mut temp)).collect();
let ret = unsafe { fptr(ffi_args.len(), ffi_args.as_ptr()) };
from_ffi_value(ret)
});
module.action(&fname, action);
}
self.ffi_handles.push(lib);
self.install_module(module);
Ok(())
}
fn ensure_type(&self, name: &str, expected: &TypeTag, value: &Value) -> Result<(), String> {
let actual = value_type_tag(value);
if &actual == expected {
return Ok(());
}
let e = LangError::new(
ErrorKind::TypeError,
format!(
"Type mismatch for '{}': expected {}, got {}",
name,
type_tag_name(expected),
value_type_name(value)
),
)
.with_hint("Convert the value or update the type hint");
Err((self.error_handler)(&e))
}
}
fn value_type_name(v: &Value) -> &'static str {
match v {
Value::Int(_) => "int",
Value::Float(_) => "float",
Value::Str(_) => "string",
Value::Bool(_) => "bool",
Value::Array(_) => "array",
Value::Null => "null",
}
}
type FfiFn = unsafe extern "C" fn(argc: usize, argv: *const FfiValue) -> FfiValue;
type FfiRegister = unsafe extern "C" fn(registry: *mut FfiRegistry);
#[repr(C)]
struct FfiRegistry {
register: extern "C" fn(*mut FfiRegistry, *const std::os::raw::c_char, FfiFn),
ctx: *mut FfiRegistryCtx,
}
struct FfiRegistryCtx {
functions: Vec<(String, FfiFn)>,
}
extern "C" fn ffi_register(
registry: *mut FfiRegistry,
name: *const std::os::raw::c_char,
func: FfiFn,
) {
if registry.is_null() || name.is_null() {
return;
}
let reg = unsafe { &mut *registry };
let ctx = unsafe { &mut *reg.ctx };
let cstr = unsafe { CStr::from_ptr(name) };
if let Ok(name) = cstr.to_str() {
ctx.functions.push((name.to_string(), func));
}
}
#[repr(C)]
struct FfiValue {
tag: u8,
int_value: i64,
float_value: f64,
bool_value: u8,
str_ptr: *const std::os::raw::c_char,
}
fn to_ffi_value(value: &Value, temp: &mut Vec<CString>) -> FfiValue {
match value {
Value::Null => FfiValue {
tag: 0,
int_value: 0,
float_value: 0.0,
bool_value: 0,
str_ptr: std::ptr::null(),
},
Value::Int(i) => FfiValue {
tag: 1,
int_value: *i,
float_value: 0.0,
bool_value: 0,
str_ptr: std::ptr::null(),
},
Value::Float(f) => FfiValue {
tag: 2,
int_value: 0,
float_value: *f,
bool_value: 0,
str_ptr: std::ptr::null(),
},
Value::Bool(b) => FfiValue {
tag: 3,
int_value: 0,
float_value: 0.0,
bool_value: if *b { 1 } else { 0 },
str_ptr: std::ptr::null(),
},
Value::Str(s) => {
let c = CString::new(s.as_str()).unwrap_or_else(|_| CString::new("").unwrap());
let ptr = c.as_ptr();
temp.push(c);
FfiValue {
tag: 4,
int_value: 0,
float_value: 0.0,
bool_value: 0,
str_ptr: ptr,
}
}
Value::Array(_) => FfiValue {
tag: 0,
int_value: 0,
float_value: 0.0,
bool_value: 0,
str_ptr: std::ptr::null(),
},
}
}
fn from_ffi_value(value: FfiValue) -> Value {
match value.tag {
1 => Value::Int(value.int_value),
2 => Value::Float(value.float_value),
3 => Value::Bool(value.bool_value != 0),
4 => {
if value.str_ptr.is_null() {
Value::Str(String::new())
} else {
let s = unsafe { CStr::from_ptr(value.str_ptr) };
Value::Str(s.to_string_lossy().to_string())
}
}
_ => Value::Null,
}
}
#[derive(Debug, Clone)]
pub enum Instr {
Push(Value),
Load(String),
Store(String),
Pop,
Declare {
name: String,
is_mut: bool,
type_hint: Option<String>,
},
BinOp(String),
MakeArray(usize),
Range,
Call {
name: String,
argc: usize,
},
Import(String),
If {
cond: Vec<Instr>,
then_block: Vec<Instr>,
else_block: Vec<Instr>,
},
Loop {
body: Vec<Instr>,
},
While {
cond: Vec<Instr>,
body: Vec<Instr>,
},
For {
var: String,
iter: Vec<Instr>,
body: Vec<Instr>,
},
FnDef {
name: String,
params: Vec<String>,
body: Vec<Instr>,
},
Return,
Break,
Switch {
value: Vec<Instr>,
cases: Vec<(Vec<Instr>, Vec<Instr>)>,
default: Option<Vec<Instr>>,
},
Try {
body: Vec<Instr>,
catch_body: Vec<Instr>,
},
Interpolated(Vec<InstrPart>),
}
#[derive(Debug, Clone)]
pub enum InstrPart {
Text(String),
Expr(Vec<Instr>),
}
#[derive(Debug)]
enum VmOutcome {
Value(Value),
Return(Value),
Break,
}
pub fn compile_block(stmts: &[Expr]) -> Result<Vec<Instr>, String> {
let mut out = Vec::new();
for (i, stmt) in stmts.iter().enumerate() {
match stmt {
Expr::Return(expr) => {
out.extend(compile_expr(expr)?);
out.push(Instr::Return);
break;
}
Expr::Break => {
out.push(Instr::Break);
break;
}
_ => {
out.extend(compile_expr(stmt)?);
if i + 1 < stmts.len() {
out.push(Instr::Pop);
}
}
}
}
Ok(out)
}
fn compile_expr(expr: &Expr) -> Result<Vec<Instr>, String> {
match expr {
Expr::Literal(v) => Ok(vec![Instr::Push(v.clone())]),
Expr::Ident(name) => Ok(vec![Instr::Load(name.clone())]),
Expr::Array(items) => {
let mut out = Vec::new();
for item in items {
out.extend(compile_expr(item)?);
}
out.push(Instr::MakeArray(items.len()));
Ok(out)
}
Expr::Range(start, end) => {
let mut out = Vec::new();
out.extend(compile_expr(start)?);
out.extend(compile_expr(end)?);
out.push(Instr::Range);
Ok(out)
}
Expr::BinOp { left, op, right } => {
let mut out = Vec::new();
out.extend(compile_expr(left)?);
out.extend(compile_expr(right)?);
out.push(Instr::BinOp(op.clone()));
Ok(out)
}
Expr::Assign { name, value } => {
let mut out = Vec::new();
out.extend(compile_expr(value)?);
out.push(Instr::Store(name.clone()));
Ok(out)
}
Expr::VarDecl {
name,
type_hint,
value,
is_mut,
} => {
let mut out = Vec::new();
out.extend(compile_expr(value)?);
out.push(Instr::Declare {
name: name.clone(),
is_mut: *is_mut,
type_hint: type_hint.clone(),
});
Ok(out)
}
Expr::Call { keyword, args } => {
let mut out = Vec::new();
for arg in args {
out.extend(compile_expr(arg)?);
}
out.push(Instr::Call {
name: keyword.clone(),
argc: args.len(),
});
Ok(out)
}
Expr::If {
cond,
then_block,
else_block,
} => Ok(vec![Instr::If {
cond: compile_expr(cond)?,
then_block: compile_block(then_block)?,
else_block: else_block
.as_ref()
.map(|b| compile_block(b))
.unwrap_or(Ok(Vec::new()))?,
}]),
Expr::Loop { body } => Ok(vec![Instr::Loop {
body: compile_block(body)?,
}]),
Expr::While { cond, body } => Ok(vec![Instr::While {
cond: compile_expr(cond)?,
body: compile_block(body)?,
}]),
Expr::For { var, iter, body } => Ok(vec![Instr::For {
var: var.clone(),
iter: compile_expr(iter)?,
body: compile_block(body)?,
}]),
Expr::FnDef { name, params, body } => Ok(vec![Instr::FnDef {
name: name.clone(),
params: params.clone(),
body: compile_block(body)?,
}]),
Expr::Return(expr) => {
let mut out = compile_expr(expr)?;
out.push(Instr::Return);
Ok(out)
}
Expr::Break => Ok(vec![Instr::Break]),
Expr::Switch {
value,
cases,
default,
} => {
let mut compiled_cases = Vec::new();
for (case_val, body) in cases {
compiled_cases.push((compile_expr(case_val)?, compile_block(body)?));
}
Ok(vec![Instr::Switch {
value: compile_expr(value)?,
cases: compiled_cases,
default: match default {
Some(body) => Some(compile_block(body)?),
None => None,
},
}])
}
Expr::Try { body, catch_body } => Ok(vec![Instr::Try {
body: compile_block(body)?,
catch_body: compile_block(catch_body)?,
}]),
Expr::Interpolated(parts) => {
let mut compiled_parts = Vec::new();
for part in parts {
match part {
InterpolPart::Text(t) => compiled_parts.push(InstrPart::Text(t.clone())),
InterpolPart::Expr(e) => compiled_parts.push(InstrPart::Expr(compile_expr(e)?)),
}
}
Ok(vec![Instr::Interpolated(compiled_parts)])
}
Expr::Import(path) => Ok(vec![Instr::Import(path.clone())]),
Expr::Lambda { .. } => Err("Lambdas are not supported in VM yet".to_string()),
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeTag {
Int,
Float,
Str,
Bool,
Array,
Null,
}
fn type_tag_name(tag: &TypeTag) -> &'static str {
match tag {
TypeTag::Int => "int",
TypeTag::Float => "float",
TypeTag::Str => "string",
TypeTag::Bool => "bool",
TypeTag::Array => "array",
TypeTag::Null => "null",
}
}
fn parse_type_hint(hint: &str) -> Option<TypeTag> {
match hint.to_lowercase().as_str() {
"int" | "i64" => Some(TypeTag::Int),
"float" | "f64" => Some(TypeTag::Float),
"string" | "str" => Some(TypeTag::Str),
"bool" | "boolean" => Some(TypeTag::Bool),
"array" | "list" => Some(TypeTag::Array),
"null" => Some(TypeTag::Null),
_ => None,
}
}
fn value_type_tag(v: &Value) -> TypeTag {
match v {
Value::Int(_) => TypeTag::Int,
Value::Float(_) => TypeTag::Float,
Value::Str(_) => TypeTag::Str,
Value::Bool(_) => TypeTag::Bool,
Value::Array(_) => TypeTag::Array,
Value::Null => TypeTag::Null,
}
}