rink_js/
lib.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use 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// Use `wee_alloc` as the global allocator.
15#[global_allocator]
16static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
17
18pub fn set_panic_hook() {
19    // When the `console_error_panic_hook` feature is enabled, we can call the
20    // `set_panic_hook` function at least once during initialization, and then
21    // we will get better error messages if our code ever panics.
22    //
23    // For more details see
24    // https://github.com/rustwasm/console_error_panic_hook#readme
25    #[cfg(feature = "console_error_panic_hook")]
26    console_error_panic_hook::set_once();
27}
28
29/// Wrapper around Result because serde produces ugly output by default.
30#[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        // Will panic if this is set.
109        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}