1use alloc::rc::Rc;
12use alloc::vec::Vec;
13use alloc::boxed::Box;
14use alloc::string::String;
15use alloc::collections::VecDeque;
16
17use core::time::Duration;
18use core::cell::{Cell, RefCell};
19use core::{mem, fmt};
20
21use std::fs::File;
22use std::io::{self, Read, Write as IoWrite, stdout};
23use std::sync::{Arc, Mutex};
24use std::sync::mpsc::{channel, Sender, TryRecvError};
25use std::sync::atomic::{AtomicBool, Ordering as MemoryOrder};
26use std::thread;
27
28use clap::Subcommand;
29use actix_web::{get, post, web, App, HttpServer, Responder, HttpResponse};
30use actix_cors::Cors;
31
32use crossterm::{cursor, execute, queue};
33use crossterm::tty::IsTty;
34use crossterm::event::{self, Event, KeyCode as RawKeyCode, KeyModifiers as RawKeyModifiers};
35use crossterm::terminal::{self, ClearType};
36use crossterm::style::{ResetColor, SetForegroundColor, Color, Print};
37
38use crate::*;
39use crate::gc::*;
40use crate::json::*;
41use crate::real_time::*;
42use crate::std_system::*;
43use crate::std_util::*;
44use crate::bytecode::*;
45use crate::runtime::*;
46use crate::process::*;
47use crate::project::*;
48use crate::template::*;
49use crate::compact_str::*;
50
51const DEFAULT_BASE_URL: &str = "https://cloud.netsblox.org";
52const DEFAULT_EDITOR_URL: &str = "https://editor.netsblox.org";
53
54const STEPS_PER_IO_ITER: usize = 64;
55const MAX_REQUEST_SIZE_BYTES: usize = 1024 * 1024 * 1024;
56const YIELDS_BEFORE_IDLE_SLEEP: usize = 256;
57const IDLE_SLEEP_TIME: Duration = Duration::from_micros(500);
58const CLOCK_INTERVAL: Duration = Duration::from_millis(10);
59const COLLECT_INTERVAL: Duration = Duration::from_secs(60);
60
61macro_rules! crash {
62 ($ret:literal : $($tt:tt)*) => {{
63 eprint!($($tt)*);
64 eprint!("\r\n");
65 std::process::exit($ret);
66 }}
67}
68
69struct AtExit<F: FnOnce()>(Option<F>);
70impl<F: FnOnce()> AtExit<F> {
71 fn new(f: F) -> Self { Self(Some(f)) }
72}
73impl<F: FnOnce()> Drop for AtExit<F> {
74 fn drop(&mut self) {
75 self.0.take().unwrap()()
76 }
77}
78
79#[derive(Collect)]
80#[collect(no_drop, bound = "")]
81struct Env<'gc, C: CustomTypes<StdSystem<C>>> {
82 proj: Gc<'gc, RefLock<Project<'gc, C, StdSystem<C>>>>,
83 #[collect(require_static)] locs: Locations,
84}
85type EnvArena<S> = Arena<Rootable![Env<'_, S>]>;
86
87fn get_env<C: CustomTypes<StdSystem<C>>>(role: &ast::Role, system: Rc<StdSystem<C>>) -> Result<EnvArena<C>, FromAstError> {
88 let (bytecode, init_info, locs, _) = ByteCode::compile(role)?;
89 Ok(EnvArena::new(|mc| {
90 let proj = Project::from_init(mc, &init_info, Rc::new(bytecode), Settings::default(), system);
91 Env { proj: Gc::new(mc, RefLock::new(proj)), locs }
92 }))
93}
94
95#[derive(Subcommand)]
97pub enum Mode {
98 Run {
100 src: CompactString,
102 #[clap(long)]
104 role: Option<CompactString>,
105
106 #[clap(long, default_value_t = CompactString::from(DEFAULT_BASE_URL))]
108 server: CompactString,
109 },
110 Dump {
112 src: CompactString,
114 #[clap(long)]
116 role: Option<CompactString>,
117 },
118 Start {
120 #[clap(long, default_value_t = CompactString::from(DEFAULT_BASE_URL))]
122 server: CompactString,
123
124 #[clap(long, default_value_t = CompactString::from("127.0.0.1"))]
126 addr: CompactString,
127 #[clap(long, default_value_t = 6286)]
129 port: u16,
130 },
131}
132
133#[derive(Debug)]
134enum OpenProjectError<'a> {
135 ParseError { error: Box<ast::Error> },
136 RoleNotFound { role: &'a str },
137 NoRoles,
138 MultipleRoles { count: usize },
139}
140impl fmt::Display for OpenProjectError<'_> {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 match self {
143 OpenProjectError::ParseError { error } => write!(f, "failed to parse project: {error:?}"),
144 OpenProjectError::RoleNotFound { role } => write!(f, "no role named '{role}'"),
145 OpenProjectError::NoRoles => write!(f, "project had no roles"),
146 OpenProjectError::MultipleRoles { count } => write!(f, "project had multiple ({count}) roles, but a specific role was not specified"),
147 }
148 }
149}
150
151fn read_file(src: &str) -> io::Result<String> {
152 let mut file = File::open(src)?;
153 let mut s = String::new();
154 file.read_to_string(&mut s)?;
155 Ok(s)
156}
157fn open_project<'a>(content: &str, role: Option<&'a str>) -> Result<(CompactString, ast::Role), OpenProjectError<'a>> {
158 let parsed = match ast::Parser::default().parse(content) {
159 Ok(x) => x,
160 Err(error) => return Err(OpenProjectError::ParseError { error }),
161 };
162 let role = match role {
163 Some(role) => match parsed.roles.into_iter().find(|x| x.name == role) {
164 Some(x) => x,
165 None => return Err(OpenProjectError::RoleNotFound { role }),
166 }
167 None => match parsed.roles.len() {
168 0 => return Err(OpenProjectError::NoRoles),
169 1 => parsed.roles.into_iter().next().unwrap(),
170 count => return Err(OpenProjectError::MultipleRoles { count }),
171 }
172 };
173 Ok((parsed.name, role))
174}
175
176fn run_proj_tty<C: CustomTypes<StdSystem<C>>>(project_name: &str, server: CompactString, role: &ast::Role, overrides: Config<C, StdSystem<C>>, clock: Arc<Clock>) {
177 terminal::enable_raw_mode().unwrap();
178 execute!(stdout(), cursor::Hide).unwrap();
179 let _tty_mode_guard = AtExit::new(|| {
180 terminal::disable_raw_mode().unwrap();
181 execute!(stdout(), cursor::Show).unwrap()
182 });
183
184 let old_panic_hook = std::panic::take_hook();
185 std::panic::set_hook(Box::new(move |ctx| {
186 let _ = terminal::disable_raw_mode();
187 old_panic_hook(ctx);
188 }));
189
190 let update_flag = Rc::new(Cell::new(false));
191 let input_queries = Rc::new(RefCell::new(VecDeque::new()));
192 let mut term_size = terminal::size().unwrap();
193 let mut input_value = CompactString::default();
194
195 let config = overrides.fallback(&Config {
196 command: {
197 let update_flag = update_flag.clone();
198 Some(Rc::new(move |_, key, command, proc| match command {
199 Command::Print { style: _, value } => {
200 let entity = &*proc.get_call_stack().last().unwrap().entity.borrow();
201 if let Some(value) = value {
202 print!("{entity:?} > {value:?}\r\n");
203 update_flag.set(true);
204 }
205 key.complete(Ok(()));
206 CommandStatus::Handled
207 }
208 _ => CommandStatus::UseDefault { key, command },
209 }))
210 },
211 request: {
212 let update_flag = update_flag.clone();
213 let input_queries = input_queries.clone();
214 Some(Rc::new(move |_, key, request, proc| match request {
215 Request::Input { prompt } => {
216 let entity = &*proc.get_call_stack().last().unwrap().entity.borrow();
217 input_queries.borrow_mut().push_back((format!("{entity:?} {prompt:?} > "), key));
218 update_flag.set(true);
219 RequestStatus::Handled
220 }
221 _ => RequestStatus::UseDefault { key, request },
222 }))
223 },
224 });
225
226 let system = Rc::new(StdSystem::new_sync(server, Some(project_name), config, clock.clone()));
227 let mut idle_sleeper = IdleAction::new(YIELDS_BEFORE_IDLE_SLEEP, Box::new(|| thread::sleep(IDLE_SLEEP_TIME)));
228 print!("public id: {}\r\n", system.get_public_id());
229
230 let mut env = match get_env(role, system) {
231 Ok(x) => x,
232 Err(e) => {
233 print!("error loading project: {e:?}\r\n");
234 return;
235 }
236 };
237 env.mutate(|mc, env| env.proj.borrow_mut(mc).input(mc, Input::Start));
238
239 let mut next_collect = clock.read(Precision::Medium) + COLLECT_INTERVAL;
240 let mut input_sequence = Vec::with_capacity(16);
241 let in_input_mode = || !input_queries.borrow().is_empty();
242 'program: loop {
243 debug_assert_eq!(input_sequence.len(), 0);
244 while event::poll(Duration::from_secs(0)).unwrap() {
245 match event::read().unwrap() {
246 Event::Key(key) => match key.code {
247 RawKeyCode::Char('c') if key.modifiers == RawKeyModifiers::CONTROL => break 'program,
248 RawKeyCode::Esc => input_sequence.push(Input::Stop),
249 RawKeyCode::Char(ch) => match in_input_mode() {
250 true => { input_value.push(ch); update_flag.set(true); }
251 false => input_sequence.push(Input::KeyDown { key: KeyCode::Char(ch.to_ascii_lowercase()) }),
252 }
253 RawKeyCode::Backspace => if in_input_mode() && input_value.pop().is_some() { update_flag.set(true) }
254 RawKeyCode::Enter => if let Some((_, res_key)) = input_queries.borrow_mut().pop_front() {
255 res_key.complete(Ok(SimpleValue::Text(mem::take(&mut input_value)).into()));
256 update_flag.set(true);
257 }
258 RawKeyCode::Up => if !in_input_mode() { input_sequence.push(Input::KeyDown { key: KeyCode::Up }) }
259 RawKeyCode::Down => if !in_input_mode() { input_sequence.push(Input::KeyDown { key: KeyCode::Down }) }
260 RawKeyCode::Left => if !in_input_mode() { input_sequence.push(Input::KeyDown { key: KeyCode::Left }) }
261 RawKeyCode::Right => if !in_input_mode() { input_sequence.push(Input::KeyDown { key: KeyCode::Right }) }
262 _ => (),
263 }
264 Event::Resize(c, r) => {
265 term_size = (c, r);
266 update_flag.set(true);
267 }
268 _ => (),
269 }
270 }
271
272 env.mutate(|mc, env| {
273 let mut proj = env.proj.borrow_mut(mc);
274 for input in input_sequence.drain(..) { proj.input(mc, input); }
275 for _ in 0..STEPS_PER_IO_ITER {
276 let res = proj.step(mc);
277 if let ProjectStep::Error { error, proc: _ } = &res {
278 print!("\r\n>>> runtime error: {:?}\r\n\r\n", error.cause);
279 }
280 idle_sleeper.consume(&res);
281 }
282 });
283 if clock.read(Precision::Low) > next_collect {
284 env.collect_all();
285 next_collect = clock.read(Precision::Medium) + COLLECT_INTERVAL;
286 }
287
288 if update_flag.get() {
289 update_flag.set(false);
290
291 queue!(stdout(),
292 cursor::SavePosition,
293 cursor::MoveTo(0, term_size.1 - 1),
294 terminal::Clear(ClearType::CurrentLine)).unwrap();
295 let queries = input_queries.borrow();
296 if let Some((query, _)) = queries.front() {
297 queue!(stdout(),
298 SetForegroundColor(Color::Blue),
299 Print(query),
300 ResetColor,
301 Print(&input_value)).unwrap();
302 }
303 queue!(stdout(), cursor::RestorePosition).unwrap();
304 stdout().flush().unwrap();
305 }
306 }
307
308 execute!(stdout(), terminal::Clear(ClearType::CurrentLine)).unwrap();
309}
310fn run_proj_non_tty<C: CustomTypes<StdSystem<C>>>(project_name: &str, server: CompactString, role: &ast::Role, overrides: Config<C, StdSystem<C>>, clock: Arc<Clock>) {
311 let config = overrides.fallback(&Config {
312 request: None,
313 command: Some(Rc::new(move |_, key, command, proc| match command {
314 Command::Print { style: _, value } => {
315 let entity = &*proc.get_call_stack().last().unwrap().entity.borrow();
316 if let Some(value) = value { println!("{entity:?} > {value:?}") }
317 key.complete(Ok(()));
318 CommandStatus::Handled
319 }
320 _ => CommandStatus::UseDefault { key, command },
321 })),
322 });
323
324 let system = Rc::new(StdSystem::new_sync(server, Some(project_name), config, clock.clone()));
325 let mut idle_sleeper = IdleAction::new(YIELDS_BEFORE_IDLE_SLEEP, Box::new(|| thread::sleep(IDLE_SLEEP_TIME)));
326 println!(">>> public id: {}\n", system.get_public_id());
327
328 let mut env = match get_env(role, system) {
329 Ok(x) => x,
330 Err(e) => {
331 println!(">>> error loading project: {e:?}");
332 return;
333 }
334 };
335 env.mutate(|mc, env| env.proj.borrow_mut(mc).input(mc, Input::Start));
336
337 let mut next_collect = clock.read(Precision::Medium) + COLLECT_INTERVAL;
338 loop {
339 env.mutate(|mc, env| {
340 let mut proj = env.proj.borrow_mut(mc);
341 for _ in 0..STEPS_PER_IO_ITER {
342 let res = proj.step(mc);
343 if let ProjectStep::Error { error, proc: _ } = &res {
344 println!("\n>>> runtime error: {:?}\n", error.cause);
345 }
346 idle_sleeper.consume(&res);
347 }
348 });
349 if clock.read(Precision::Low) > next_collect {
350 env.collect_all();
351 next_collect = clock.read(Precision::Medium) + COLLECT_INTERVAL;
352 }
353 }
354}
355fn run_server<C: CustomTypes<StdSystem<C>>>(nb_server: CompactString, addr: CompactString, port: u16, overrides: Config<C, StdSystem<C>>, clock: Arc<Clock>, syscalls: &[SyscallMenu]) {
356 println!(r#"connect from {DEFAULT_EDITOR_URL}/?extensions=["http://{addr}:{port}/extension.js"]"#);
357
358 let extension = ExtensionArgs {
359 server: &format!("http://{addr}:{port}"),
360 syscalls,
361 omitted_elements: &["thumbnail", "pentrails", "history", "replay"],
362 pull_interval: Duration::from_millis(250),
363 }.render();
364
365 enum ServerCommand {
366 SetProject(String),
367 Input(Input),
368 }
369
370 let (proj_sender, proj_receiver) = channel();
371
372 struct State {
373 extension: String,
374 running: AtomicBool,
375 current_proj: Mutex<String>,
376 proj_sender: Mutex<Sender<ServerCommand>>,
377 output: Mutex<String>,
378 errors: Mutex<Vec<ErrorSummary>>,
379 }
380 let state = web::Data::new(State {
381 extension,
382 running: AtomicBool::new(true),
383 current_proj: Mutex::new(EMPTY_PROJECT.into()),
384 proj_sender: Mutex::new(proj_sender),
385 output: Mutex::new(String::with_capacity(1024)),
386 errors: Mutex::new(Vec::with_capacity(8)),
387 });
388
389 macro_rules! tee_println {
390 ($state:expr => $($t:tt)*) => {{
391 let content = format!($($t)*);
392 if let Some(state) = $state {
393 let mut output = state.output.lock().unwrap();
394 output.push_str(&content);
395 output.push('\n');
396 }
397 println!("{content}");
398 }}
399 }
400
401 let weak_state = Arc::downgrade(&state);
402 let config = overrides.fallback(&Config {
403 request: None,
404 command: Some(Rc::new(move |_, key, command, proc| match command {
405 Command::Print { style: _, value } => {
406 let entity = &*proc.get_call_stack().last().unwrap().entity.borrow();
407 if let Some(value) = value { tee_println!(weak_state.upgrade() => "{entity:?} > {value:?}") }
408 key.complete(Ok(()));
409 CommandStatus::Handled
410 }
411 _ => CommandStatus::UseDefault { key, command },
412 })),
413 });
414 let system = Rc::new(StdSystem::new_sync(nb_server, Some("native-server"), config, clock.clone()));
415 let mut idle_sleeper = IdleAction::new(YIELDS_BEFORE_IDLE_SLEEP, Box::new(|| thread::sleep(IDLE_SLEEP_TIME)));
416 println!("public id: {}", system.get_public_id());
417
418 #[tokio::main(flavor = "multi_thread", worker_threads = 1)]
419 async fn run_http(state: web::Data<State>, port: u16) {
420 #[get("/extension.js")]
421 async fn get_extension(state: web::Data<State>) -> impl Responder {
422 HttpResponse::Ok().content_type("text/javascript").body(state.extension.clone())
423 }
424
425 #[post("/pull")]
426 async fn pull_status(state: web::Data<State>) -> impl Responder {
427 let running = state.running.load(MemoryOrder::Relaxed);
428 let output = mem::take(&mut *state.output.lock().unwrap());
429 let errors = mem::take(&mut *state.errors.lock().unwrap());
430
431 HttpResponse::Ok().content_type("application/json").body(serde_json::to_string(&Status { running, output, errors }).unwrap())
432 }
433
434 #[post("/project")]
435 async fn set_project(state: web::Data<State>, body: web::Bytes) -> impl Responder {
436 match String::from_utf8(body.to_vec()) {
437 Ok(content) => {
438 state.proj_sender.lock().unwrap().send(ServerCommand::SetProject(content)).unwrap();
439 HttpResponse::Ok().content_type("text/plain").body("loaded project")
440 }
441 Err(_) => HttpResponse::BadRequest().content_type("text/plain").body("project was not valid utf8"),
442 }
443 }
444
445 #[get("/project")]
446 async fn get_project(state: web::Data<State>) -> impl Responder {
447 let proj = state.current_proj.lock().unwrap().clone();
448 HttpResponse::Ok().content_type("text/xml").append_header(("Content-Disposition", "attachment; filename=\"project.xml\"")).body(proj)
449 }
450
451 #[post("/input")]
452 async fn send_input(state: web::Data<State>, input: web::Bytes) -> impl Responder {
453 let input = match String::from_utf8(input.to_vec()) {
454 Ok(input) => match input.as_str() {
455 "start" => Input::Start,
456 "stop" => Input::Stop,
457 _ => return HttpResponse::BadRequest().content_type("text/plain").body(format!("unknown input: {input:?}")),
458 }
459 Err(_) => return HttpResponse::BadRequest().content_type("text/plain").body("input was not valid utf8")
460 };
461 state.proj_sender.lock().unwrap().send(ServerCommand::Input(input)).unwrap();
462 HttpResponse::Ok().content_type("text/plain").body("sent input")
463 }
464
465 #[post("/toggle-paused")]
466 async fn toggle_paused(state: web::Data<State>) -> impl Responder {
467 state.running.fetch_xor(true, MemoryOrder::Relaxed);
468 HttpResponse::Ok().content_type("text/plain").body("toggled pause state")
469 }
470
471 HttpServer::new(move || {
472 App::new()
473 .wrap(Cors::permissive())
474 .app_data(web::PayloadConfig::new(MAX_REQUEST_SIZE_BYTES))
475 .app_data(state.clone())
476 .service(get_extension)
477 .service(pull_status)
478 .service(set_project)
479 .service(get_project)
480 .service(send_input)
481 .service(toggle_paused)
482 })
483 .workers(1)
484 .bind(("localhost", port)).unwrap().run().await.unwrap();
485 }
486 let weak_state = Arc::downgrade(&state);
487 thread::spawn(move || run_http(state, port));
488
489 let (_, empty_role) = open_project(EMPTY_PROJECT, None).unwrap_or_else(|_| crash!(666: "default project failed to load"));
490 let mut env = get_env(&empty_role, system.clone()).unwrap();
491
492 let mut next_collect = clock.read(Precision::Medium) + COLLECT_INTERVAL;
493 'program: loop {
494 'input: loop {
495 match proj_receiver.try_recv() {
496 Ok(command) => match command {
497 ServerCommand::SetProject(content) => match open_project(&content, None) {
498 Ok((proj_name, role)) => {
499 let mut state = weak_state.upgrade().unwrap();
500 tee_println!(Some(&mut state) => "\n>>> loaded project '{proj_name}'\n");
501 match get_env(&role, system.clone()) {
502 Ok(x) => {
503 env = x;
504 *state.current_proj.lock().unwrap() = content;
505 }
506 Err(e) => tee_println!(Some(&mut state) => "\n>>> project load error: {e:?}\n>>> keeping previous project...\n"),
507 }
508 }
509 Err(e) => match e {
510 OpenProjectError::ParseError { error } if error.location.collab_id.is_some() => {
511 let mut state = weak_state.upgrade().unwrap();
512 let cause = format_compact!("{:?}", error.kind);
513 state.errors.lock().unwrap().push(ErrorSummary {
514 cause: cause.clone(),
515 entity: error.location.entity.unwrap_or_default(),
516 globals: vec![],
517 fields: vec![],
518 trace: vec![TraceEntry { location: error.location.collab_id.unwrap(), locals: vec![] }], });
520 tee_println!(Some(&mut state) => "\n>>> project load error: {cause:?}\n>>> see red error comments...\n>>> keeping previous project...\n");
521 }
522 _ => tee_println!(weak_state.upgrade() => "\n>>> project load error: {e:?}\n>>> keeping previous project...\n"),
523 }
524 }
525 ServerCommand::Input(input) => {
526 if let Input::Start = &input {
527 if let Some(state) = weak_state.upgrade() {
528 state.running.store(true, MemoryOrder::Relaxed);
529 }
530 }
531 env.mutate(|mc, env| env.proj.borrow_mut(mc).input(mc, input));
532 }
533 }
534 Err(TryRecvError::Disconnected) => break 'program,
535 Err(TryRecvError::Empty) => break 'input,
536 }
537 }
538 if !weak_state.upgrade().map(|state| state.running.load(MemoryOrder::Relaxed)).unwrap_or(true) {
539 idle_sleeper.trigger();
540 continue;
541 }
542
543 env.mutate(|mc, env| {
544 let mut proj = env.proj.borrow_mut(mc);
545 for _ in 0..STEPS_PER_IO_ITER {
546 let res = proj.step(mc);
547 match &res {
548 ProjectStep::Error { error, proc } => if let Some(state) = weak_state.upgrade() {
549 let summary = ErrorSummary::extract(error, proc, &env.locs);
550
551 tee_println!(Some(&state) => "\n>>> runtime error in entity {:?}: {:?}\n>>> see red error comments...\n", summary.entity, summary.cause);
552
553 state.errors.lock().unwrap().push(summary);
554 }
555 ProjectStep::Pause => if let Some(state) = weak_state.upgrade() {
556 state.running.store(false, MemoryOrder::Relaxed);
557 break
558 }
559 _ => (),
560 }
561 idle_sleeper.consume(&res);
562 }
563 });
564 if clock.read(Precision::Low) > next_collect {
565 env.collect_all();
566 next_collect = clock.read(Precision::Medium) + COLLECT_INTERVAL;
567 }
568 }
569}
570
571pub fn run<C: CustomTypes<StdSystem<C>>>(mode: Mode, config: Config<C, StdSystem<C>>, syscalls: &[SyscallMenu]) {
573 let utc_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
574 let clock = Arc::new(Clock::new(utc_offset, Some(Precision::Medium)));
575 let clock_clone = clock.clone();
576 thread::spawn(move || loop {
577 thread::sleep(CLOCK_INTERVAL);
578 clock_clone.update();
579 });
580
581 match mode {
582 Mode::Run { src, role, server } => {
583 let content = read_file(&src).unwrap_or_else(|_| crash!(1: "failed to read file '{src}'"));
584 let (project_name, role) = open_project(&content, role.as_deref()).unwrap_or_else(|e| crash!(2: "{e}"));
585
586 if stdout().is_tty() {
587 run_proj_tty(&project_name, server, &role, config, clock);
588 } else {
589 run_proj_non_tty(&project_name, server, &role, config, clock);
590 }
591 }
592 Mode::Dump { src, role } => {
593 let content = read_file(&src).unwrap_or_else(|_| crash!(1: "failed to read file '{src}'"));
594 let (_, role) = open_project(&content, role.as_deref()).unwrap_or_else(|e| crash!(2: "{e}"));
595
596 let (bytecode, _, _, _) = ByteCode::compile(&role).unwrap();
597 println!("instructions:");
598 bytecode.dump_code(&mut std::io::stdout().lock()).unwrap();
599 println!("\ndata:");
600 bytecode.dump_data(&mut std::io::stdout().lock()).unwrap();
601 println!("\ntotal size: {}", bytecode.total_size());
602 }
603 Mode::Start { server, addr, port } => {
604 run_server(server, addr, port, config, clock, syscalls);
605 }
606 }
607}