use nepali_core::{HostJs, Value};
use rquickjs::{Context, Runtime};
use std::cell::RefCell;
use std::rc::Rc;
use swc_core::common::sync::Lrc;
use swc_core::common::{FileName, Globals, Mark, SourceMap, GLOBALS};
use swc_core::ecma::ast::{Pass, Program};
use swc_core::ecma::codegen::text_writer::JsWriter;
use swc_core::ecma::codegen::{Config as CodegenConfig, Emitter};
use swc_core::ecma::parser::{lexer::Lexer, Parser, StringInput, Syntax, TsSyntax};
use swc_core::ecma::transforms::base::resolver;
use swc_core::ecma::transforms::typescript::strip;
pub struct QuickJsHost;
impl HostJs for QuickJsHost {
fn eval(&self, code: &str) -> Result<Value, String> {
eval_js(code, "जेएस_चलाउनुहोस्")
}
fn eval_ts(&self, code: &str) -> Result<Value, String> {
let js = transpile_ts(code)?;
eval_js(&js, "टिएस_चलाउनुहोस्")
}
}
fn eval_js(code: &str, builtin_name: &str) -> Result<Value, String> {
let rt = Runtime::new().map_err(|e| format!("{builtin_name}: runtime सुरु गर्न सकिएन: {e}"))?;
let ctx = Context::full(&rt).map_err(|e| format!("{builtin_name}: context सुरु गर्न सकिएन: {e}"))?;
ctx.with(|ctx| {
let result: rquickjs::Result<rquickjs::Value> = ctx.eval(code);
match result {
Ok(v) => js_to_value(&v),
Err(e) => Err(format!("javascript error: {e}")),
}
})
}
fn transpile_ts(code: &str) -> Result<String, String> {
let cm: Lrc<SourceMap> = Default::default();
let fm = cm.new_source_file(Lrc::new(FileName::Custom("input.ts".into())), code.to_string());
let lexer = Lexer::new(
Syntax::Typescript(TsSyntax::default()),
Default::default(),
StringInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
let module = parser
.parse_module()
.map_err(|e| format!("टिएस_चलाउनुहोस्: parse error: {e:?}"))?;
let globals = Globals::new();
GLOBALS.set(&globals, || {
let unresolved_mark = Mark::new();
let top_level_mark = Mark::new();
let mut program = Program::Module(module);
resolver(unresolved_mark, top_level_mark, true).process(&mut program);
strip(unresolved_mark, top_level_mark).process(&mut program);
let module = match program {
Program::Module(m) => m,
Program::Script(_) => unreachable!("parsed as a module above, never a script"),
};
let mut buf = Vec::new();
{
let writer = JsWriter::new(cm.clone(), "\n", &mut buf, None);
let mut emitter = Emitter {
cfg: CodegenConfig::default(),
cm: cm.clone(),
comments: None,
wr: writer,
};
emitter
.emit_module(&module)
.map_err(|e| format!("टिएस_चलाउनुहोस्: codegen error: {e}"))?;
}
String::from_utf8(buf).map_err(|e| format!("टिएस_चलाउनुहोस्: generated JS wasn't valid UTF-8: {e}"))
})
}
fn js_to_value(v: &rquickjs::Value) -> Result<Value, String> {
if v.is_null() || v.is_undefined() {
return Ok(Value::Null);
}
if v.is_bool() {
return Ok(Value::Bool(v.as_bool().unwrap()));
}
if v.is_number() {
return Ok(Value::Number(v.as_number().unwrap()));
}
if v.is_string() {
return Ok(Value::Str(
v.as_string().unwrap().to_string().map_err(|e| e.to_string())?,
));
}
if v.is_array() {
let arr = v.as_array().unwrap();
let mut values = Vec::with_capacity(arr.len());
for item in arr.iter::<rquickjs::Value>() {
let item = item.map_err(|e| e.to_string())?;
values.push(js_to_value(&item)?);
}
return Ok(Value::Array(Rc::new(RefCell::new(values))));
}
Err(format!(
"परिणामको type '{}' लाई यो भाषाको मानमा बदल्न सकिँदैन \
(numbers, strings, bools, null/undefined, ra arrays matra samarthit cha)",
v.type_name()
))
}
#[cfg(test)]
mod tests {
use super::*;
fn number(v: Value) -> f64 {
match v {
Value::Number(n) => n,
other => panic!("expected Number, got {other:?}"),
}
}
#[test]
fn real_js_arithmetic() {
let host = QuickJsHost;
assert_eq!(number(host.eval("1 + 2 * 3").unwrap()), 7.0);
}
#[test]
fn real_js_function_with_map_returns_array() {
let host = QuickJsHost;
let result = host.eval("[0,1,2,3].map(x => x * x)").unwrap();
match result {
Value::Array(a) => {
let nums: Vec<f64> = a.borrow().iter().map(|v| number(v.clone())).collect();
assert_eq!(nums, vec![0.0, 1.0, 4.0, 9.0]);
}
other => panic!("expected Array, got {other:?}"),
}
}
#[test]
fn js_syntax_error_is_a_real_error() {
let host = QuickJsHost;
assert!(host.eval("this is not { valid js (((").is_err());
}
#[test]
fn js_object_result_is_a_real_explicit_error() {
let host = QuickJsHost;
let err = host.eval("({a: 1})").unwrap_err();
assert!(err.contains("object"), "error should name the real JS type: {err}");
}
#[test]
fn real_ts_interface_and_typed_function() {
let host = QuickJsHost;
let ts = "interface Point { x: number; y: number; } \
function dist(a: Point, b: Point): number { \
return Math.sqrt((a.x-b.x)**2 + (a.y-b.y)**2); \
} \
dist({x:0,y:0}, {x:3,y:4})";
assert_eq!(number(host.eval_ts(ts).unwrap()), 5.0);
}
#[test]
fn real_ts_enum_compiles_to_real_reverse_mapping() {
let host = QuickJsHost;
let ts = "enum Direction { Up, Down } \
Direction[Direction.Up]";
match host.eval_ts(ts).unwrap() {
Value::Str(s) => assert_eq!(s, "Up"),
other => panic!("expected Str(\"Up\"), got {other:?}"),
}
}
#[test]
fn real_ts_generic_function() {
let host = QuickJsHost;
let ts = "function identity<T>(x: T): T { return x; } identity<string>('ok')";
match host.eval_ts(ts).unwrap() {
Value::Str(s) => assert_eq!(s, "ok"),
other => panic!("expected Str(\"ok\"), got {other:?}"),
}
}
#[test]
fn ts_type_error_at_parse_time_is_a_real_error() {
let host = QuickJsHost;
assert!(host.eval_ts("interface Broken {").is_err());
}
}