use super::SExprError;
use crate::ir::*;
use serde_json::Value;
pub fn from_sexpr(sexpr: &Value) -> Result<Program, SExprError> {
let stmt = value_to_stmt(sexpr)?;
Ok(Program::new(vec![stmt]))
}
fn value_to_stmt(value: &Value) -> Result<Stmt, SExprError> {
match value {
Value::Null => Ok(Stmt::expr(Expr::null())),
Value::Bool(b) => Ok(Stmt::expr(Expr::bool(*b))),
Value::Number(n) => Ok(Stmt::expr(Expr::number(n.as_f64().unwrap_or(0.0)))),
Value::String(s) => Ok(Stmt::expr(Expr::string(s.clone()))),
Value::Array(arr) => {
if arr.is_empty() {
return Ok(Stmt::expr(Expr::array(vec![])));
}
let opcode = arr[0]
.as_str()
.ok_or_else(|| SExprError::ExpectedOpcode(format!("{:?}", arr[0])))?;
let args = &arr[1..];
parse_opcode(opcode, args)
}
Value::Object(_) => Err(SExprError::InvalidArgument(
"unexpected object in S-expression".into(),
)),
}
}
fn value_to_expr(value: &Value) -> Result<Expr, SExprError> {
match value {
Value::Null => Ok(Expr::null()),
Value::Bool(b) => Ok(Expr::bool(*b)),
Value::Number(n) => Ok(Expr::number(n.as_f64().unwrap_or(0.0))),
Value::String(s) => Ok(Expr::string(s.clone())),
Value::Array(arr) => {
if arr.is_empty() {
return Ok(Expr::array(vec![]));
}
let opcode = arr[0]
.as_str()
.ok_or_else(|| SExprError::ExpectedOpcode(format!("{:?}", arr[0])))?;
let args = &arr[1..];
parse_opcode_expr(opcode, args)
}
Value::Object(_) => Err(SExprError::InvalidArgument(
"unexpected object in S-expression".into(),
)),
}
}
fn parse_opcode(opcode: &str, args: &[Value]) -> Result<Stmt, SExprError> {
match opcode {
"std.let" => {
ensure_arity(opcode, args, 2)?;
let name = args[0]
.as_str()
.ok_or_else(|| SExprError::InvalidArgument("std.let name must be string".into()))?;
let init = value_to_expr(&args[1])?;
Ok(Stmt::let_decl(name, Some(init)))
}
"std.seq" => {
let stmts: Result<Vec<Stmt>, _> = args.iter().map(value_to_stmt).collect();
Ok(Stmt::block(stmts?))
}
"std.if" => {
if args.len() < 2 {
return Err(SExprError::WrongArity {
opcode: opcode.into(),
expected: 2,
got: args.len(),
});
}
let test = value_to_expr(&args[0])?;
let consequent = value_to_stmt(&args[1])?;
let alternate = if args.len() > 2 {
Some(value_to_stmt(&args[2])?)
} else {
None
};
Ok(Stmt::if_stmt(test, consequent, alternate))
}
"std.while" => {
ensure_arity(opcode, args, 2)?;
let test = value_to_expr(&args[0])?;
let body = value_to_stmt(&args[1])?;
Ok(Stmt::while_loop(test, body))
}
"std.for" => {
ensure_arity(opcode, args, 3)?;
let var = args[0]
.as_str()
.ok_or_else(|| SExprError::InvalidArgument("std.for var must be string".into()))?;
let iterable = value_to_expr(&args[1])?;
let body = value_to_stmt(&args[2])?;
Ok(Stmt::for_in(var, iterable, body))
}
"std.return" => {
let expr = if args.is_empty() {
None
} else {
Some(value_to_expr(&args[0])?)
};
Ok(Stmt::return_stmt(expr))
}
"std.break" => Ok(Stmt::break_stmt()),
"std.continue" => Ok(Stmt::continue_stmt()),
"std.try" => {
if args.is_empty() {
return Err(SExprError::WrongArity {
opcode: opcode.into(),
expected: 1,
got: 0,
});
}
let body = value_to_stmt(&args[0])?;
let mut catch_param = None;
let mut catch_body = None;
let mut finally_body = None;
for arg in &args[1..] {
if let Value::Array(arr) = arg
&& let Some(tag) = arr.first().and_then(|v| v.as_str())
{
match tag {
"catch" if arr.len() >= 3 => {
catch_param = arr[1].as_str().map(String::from);
catch_body = Some(value_to_stmt(&arr[2])?);
}
"finally" if arr.len() >= 2 => {
finally_body = Some(value_to_stmt(&arr[1])?);
}
_ => {}
}
}
}
Ok(Stmt::try_catch(body, catch_param, catch_body, finally_body))
}
"std.fn" => {
ensure_arity(opcode, args, 3)?;
let name = args[0]
.as_str()
.ok_or_else(|| SExprError::InvalidArgument("std.fn name must be string".into()))?;
let params = parse_params(&args[1])?;
let body = value_to_stmt(&args[2])?;
Ok(Stmt::function(Function::new(name, params, vec![body])))
}
"std.import" => {
if args.len() < 2 {
return Err(SExprError::WrongArity {
opcode: opcode.into(),
expected: 2,
got: args.len(),
});
}
let source = args[0]
.as_str()
.ok_or_else(|| {
SExprError::InvalidArgument("std.import source must be string".into())
})?
.to_string();
let mut names = Vec::new();
if let Value::Array(name_arr) = &args[1] {
for item in name_arr {
match item {
Value::String(s) => {
names.push(ImportName::named(s.clone()));
}
Value::Array(parts) if parts.len() == 2 => {
let first = parts[0].as_str().unwrap_or("").to_string();
let second = parts[1].as_str().unwrap_or("").to_string();
if first == "ns" {
names.push(ImportName::namespace(second));
} else {
names.push(ImportName::aliased(first, second));
}
}
_ => {}
}
}
}
Ok(Stmt::import(source, names))
}
"std.export" => {
if args.is_empty() {
return Err(SExprError::WrongArity {
opcode: opcode.into(),
expected: 1,
got: 0,
});
}
let mut names = Vec::new();
if let Value::Array(name_arr) = &args[0] {
for item in name_arr {
match item {
Value::String(s) => {
names.push(ExportName::named(s.clone()));
}
Value::Array(parts) if parts.len() == 2 => {
let first = parts[0].as_str().unwrap_or("").to_string();
let second = parts[1].as_str().unwrap_or("").to_string();
names.push(ExportName::aliased(first, second));
}
_ => {}
}
}
}
let source = args.get(1).and_then(|v| v.as_str()).map(String::from);
Ok(Stmt::export(names, source))
}
"std.class" => {
if args.len() < 3 {
return Err(SExprError::WrongArity {
opcode: opcode.into(),
expected: 3,
got: args.len(),
});
}
let name = args[0]
.as_str()
.ok_or_else(|| SExprError::InvalidArgument("std.class name must be string".into()))?
.to_string();
let extends = args[1].as_str().map(String::from);
let mut methods = Vec::new();
if let Value::Array(method_arr) = &args[2] {
for item in method_arr {
if let Value::Array(parts) = item
&& parts.len() >= 3
{
let method_name = parts[0].as_str().unwrap_or("").to_string();
let params = parse_params(&parts[1])?;
let body_stmts = if let Value::Array(body_arr) = &parts[2] {
body_arr
.iter()
.map(value_to_stmt)
.collect::<Result<Vec<_>, _>>()?
} else {
vec![]
};
methods.push(Method::new(method_name, params, body_stmts));
}
}
}
Ok(Stmt::class(name, extends, methods))
}
_ => {
let expr = parse_opcode_expr(opcode, args)?;
Ok(Stmt::expr(expr))
}
}
}
fn parse_opcode_expr(opcode: &str, args: &[Value]) -> Result<Expr, SExprError> {
match opcode {
"std.var" => {
ensure_arity(opcode, args, 1)?;
let name = args[0]
.as_str()
.ok_or_else(|| SExprError::InvalidArgument("std.var name must be string".into()))?;
Ok(Expr::ident(name))
}
"std.set" => {
ensure_arity(opcode, args, 2)?;
let name = args[0]
.as_str()
.ok_or_else(|| SExprError::InvalidArgument("std.set name must be string".into()))?;
let value = value_to_expr(&args[1])?;
Ok(Expr::assign(Expr::ident(name), value))
}
"math.add" => binary_op(args, BinaryOp::Add),
"math.sub" => binary_op(args, BinaryOp::Sub),
"math.mul" => binary_op(args, BinaryOp::Mul),
"math.div" => binary_op(args, BinaryOp::Div),
"math.mod" => binary_op(args, BinaryOp::Mod),
"math.neg" => unary_op(args, UnaryOp::Neg),
"bool.eq" => binary_op(args, BinaryOp::Eq),
"bool.neq" => binary_op(args, BinaryOp::Ne),
"bool.lt" => binary_op(args, BinaryOp::Lt),
"bool.lte" => binary_op(args, BinaryOp::Le),
"bool.gt" => binary_op(args, BinaryOp::Gt),
"bool.gte" => binary_op(args, BinaryOp::Ge),
"bool.and" => binary_op(args, BinaryOp::And),
"bool.or" => binary_op(args, BinaryOp::Or),
"bool.not" => unary_op(args, UnaryOp::Not),
"str.concat" => binary_op(args, BinaryOp::Concat),
"obj.get" => {
ensure_arity(opcode, args, 2)?;
let obj = value_to_expr(&args[0])?;
let prop = value_to_expr(&args[1])?;
Ok(Expr::Member {
object: Box::new(obj),
property: Box::new(prop),
computed: true,
span: None,
})
}
"obj.set" => {
ensure_arity(opcode, args, 3)?;
let obj = value_to_expr(&args[0])?;
let prop = value_to_expr(&args[1])?;
let value = value_to_expr(&args[2])?;
let target = Expr::Member {
object: Box::new(obj),
property: Box::new(prop),
computed: true,
span: None,
};
Ok(Expr::assign(target, value))
}
"obj.new" => {
let pairs: Result<Vec<(String, Expr)>, _> = args
.iter()
.map(|pair| {
let arr = pair.as_array().ok_or_else(|| {
SExprError::InvalidArgument("obj.new pair must be array".into())
})?;
if arr.len() != 2 {
return Err(SExprError::InvalidArgument(
"obj.new pair must have 2 elements".into(),
));
}
let key = arr[0].as_str().ok_or_else(|| {
SExprError::InvalidArgument("obj.new key must be string".into())
})?;
let val = value_to_expr(&arr[1])?;
Ok((key.to_string(), val))
})
.collect();
Ok(Expr::object(pairs?))
}
"list.new" => {
let items: Result<Vec<Expr>, _> = args.iter().map(value_to_expr).collect();
Ok(Expr::array(items?))
}
"list.get" => {
ensure_arity(opcode, args, 2)?;
let arr = value_to_expr(&args[0])?;
let idx = value_to_expr(&args[1])?;
Ok(Expr::index(arr, idx))
}
"std.lambda" => {
ensure_arity(opcode, args, 2)?;
let params = parse_params(&args[0])?;
let body = value_to_stmt(&args[1])?;
Ok(Expr::Function(Box::new(Function::anonymous(
params,
vec![body],
))))
}
"std.this" => Ok(Expr::ident("this")),
"std.apply" => {
if args.is_empty() {
return Err(SExprError::WrongArity {
opcode: opcode.into(),
expected: 1,
got: 0,
});
}
let callee = value_to_expr(&args[0])?;
let call_args: Result<Vec<Expr>, _> = args[1..].iter().map(value_to_expr).collect();
Ok(Expr::call(callee, call_args?))
}
_ => {
let parts: Vec<&str> = opcode.split('.').collect();
let callee = if parts.len() == 1 {
Expr::ident(parts[0])
} else {
let mut expr = Expr::ident(parts[0]);
for part in &parts[1..] {
expr = Expr::member(expr, *part);
}
expr
};
let call_args: Result<Vec<Expr>, _> = args.iter().map(value_to_expr).collect();
Ok(Expr::call(callee, call_args?))
}
}
}
fn binary_op(args: &[Value], op: BinaryOp) -> Result<Expr, SExprError> {
if args.len() != 2 {
return Err(SExprError::WrongArity {
opcode: format!("{:?}", op),
expected: 2,
got: args.len(),
});
}
let left = value_to_expr(&args[0])?;
let right = value_to_expr(&args[1])?;
Ok(Expr::binary(left, op, right))
}
fn unary_op(args: &[Value], op: UnaryOp) -> Result<Expr, SExprError> {
if args.len() != 1 {
return Err(SExprError::WrongArity {
opcode: format!("{:?}", op),
expected: 1,
got: args.len(),
});
}
Ok(Expr::unary(op, value_to_expr(&args[0])?))
}
fn parse_params(value: &Value) -> Result<Vec<Param>, SExprError> {
let arr = value
.as_array()
.ok_or_else(|| SExprError::InvalidArgument("params must be array".into()))?;
arr.iter()
.map(|v| {
v.as_str()
.map(Param::new)
.ok_or_else(|| SExprError::InvalidArgument("param must be string".into()))
})
.collect()
}
fn ensure_arity(opcode: &str, args: &[Value], expected: usize) -> Result<(), SExprError> {
if args.len() != expected {
Err(SExprError::WrongArity {
opcode: opcode.into(),
expected,
got: args.len(),
})
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_simple_let() -> Result<(), SExprError> {
let sexpr = json!(["std.let", "x", 42]);
let program = from_sexpr(&sexpr)?;
assert_eq!(program.body.len(), 1);
match &program.body[0] {
Stmt::Let { name, init, .. } => {
assert_eq!(name, "x");
assert!(init.is_some());
}
_ => panic!("expected Let"),
}
Ok(())
}
#[test]
fn test_binary_expr() -> Result<(), SExprError> {
let sexpr = json!(["math.add", 1, 2]);
let program = from_sexpr(&sexpr)?;
match &program.body[0] {
Stmt::Expr(Expr::Binary { op, .. }) => {
assert_eq!(*op, BinaryOp::Add);
}
_ => panic!("expected Binary"),
}
Ok(())
}
#[test]
fn test_function_call() -> Result<(), SExprError> {
let sexpr = json!(["console.log", "hello"]);
let program = from_sexpr(&sexpr)?;
match &program.body[0] {
Stmt::Expr(Expr::Call { callee, args, .. }) => {
assert_eq!(args.len(), 1);
match callee.as_ref() {
Expr::Member { .. } => {}
_ => panic!("expected Member expression"),
}
}
_ => panic!("expected Call"),
}
Ok(())
}
}