@@ -13,6 +13,7 @@ mod logger;
1313
1414use crate :: logger:: SharedExternalPrinterLogger ;
1515use boa_engine:: context:: time:: JsInstant ;
16+ use boa_engine:: error:: JsErasedError ;
1617use boa_engine:: job:: { GenericJob , TimeoutJob } ;
1718use boa_engine:: {
1819 Context , JsError , JsResult , Source ,
@@ -38,15 +39,13 @@ use rustyline::{EditMode, Editor, config::Config, error::ReadlineError};
3839use std:: collections:: BTreeMap ;
3940use std:: mem;
4041use std:: sync:: mpsc:: { Sender , TryRecvError } ;
41- use std:: time:: Duration ;
42+ use std:: time:: { Duration , Instant } ;
4243use std:: {
4344 cell:: RefCell ,
4445 collections:: VecDeque ,
45- eprintln,
4646 fs:: OpenOptions ,
47- io,
47+ io:: { self , IsTerminal , Read } ,
4848 path:: { Path , PathBuf } ,
49- println,
5049 rc:: Rc ,
5150 thread,
5251} ;
@@ -124,6 +123,10 @@ struct Opt {
124123 #[ arg( long = "vi" ) ]
125124 vi_mode : bool ,
126125
126+ /// Report parsing and execution timings.
127+ #[ arg( long) ]
128+ time : bool ,
129+
127130 #[ arg( long, short = 'O' , group = "optimizer" ) ]
128131 optimize : bool ,
129132
@@ -214,21 +217,90 @@ enum FlowgraphDirection {
214217 RightToLeft ,
215218}
216219
220+ struct Timer < ' a > {
221+ name : & ' static str ,
222+ start : Instant ,
223+ counters : & ' a mut Vec < ( & ' static str , Duration ) > ,
224+ }
225+
226+ impl Drop for Timer < ' _ > {
227+ fn drop ( & mut self ) {
228+ self . counters . push ( ( self . name , self . start . elapsed ( ) ) ) ;
229+ }
230+ }
231+
232+ struct Counters {
233+ counters : Option < Vec < ( & ' static str , Duration ) > > ,
234+ }
235+
236+ impl Counters {
237+ fn new ( enabled : bool ) -> Self {
238+ Self {
239+ counters : enabled. then_some ( Vec :: new ( ) ) ,
240+ }
241+ }
242+
243+ fn new_timer ( & mut self , name : & ' static str ) -> Option < Timer < ' _ > > {
244+ self . counters . as_mut ( ) . map ( |counters| Timer {
245+ name,
246+ start : Instant :: now ( ) ,
247+ counters,
248+ } )
249+ }
250+ }
251+
252+ impl Drop for Counters {
253+ fn drop ( & mut self ) {
254+ let Some ( counters) = self . counters . take ( ) else {
255+ return ;
256+ } ;
257+ if counters. is_empty ( ) {
258+ return ;
259+ }
260+
261+ let max_width = counters
262+ . iter ( )
263+ . map ( |( name, _) | name. len ( ) )
264+ . max ( )
265+ . unwrap_or ( 0 )
266+ . max ( "Total" . len ( ) )
267+ + 1 ; // +1 for the colon
268+
269+ let mut total = Duration :: ZERO ;
270+ eprintln ! ( ) ;
271+ for ( name, elapsed) in & counters {
272+ eprintln ! (
273+ "{:<width$} {elapsed:.2?}" ,
274+ format!( "{name}:" ) ,
275+ width = max_width
276+ ) ;
277+ total += * elapsed;
278+ }
279+ if counters. len ( ) > 1 {
280+ eprintln ! ( "{:<width$} {total:.2?}" , "Total:" , width = max_width) ;
281+ }
282+ }
283+ }
284+
217285/// Dumps the AST to stdout with format controlled by the given arguments.
218286///
219287/// Returns a error of type String with a error message,
220288/// if the source has a syntax or parsing error.
221289fn dump < R : ReadChar > ( src : Source < ' _ , R > , args : & Opt , context : & mut Context ) -> Result < ( ) > {
222290 if let Some ( arg) = args. dump_ast {
291+ let mut counters = Counters :: new ( args. time ) ;
223292 let arg = arg. unwrap_or_default ( ) ;
224293 let mut parser = boa_parser:: Parser :: new ( src) ;
225294 let dump =
226295 if args. module {
227296 let scope = context. realm ( ) . scope ( ) . clone ( ) ;
228- let module = parser
229- . parse_module ( & scope, context. interner_mut ( ) )
230- . map_err ( |e| eyre ! ( "Uncaught SyntaxError: {e}" ) ) ?;
231-
297+ let module = {
298+ let _timer = counters. new_timer ( "Parsing" ) ;
299+ parser
300+ . parse_module ( & scope, context. interner_mut ( ) )
301+ . map_err ( |e| eyre ! ( "Uncaught SyntaxError: {e}" ) ) ?
302+ } ;
303+ let _timer = counters. new_timer ( "AST generation" ) ;
232304 match arg {
233305 DumpFormat :: Json => serde_json:: to_string ( & module)
234306 . expect ( "could not convert AST to a JSON string" ) ,
@@ -238,14 +310,18 @@ fn dump<R: ReadChar>(src: Source<'_, R>, args: &Opt, context: &mut Context) -> R
238310 }
239311 } else {
240312 let scope = context. realm ( ) . scope ( ) . clone ( ) ;
241- let mut script = parser
242- . parse_script ( & scope, context. interner_mut ( ) )
243- . map_err ( |e| eyre ! ( "Uncaught SyntaxError: {e}" ) ) ?;
313+ let mut script = {
314+ let _timer = counters. new_timer ( "Parsing" ) ;
315+ parser
316+ . parse_script ( & scope, context. interner_mut ( ) )
317+ . map_err ( |e| eyre ! ( "Uncaught SyntaxError: {e}" ) ) ?
318+ } ;
244319
245320 if args. optimize {
246321 context. optimize_statement_list ( script. statements_mut ( ) ) ;
247322 }
248323
324+ let _timer = counters. new_timer ( "AST generation" ) ;
249325 match arg {
250326 DumpFormat :: Json => serde_json:: to_string ( & script)
251327 . expect ( "could not convert AST to a JSON string" ) ,
@@ -254,7 +330,7 @@ fn dump<R: ReadChar>(src: Source<'_, R>, args: &Opt, context: &mut Context) -> R
254330 DumpFormat :: Debug => format ! ( "{script:#?}" ) ,
255331 }
256332 } ;
257-
333+ drop ( counters ) ;
258334 println ! ( "{dump}" ) ;
259335 }
260336
@@ -307,7 +383,7 @@ fn evaluate_expr(
307383 printer : & SharedExternalPrinterLogger ,
308384) -> Result < ( ) > {
309385 if args. has_dump_flag ( ) {
310- dump ( Source :: from_bytes ( & line) , args, context) ?;
386+ dump ( Source :: from_bytes ( line) , args, context) ?;
311387 } else if let Some ( flowgraph) = args. flowgraph {
312388 match generate_flowgraph (
313389 context,
@@ -319,8 +395,27 @@ fn evaluate_expr(
319395 Err ( v) => eprintln ! ( "{v:?}" ) ,
320396 }
321397 } else {
322- match context. eval ( Source :: from_bytes ( line) ) {
323- Ok ( v) => printer. print ( format ! ( "{}\n " , v. display( ) ) ) ,
398+ let mut counters = Counters :: new ( args. time ) ;
399+ let script = {
400+ let _timer = counters. new_timer ( "Parsing" ) ;
401+ Script :: parse ( Source :: from_bytes ( line) , None , context)
402+ } ;
403+
404+ match script {
405+ Ok ( script) => {
406+ let result = {
407+ let _timer = counters. new_timer ( "Execution" ) ;
408+ let result = script. evaluate ( context) ;
409+ if let Err ( err) = context. run_jobs ( ) {
410+ printer. print ( uncaught_job_error ( & err) ) ;
411+ }
412+ result
413+ } ;
414+ match result {
415+ Ok ( v) => printer. print ( format ! ( "{}\n " , v. display( ) ) ) ,
416+ Err ( ref v) => printer. print ( uncaught_error ( v) ) ,
417+ }
418+ }
324419 Err ( ref v) => printer. print ( uncaught_error ( v) ) ,
325420 }
326421 }
@@ -353,29 +448,53 @@ fn evaluate_file(
353448 }
354449
355450 if args. module {
356- let module = Module :: parse ( Source :: from_filepath ( file) ?, None , context)
357- . map_err ( |e| e. into_erased ( context) ) ?;
451+ let source = Source :: from_filepath ( file) ?;
452+ let mut counters = Counters :: new ( args. time ) ;
453+ let module = {
454+ let _timer = counters. new_timer ( "Parsing" ) ;
455+ Module :: parse ( source, None , context)
456+ } ;
457+ let module = module. map_err ( |e| e. into_erased ( context) ) ?;
358458
359459 loader. insert (
360460 file. canonicalize ( )
361461 . wrap_err ( "could not canonicalize input file path" ) ?,
362462 module. clone ( ) ,
363463 ) ;
364464
365- let promise = module. load_link_evaluate ( context) ;
366- context. run_jobs ( ) . map_err ( |err| err. into_erased ( context) ) ?;
465+ let promise = {
466+ let _timer = counters. new_timer ( "Execution" ) ;
467+ let promise = module. load_link_evaluate ( context) ;
468+ context. run_jobs ( ) . map_err ( |err| err. into_erased ( context) ) ?;
469+ Ok :: < _ , JsErasedError > ( promise)
470+ } ?;
367471 let result = promise. state ( ) ;
368472
369473 return match result {
370474 PromiseState :: Pending => Err ( eyre ! ( "module didn't execute" ) ) ,
371475 PromiseState :: Fulfilled ( _) => Ok ( ( ) ) ,
372476 PromiseState :: Rejected ( err) => {
373- return Err ( JsError :: from_opaque ( err) . into_erased ( context) . into ( ) ) ;
477+ Err ( JsError :: from_opaque ( err) . into_erased ( context) . into ( ) )
374478 }
375479 } ;
376480 }
377481
378- match context. eval ( Source :: from_filepath ( file) ?) {
482+ let source = Source :: from_filepath ( file) ?;
483+ let mut counters = Counters :: new ( args. time ) ;
484+ let script = {
485+ let _timer = counters. new_timer ( "Parsing" ) ;
486+ Script :: parse ( source, None , context)
487+ } ;
488+ let script = script. map_err ( |e| e. into_erased ( context) ) ?;
489+
490+ let result = {
491+ let _timer = counters. new_timer ( "Execution" ) ;
492+ let result = script. evaluate ( context) ;
493+ context. run_jobs ( ) . map_err ( |err| err. into_erased ( context) ) ?;
494+ result
495+ } ;
496+
497+ match result {
379498 Ok ( v) => {
380499 if !v. is_undefined ( ) {
381500 println ! ( "{}" , v. display( ) ) ;
@@ -384,9 +503,7 @@ fn evaluate_file(
384503 Err ( v) => printer. print ( uncaught_error ( & v) ) ,
385504 }
386505
387- context
388- . run_jobs ( )
389- . map_err ( |err| err. into_erased ( context) . into ( ) )
506+ Ok ( ( ) )
390507}
391508
392509fn evaluate_files (
@@ -454,6 +571,16 @@ fn main() -> Result<()> {
454571 } else if let Some ( ref expr) = args. expression {
455572 evaluate_expr ( expr, & args, & mut context, & printer) ?;
456573 return Ok ( ( ) ) ;
574+ } else if !io:: stdin ( ) . is_terminal ( ) {
575+ let mut input = String :: new ( ) ;
576+ io:: stdin ( )
577+ . read_to_string ( & mut input)
578+ . wrap_err ( "failed to read stdin" ) ?;
579+ return if input. is_empty ( ) {
580+ Ok ( ( ) )
581+ } else {
582+ evaluate_expr ( & input, & args, & mut context, & printer)
583+ } ;
457584 }
458585
459586 let handle = start_readline_thread ( sender, printer. clone ( ) , args. vi_mode ) ;
0 commit comments