use parallax_core::{ErrorCode, ParallaxError};
use parallax_ir::{PirDocument, PirValue};
pub fn emit_javascript(doc: &PirDocument) -> Result<String, ParallaxError> {
let mut out = String::from("// Generated by Parallax migrate - preview of restored bindings\n");
for (name, value) in &doc.bindings {
out.push_str(&format!(
"const {} = {};\n",
ident(name)?,
js_expr(value, 0)?
));
}
if let Some(state) = doc.binding("state") {
out.push_str("\nconsole.log(JSON.stringify(state, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2));\n");
let _ = state;
}
Ok(out)
}
pub fn emit_python(doc: &PirDocument) -> Result<String, ParallaxError> {
let mut out = String::from("# Generated by Parallax migrate - preview of restored bindings\n");
for (name, value) in &doc.bindings {
out.push_str(&format!("{} = {}\n", ident(name)?, py_expr(value, 0)?));
}
if doc.binding("state").is_some() {
out.push_str("\nprint(state)\n");
}
Ok(out)
}
fn ident(name: &str) -> Result<&str, ParallaxError> {
if name
.chars()
.next()
.map(|c| c.is_ascii_alphabetic() || c == '_')
.unwrap_or(false)
&& name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
{
Ok(name)
} else {
Err(ParallaxError::new(
ErrorCode::UnsupportedValue,
format!("cannot emit binding name as identifier: {name}"),
))
}
}
fn js_float_literal(v: f64) -> String {
if v.is_nan() {
"NaN".into()
} else if v == f64::INFINITY {
"Infinity".into()
} else if v == f64::NEG_INFINITY {
"-Infinity".into()
} else {
format!("{v}")
}
}
fn py_float_literal(v: f64) -> String {
if v.is_nan() {
"float('nan')".into()
} else if v == f64::INFINITY {
"float('inf')".into()
} else if v == f64::NEG_INFINITY {
"float('-inf')".into()
} else {
format!("{v}")
}
}
fn js_expr(v: &PirValue, depth: usize) -> Result<String, ParallaxError> {
if depth > 64 {
return Err(ParallaxError::new(
ErrorCode::UnsupportedValue,
"value nesting too deep",
));
}
Ok(match v {
PirValue::Null => "null".into(),
PirValue::Bool { v } => if *v { "true" } else { "false" }.into(),
PirValue::Int { v } => v.decimal.clone(),
PirValue::Float { v } => js_float_literal(*v),
PirValue::String { v } => serde_json::to_string(v).unwrap_or_else(|_| "\"\"".into()),
PirValue::Bytes { v } => format!(
"new Uint8Array({})",
serde_json::to_string(v).unwrap_or_else(|_| "[]".into())
),
PirValue::List { v } | PirValue::Tuple { v } | PirValue::Set { v } => {
let parts: Result<Vec<_>, _> = v.iter().map(|x| js_expr(x, depth + 1)).collect();
format!("[{}]", parts?.join(", "))
}
PirValue::Map { entries } => {
let mut parts = Vec::new();
for e in entries {
match &e.key {
PirValue::String { v: k } => {
parts.push(format!(
"{}: {}",
serde_json::to_string(k).unwrap_or_else(|_| "\"\"".into()),
js_expr(&e.value, depth + 1)?
));
}
_ => {
return Err(ParallaxError::new(
ErrorCode::UnsupportedValue,
"JS object emit requires string keys",
));
}
}
}
format!("{{ {} }}", parts.join(", "))
}
PirValue::BigInt { v } => format!("{v}n"),
PirValue::Unsupported { reason, .. } => {
return Err(ParallaxError::new(
ErrorCode::UnsupportedValue,
format!("cannot emit unsupported value: {reason}"),
));
}
PirValue::Function { name, .. } => {
return Err(ParallaxError::new(
ErrorCode::UnsupportedValue,
format!("cannot emit function {:?}", name),
));
}
PirValue::Ref { id } => {
return Err(ParallaxError::new(
ErrorCode::UnsupportedValue,
format!("cannot emit ref {id}"),
));
}
})
}
fn py_expr(v: &PirValue, depth: usize) -> Result<String, ParallaxError> {
if depth > 64 {
return Err(ParallaxError::new(
ErrorCode::UnsupportedValue,
"value nesting too deep",
));
}
Ok(match v {
PirValue::Null => "None".into(),
PirValue::Bool { v } => if *v { "True" } else { "False" }.into(),
PirValue::Int { v } => v.decimal.clone(),
PirValue::BigInt { v } => v.clone(),
PirValue::Float { v } => py_float_literal(*v),
PirValue::String { v } => serde_json::to_string(v).unwrap_or_else(|_| "\"\"".into()),
PirValue::Bytes { v } => format!("bytes({v:?})"),
PirValue::List { v } => {
let parts: Result<Vec<_>, _> = v.iter().map(|x| py_expr(x, depth + 1)).collect();
format!("[{}]", parts?.join(", "))
}
PirValue::Tuple { v } => {
let parts: Result<Vec<_>, _> = v.iter().map(|x| py_expr(x, depth + 1)).collect();
let p = parts?;
if p.len() == 1 {
format!("({},)", p[0])
} else {
format!("({})", p.join(", "))
}
}
PirValue::Set { v } => {
let parts: Result<Vec<_>, _> = v.iter().map(|x| py_expr(x, depth + 1)).collect();
format!("{{{}}}", parts?.join(", "))
}
PirValue::Map { entries } => {
let mut parts = Vec::new();
for e in entries {
parts.push(format!(
"{}: {}",
py_expr(&e.key, depth + 1)?,
py_expr(&e.value, depth + 1)?
));
}
format!("{{{}}}", parts.join(", "))
}
PirValue::Unsupported { reason, .. } => {
return Err(ParallaxError::new(
ErrorCode::UnsupportedValue,
format!("cannot emit unsupported value: {reason}"),
));
}
PirValue::Function { name, .. } => {
return Err(ParallaxError::new(
ErrorCode::UnsupportedValue,
format!("cannot emit function {:?}", name),
));
}
PirValue::Ref { id } => {
return Err(ParallaxError::new(
ErrorCode::UnsupportedValue,
format!("cannot emit ref {id}"),
));
}
})
}