1use chrono::{Local, TimeZone};
6use js_sys::Date;
7use rink_core::ast;
8use rink_core::output::fmt::{FmtToken, Span, TokenFmt};
9use rink_core::output::QueryReply;
10use rink_core::parsing::text_query;
11use serde_derive::*;
12use wasm_bindgen::prelude::*;
13
14#[global_allocator]
16static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
17
18pub fn set_panic_hook() {
19 #[cfg(feature = "console_error_panic_hook")]
26 console_error_panic_hook::set_once();
27}
28
29#[derive(Serialize, Debug)]
31#[serde(rename_all = "lowercase")]
32#[serde(tag = "result")]
33enum Success<Ok, Err> {
34 Ok(Ok),
35 Err(Err),
36}
37
38impl<Ok, Err> From<Result<Ok, Err>> for Success<Ok, Err> {
39 fn from(result: Result<Ok, Err>) -> Success<Ok, Err> {
40 match result {
41 Ok(value) => Success::Ok(value),
42 Err(value) => Success::Err(value),
43 }
44 }
45}
46
47#[wasm_bindgen]
48pub struct Query {
49 query: ast::Query,
50}
51
52#[wasm_bindgen]
53impl Query {
54 #[wasm_bindgen(constructor)]
55 pub fn new(input: &str) -> Query {
56 set_panic_hook();
57 let mut iter = text_query::TokenIterator::new(input.trim()).peekable();
58 let query = text_query::parse_query(&mut iter);
59 Query { query }
60 }
61
62 #[wasm_bindgen(js_name = getExpr)]
63 pub fn get_expr(&self) -> JsValue {
64 serde_wasm_bindgen::to_value(&self.query).unwrap()
65 }
66}
67
68#[wasm_bindgen]
69pub struct Context {
70 context: rink_core::Context,
71}
72
73#[derive(Serialize, Debug)]
74struct Token {
75 pub text: String,
76 pub fmt: FmtToken,
77}
78
79#[derive(Serialize, Debug)]
80#[serde(tag = "type")]
81#[serde(rename_all = "lowercase")]
82enum SpanOrList {
83 Span(Token),
84 List { children: Vec<SpanOrList> },
85}
86
87fn visit_tokens(spans: &[Span]) -> Vec<SpanOrList> {
88 spans
89 .iter()
90 .map(|span| match span {
91 Span::Content { text, token } => SpanOrList::Span(Token {
92 text: text.to_string(),
93 fmt: *token,
94 }),
95 Span::Child(child) => SpanOrList::List {
96 children: visit_tokens(&child.to_spans()),
97 },
98 })
99 .collect()
100}
101
102#[wasm_bindgen]
103impl Context {
104 #[wasm_bindgen(constructor)]
105 pub fn new() -> Context {
106 set_panic_hook();
107 let mut context = rink_core::simple_context().unwrap();
108 context.use_humanize = false;
110 Context { context }
111 }
112
113 #[wasm_bindgen(js_name = setSavePreviousResult)]
114 pub fn set_save_previous_result(&mut self, value: bool) {
115 self.context.save_previous_result = value;
116 }
117
118 #[wasm_bindgen(js_name = setTime)]
119 pub fn set_time(&mut self, date: Date) {
120 self.context
121 .set_time(Local.timestamp_millis_opt(date.value_of() as i64).unwrap());
122 }
123
124 #[wasm_bindgen(js_name = loadCurrency)]
125 pub fn load_currency(&mut self, live_defs: String) -> Result<(), JsValue> {
126 let mut live_defs: Vec<ast::DefEntry> =
127 serde_json::from_str(&live_defs).map_err(|e| e.to_string())?;
128
129 let mut base_defs = {
130 use rink_core::loader::gnu_units;
131 let defs = rink_core::CURRENCY_FILE.unwrap();
132 let mut iter = gnu_units::TokenIterator::new(defs).peekable();
133 gnu_units::parse(&mut iter)
134 };
135 let currency = {
136 let mut defs = vec![];
137 defs.append(&mut live_defs);
138 defs.append(&mut base_defs.defs);
139 ast::Defs { defs }
140 };
141 self.context.load(currency)?;
142
143 Ok(())
144 }
145
146 #[wasm_bindgen]
147 pub fn eval(&mut self, expr: &Query) -> JsValue {
148 let value = self.context.eval_query(&expr.query);
149 if self.context.save_previous_result {
150 if let Ok(QueryReply::Number(ref number_parts)) = value {
151 if let Some(ref raw) = number_parts.raw_value {
152 self.context.previous_result = Some(raw.clone());
153 }
154 }
155 }
156 let value = Success::from(value);
157 match serde_wasm_bindgen::to_value(&value) {
158 Ok(value) => value,
159 Err(err) => format!("Failed to serialize: {}\n{:#?}", err, value).into(),
160 }
161 }
162
163 #[wasm_bindgen]
164 pub fn eval_tokens(&mut self, expr: &Query) -> JsValue {
165 let value = self.context.eval_query(&expr.query);
166 if self.context.save_previous_result {
167 if let Ok(QueryReply::Number(ref number_parts)) = value {
168 if let Some(ref raw) = number_parts.raw_value {
169 self.context.previous_result = Some(raw.clone());
170 }
171 }
172 }
173 let spans = match value {
174 Ok(ref value) => value.to_spans(),
175 Err(ref value) => value.to_spans(),
176 };
177 let tokens = visit_tokens(&spans);
178
179 match serde_wasm_bindgen::to_value(&tokens) {
180 Ok(value) => value,
181 Err(err) => format!("Failed to serialize: {}\n{:#?}", err, tokens).into(),
182 }
183 }
184}
185
186#[wasm_bindgen]
187pub fn version() -> String {
188 rink_core::version().to_owned()
189}