boa/
lib.rs

1/*!
2This is an experimental Javascript lexer, parser and compiler written in Rust. Currently, it has support for some of the language.
3
4# Crate Features
5 - **serde** - Enables serialization and deserialization of the AST (Abstract Syntax Tree).
6 - **console** - Enables `boa`s WHATWG `console` object implementation.
7 - **profiler** - Enables profiling with measureme (this is mostly internal).
8
9**/
10
11#![doc(
12    html_logo_url = "https://raw.githubusercontent.com/jasonwilliams/boa/master/assets/logo.svg",
13    html_favicon_url = "https://raw.githubusercontent.com/jasonwilliams/boa/master/assets/logo.svg"
14)]
15#![deny(
16    clippy::all,
17    unused_qualifications,
18    unused_import_braces,
19    unused_lifetimes,
20    unreachable_pub,
21    trivial_numeric_casts,
22    // rustdoc,
23    missing_debug_implementations,
24    missing_copy_implementations,
25    deprecated_in_future,
26    meta_variable_misuse,
27    non_ascii_idents,
28    rust_2018_compatibility,
29    rust_2018_idioms,
30    future_incompatible,
31    nonstandard_style,
32)]
33#![warn(clippy::perf, clippy::single_match_else, clippy::dbg_macro)]
34#![allow(
35    clippy::missing_inline_in_public_items,
36    clippy::cognitive_complexity,
37    clippy::must_use_candidate,
38    clippy::missing_errors_doc,
39    clippy::as_conversions,
40    clippy::let_unit_value,
41    rustdoc::missing_doc_code_examples
42)]
43
44pub mod bigint;
45pub mod builtins;
46pub mod class;
47pub mod context;
48pub mod environment;
49pub mod exec;
50pub mod gc;
51pub mod object;
52pub mod profiler;
53pub mod property;
54pub mod realm;
55pub mod string;
56pub mod symbol;
57pub mod syntax;
58pub mod value;
59
60#[cfg(feature = "vm")]
61pub mod bytecompiler;
62#[cfg(feature = "vm")]
63pub mod vm;
64
65/// A convenience module that re-exports the most commonly-used Boa APIs
66pub mod prelude {
67    pub use crate::{object::JsObject, Context, JsBigInt, JsResult, JsString, JsValue};
68}
69
70use std::result::Result as StdResult;
71
72pub(crate) use crate::{exec::Executable, profiler::BoaProfiler};
73
74// Export things to root level
75#[doc(inline)]
76pub use crate::{
77    bigint::JsBigInt, context::Context, string::JsString, symbol::JsSymbol, value::JsValue,
78};
79
80use crate::syntax::{
81    ast::node::StatementList,
82    parser::{ParseError, Parser},
83};
84
85/// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`)
86#[must_use]
87pub type JsResult<T> = StdResult<T, JsValue>;
88
89/// Parses the given source code.
90///
91/// It will return either the statement list AST node for the code, or a parsing error if something
92/// goes wrong.
93#[inline]
94pub fn parse<T: AsRef<[u8]>>(src: T, strict_mode: bool) -> StdResult<StatementList, ParseError> {
95    let src_bytes: &[u8] = src.as_ref();
96    Parser::new(src_bytes, strict_mode).parse_all()
97}
98
99/// Execute the code using an existing Context
100/// The str is consumed and the state of the Context is changed
101#[cfg(test)]
102pub(crate) fn forward<T: AsRef<[u8]>>(context: &mut Context, src: T) -> String {
103    let src_bytes: &[u8] = src.as_ref();
104
105    // Setup executor
106    let expr = match parse(src_bytes, false) {
107        Ok(res) => res,
108        Err(e) => {
109            return format!(
110                "Uncaught {}",
111                context
112                    .throw_syntax_error(e.to_string())
113                    .expect_err("interpreter.throw_syntax_error() did not return an error")
114                    .display()
115            );
116        }
117    };
118    expr.run(context).map_or_else(
119        |e| format!("Uncaught {}", e.display()),
120        |v| v.display().to_string(),
121    )
122}
123
124/// Execute the code using an existing Context.
125/// The str is consumed and the state of the Context is changed
126/// Similar to `forward`, except the current value is returned instad of the string
127/// If the interpreter fails parsing an error value is returned instead (error object)
128#[allow(clippy::unit_arg, clippy::drop_copy)]
129#[cfg(test)]
130pub(crate) fn forward_val<T: AsRef<[u8]>>(context: &mut Context, src: T) -> JsResult<JsValue> {
131    let main_timer = BoaProfiler::global().start_event("Main", "Main");
132
133    let src_bytes: &[u8] = src.as_ref();
134    // Setup executor
135    let result = parse(src_bytes, false)
136        .map_err(|e| {
137            context
138                .throw_syntax_error(e.to_string())
139                .expect_err("interpreter.throw_syntax_error() did not return an error")
140        })
141        .and_then(|expr| expr.run(context));
142
143    // The main_timer needs to be dropped before the BoaProfiler is.
144    drop(main_timer);
145    BoaProfiler::global().drop();
146
147    result
148}
149
150/// Create a clean Context and execute the code
151#[cfg(test)]
152pub(crate) fn exec<T: AsRef<[u8]>>(src: T) -> String {
153    let src_bytes: &[u8] = src.as_ref();
154
155    match Context::new().eval(src_bytes) {
156        Ok(value) => value.display().to_string(),
157        Err(error) => error.display().to_string(),
158    }
159}
160
161#[cfg(test)]
162pub(crate) enum TestAction {
163    Execute(&'static str),
164    TestEq(&'static str, &'static str),
165    TestStartsWith(&'static str, &'static str),
166}
167
168/// Create a clean Context, call "forward" for each action, and optionally
169/// assert equality of the returned value or if returned value starts with
170/// expected string.
171#[cfg(test)]
172#[track_caller]
173pub(crate) fn check_output(actions: &[TestAction]) {
174    let mut context = Context::new();
175
176    let mut i = 1;
177    for action in actions {
178        match action {
179            TestAction::Execute(src) => {
180                forward(&mut context, src);
181            }
182            TestAction::TestEq(case, expected) => {
183                assert_eq!(
184                    &forward(&mut context, case),
185                    expected,
186                    "Test case {} ('{}')",
187                    i,
188                    case
189                );
190                i += 1;
191            }
192            TestAction::TestStartsWith(case, expected) => {
193                assert!(
194                    &forward(&mut context, case).starts_with(expected),
195                    "Test case {} ('{}')",
196                    i,
197                    case
198                );
199                i += 1;
200            }
201        }
202    }
203}