ergo_lib_wasm/ast/
js_conv.rs1#![allow(clippy::wildcard_enum_match_arm)]
4
5use std::convert::TryFrom;
6
7use ergo_lib::ergotree_ir::bigint256::BigInt256;
8use ergo_lib::ergotree_ir::mir::constant::Constant;
9use ergo_lib::ergotree_ir::mir::constant::Literal;
10use ergo_lib::ergotree_ir::mir::constant::TryExtractFromError;
11use ergo_lib::ergotree_ir::mir::constant::TryExtractInto;
12use ergo_lib::ergotree_ir::mir::value::CollKind;
13use ergo_lib::ergotree_ir::mir::value::NativeColl;
14use ergo_lib::ergotree_ir::types::stuple::STuple;
15use ergo_lib::ergotree_ir::types::stuple::TupleItems;
16use ergo_lib::ergotree_ir::types::stype::SType;
17use js_sys::Array;
18use js_sys::JsString;
19use js_sys::Number;
20use js_sys::Uint8Array;
21use num_traits::Num;
22use sigma_util::AsVecI8;
23use sigma_util::AsVecU8;
24use thiserror::Error;
25use wasm_bindgen::prelude::wasm_bindgen;
26use wasm_bindgen::JsCast;
27use wasm_bindgen::JsValue;
28
29const TUPLE_TOKEN: &str = "Tuple";
30
31#[wasm_bindgen]
33pub fn array_as_tuple(items: Vec<JsValue>) -> JsValue {
34 let arr = Array::new();
35 arr.push(&JsValue::from_str(TUPLE_TOKEN));
36 for item in items {
37 arr.push(&item);
38 }
39 arr.into()
40}
41
42#[allow(missing_docs)]
43#[derive(Debug, Error)]
44pub enum ConvError {
45 #[error("not supported: {0:?}")]
46 NotSupported(JsValue),
47 #[error("unexpected constant: {0:?}")]
48 UnexpectedConst(Constant),
49 #[error("unexpected js value: {0:?}, expected {1}")]
50 UnexpectedJs(JsValue, &'static str),
51 #[error("IO error: {0}")]
52 TryExtractFromError(#[from] TryExtractFromError),
53 #[error("Failed to parse Long from string: {0}")]
54 FailedToParseLongFromString(String),
55 #[error("Failed to convert JS BigInt to Ergo BigInt: {0}")]
56 FailedToConvertJsBigInt(js_sys::BigInt),
57 #[error(
58 "Invalid tuple encoding, expected the first array item to be '{}''",
59 TUPLE_TOKEN
60 )]
61 InvalidTupleEncoding,
62}
63
64pub(crate) fn constant_from_js(val: &JsValue) -> Result<Constant, ConvError> {
65 if let Ok(bytes) = val.clone().dyn_into::<Uint8Array>() {
66 Ok(Constant {
67 tpe: SType::SColl(SType::SByte.into()),
68 v: Literal::Coll(CollKind::NativeColl(NativeColl::CollByte(
69 bytes.to_vec().as_vec_i8().into(),
70 ))),
71 })
72 } else if let Ok(arr) = val.clone().dyn_into::<Array>() {
73 if let Ok(str) = arr.get(0).dyn_into::<JsString>() {
74 if str == TUPLE_TOKEN {
75 tuple_from_js(&arr)
76 } else if let Ok(coll_longs) = coll_long_from_js(&arr) {
77 Ok(coll_longs)
78 } else {
79 return Err(ConvError::NotSupported(val.clone()));
80 }
81 } else {
82 let mut cs: Vec<Constant> = Vec::new();
84 for i in 0..arr.length() {
85 let elem_const = constant_from_js(&arr.get(i))?;
86 cs.push(elem_const);
87 }
88 let elem_tpe = cs[0].tpe.clone();
89 Ok(Constant {
90 tpe: SType::SColl(elem_tpe.clone().into()),
91 v: Literal::Coll(CollKind::WrappedColl {
92 elem_tpe,
93 items: cs.into_iter().map(|c| c.v).collect(),
94 }),
95 })
96 }
97 } else if let Ok(num) = val.clone().dyn_into::<Number>() {
98 let c: Constant = (num.value_of() as i32).into();
99 Ok(c)
100 } else if let Ok(long_js_str) = val.clone().dyn_into::<JsString>() {
101 let long_str: String = long_js_str.into();
102 let long: i64 = long_str
103 .parse::<i64>()
104 .map_err(|_| ConvError::FailedToParseLongFromString(long_str))?;
105 let c: Constant = long.into();
106 Ok(c)
107 } else if let Ok(bigint_js) = val.clone().dyn_into::<js_sys::BigInt>() {
108 let c: Constant = js_bigint_to_ergo_bigint(bigint_js)?.into();
109 Ok(c)
110 } else {
111 Err(ConvError::NotSupported(val.clone()))
112 }
113}
114
115fn tuple_from_js(arr: &Array) -> Result<Constant, ConvError> {
116 let mut v: Vec<Constant> = Vec::new();
118 for i in 1..arr.length() {
119 let elem_const = constant_from_js(&arr.get(i))?;
120 v.push(elem_const);
121 }
122 #[allow(clippy::unwrap_used)]
123 Ok(Constant {
124 tpe: SType::STuple(STuple {
125 items: TupleItems::try_from(
126 v.clone().into_iter().map(|c| c.tpe).collect::<Vec<SType>>(),
127 )
128 .unwrap(),
129 }),
130 v: Literal::Tup(
131 TupleItems::try_from(v.into_iter().map(|c| c.v).collect::<Vec<Literal>>()).unwrap(),
132 ),
133 })
134}
135
136fn coll_long_from_js(arr: &Array) -> Result<Constant, ConvError> {
137 let mut longs: Vec<i64> = Vec::new();
139 for i in 0..arr.length() {
140 let arr_elem = arr.get(i);
141 let js_string = arr_elem
142 .clone()
143 .dyn_into::<JsString>()
144 .map_err(|_| ConvError::UnexpectedJs(arr_elem, "JsString"))?;
145 let parsed_i64 = String::from(js_string.clone())
146 .parse::<i64>()
147 .map_err(|e| ConvError::FailedToParseLongFromString(js_string.into()))?;
148 longs.push(parsed_i64);
149 }
150 Ok(longs.into())
151}
152
153pub(crate) fn constant_to_js(c: Constant) -> Result<JsValue, ConvError> {
154 Ok(match c.tpe {
155 SType::SBoolean => c.v.try_extract_into::<bool>()?.into(),
156 SType::SShort => c.v.try_extract_into::<i16>()?.into(),
157 SType::SByte => c.v.try_extract_into::<i8>()?.into(),
158 SType::SInt => c.v.try_extract_into::<i32>()?.into(),
159 SType::SLong => c.v.try_extract_into::<i64>()?.to_string().into(),
160 SType::SBigInt => ergo_bigint_to_js_bigint(c.v.try_extract_into::<BigInt256>()?).into(),
161 SType::SColl(_) => match c.v {
162 Literal::Coll(CollKind::NativeColl(NativeColl::CollByte(v))) => {
163 Uint8Array::from(v.as_vec_u8().as_slice()).into()
164 }
165 Literal::Coll(CollKind::WrappedColl { elem_tpe, items }) => {
166 let arr = Array::new();
167 for item in items.iter() {
168 arr.push(&constant_to_js(Constant {
169 tpe: elem_tpe.clone(),
170 v: item.clone(),
171 })?);
172 }
173 arr.into()
174 }
175 _ => return Err(ConvError::UnexpectedConst(c)),
176 },
177 SType::STuple(ref item_tpes) => {
178 let vec: Vec<JsValue> = match c.v {
179 Literal::Tup(v) => v
180 .into_iter()
181 .zip(item_tpes.clone().items.into_iter())
182 .map(|(v, tpe)| constant_to_js(Constant { tpe, v }))
183 .collect::<Result<Vec<JsValue>, _>>()?,
184 _ => return Err(ConvError::UnexpectedConst(c.clone())),
185 };
186 let arr = Array::new();
187 for item in vec {
188 arr.push(&item);
189 }
190 arr.into()
191 }
192 _ => return Err(ConvError::UnexpectedConst(c.clone())),
193 })
194}
195
196fn ergo_bigint_to_js_bigint(bigint: BigInt256) -> js_sys::BigInt {
197 let bigint_str = bigint.to_string();
198 #[allow(clippy::unwrap_used)]
199 js_sys::BigInt::new(&bigint_str.into()).unwrap()
201}
202
203fn js_bigint_to_ergo_bigint(bigint: js_sys::BigInt) -> Result<BigInt256, ConvError> {
204 #[allow(clippy::unwrap_used)]
205 let bigint_js_str: String = bigint.to_string(10).unwrap().into();
207 BigInt256::from_str_radix(bigint_js_str.as_str(), 10)
208 .map_err(|_| ConvError::FailedToConvertJsBigInt(bigint))
209}