1use crate::error::{Error, ErrorSet};
2use crate::internal::as2::hir::constant_folder::fold_constants;
3use crate::internal::as2::hir::optimize_virtual_properties::optimize_virtual_properties;
4use crate::internal::as2::hir::register_promoter::promote_variables_to_registers;
5use crate::internal::as2::hir::scope::Scope;
6use crate::internal::as2::lexer::Lexer;
7use crate::internal::as2::resolver::resolve_hir;
8use crate::internal::as2::{hir, parser, type_path_to_file_path};
9use crate::internal::as2_codegen::{class_to_actions, interface_to_actions, script_to_actions};
10use crate::internal::as2_pcode::Actions;
11use crate::internal::span::Span;
12use crate::provider::SourceProvider;
13use crate::sources::SourceSet;
14use indexmap::IndexSet;
15use serde::Serialize;
16
17#[derive(Debug, Clone, Serialize)]
18pub struct SwfOptions {
19 pub(crate) frame_rate: f32,
20 pub(crate) stage_x_min: f64,
21 pub(crate) stage_y_min: f64,
22 pub(crate) stage_x_max: f64,
23 pub(crate) stage_y_max: f64,
24 pub(crate) use_network_sandbox: bool,
25}
26
27impl Default for SwfOptions {
28 fn default() -> Self {
29 Self {
30 frame_rate: 24.0,
31 stage_x_min: 0.0,
32 stage_y_min: 0.0,
33 stage_x_max: 100.0,
34 stage_y_max: 100.0,
35 use_network_sandbox: false,
36 }
37 }
38}
39
40impl SwfOptions {
41 pub fn with_frame_rate(mut self, frame_rate: f32) -> Self {
42 self.frame_rate = frame_rate;
43 self
44 }
45
46 pub fn with_stage_size(
47 mut self,
48 stage_x_min: f64,
49 stage_y_min: f64,
50 stage_x_max: f64,
51 stage_y_max: f64,
52 ) -> Self {
53 self.stage_x_min = stage_x_min;
54 self.stage_y_min = stage_y_min;
55 self.stage_x_max = stage_x_max;
56 self.stage_y_max = stage_y_max;
57 self
58 }
59
60 pub fn with_network_sandbox(mut self, use_network_sandbox: bool) -> Self {
61 self.use_network_sandbox = use_network_sandbox;
62 self
63 }
64}
65
66#[derive(Debug, Clone, Serialize)]
67pub struct CompileOptions {
68 pub(crate) swf_version: u8,
69 pub(crate) optimizations: OptimizationOptions,
70}
71
72impl Default for CompileOptions {
73 fn default() -> Self {
74 Self {
75 swf_version: 15,
76 optimizations: OptimizationOptions::full(),
77 }
78 }
79}
80
81impl CompileOptions {
82 pub fn with_swf_version(mut self, swf_version: u8) -> Self {
83 self.swf_version = swf_version;
84 self
85 }
86
87 pub fn with_optimizations(mut self, optimizations: OptimizationOptions) -> Self {
88 self.optimizations = optimizations;
89 self
90 }
91}
92
93#[derive(Debug, Serialize)]
94pub struct Program {
95 pub(crate) initial_script: Vec<hir::StatementKind>,
96 pub(crate) interfaces: Vec<hir::Interface>,
97 pub(crate) classes: Vec<hir::Class>,
98 pub(crate) custom_pcodes: Vec<Actions>,
99 pub(crate) compile_options: CompileOptions,
100}
101
102impl Program {
103 pub(crate) fn optimize(&mut self) {
104 optimize_virtual_properties(self);
105 if self
106 .compile_options
107 .optimizations
108 .promote_variables_to_registers
109 && self.compile_options.swf_version >= 7
110 {
111 promote_variables_to_registers(self);
112 }
113 }
114
115 pub fn compile(self) -> CompiledProgram {
116 let initializer = if self.initial_script.is_empty() {
117 None
118 } else {
119 Some(script_to_actions(&self.initial_script))
120 };
121 let mut extra_modules = vec![];
122 for interface in self.interfaces.into_iter().rev() {
123 let actions = interface_to_actions(&interface);
124 extra_modules.push((interface.name, actions));
125 }
126 for class in self.classes.into_iter().rev() {
127 let actions = class_to_actions(&self.compile_options, &class);
128 extra_modules.push((class.name, actions));
129 }
130 CompiledProgram {
131 initializer,
132 extra_modules,
133 compile_options: self.compile_options,
134 custom_pcodes: self.custom_pcodes,
135 }
136 }
137}
138
139#[derive(Serialize)]
140pub struct CompiledProgram {
141 pub(crate) initializer: Option<Actions>,
142 pub(crate) extra_modules: Vec<(String, Actions)>,
143 pub(crate) compile_options: CompileOptions,
144 pub(crate) custom_pcodes: Vec<Actions>,
145}
146
147impl CompiledProgram {
148 pub fn to_swf(&self, swf_options: &SwfOptions) -> swf::error::Result<Vec<u8>> {
149 crate::swf::pcode_to_swf(self, swf_options)
150 }
151}
152
153#[derive(Debug, Clone, Serialize)]
154pub struct OptimizationOptions {
155 fold_constants: bool,
156 promote_variables_to_registers: bool,
157}
158
159impl OptimizationOptions {
160 pub fn full() -> Self {
161 Self {
162 fold_constants: true,
163 promote_variables_to_registers: true,
164 }
165 }
166
167 pub fn none() -> Self {
168 Self {
169 fold_constants: false,
170 promote_variables_to_registers: false,
171 }
172 }
173
174 pub fn with_fold_constants(mut self, fold_constants: bool) -> Self {
175 self.fold_constants = fold_constants;
176 self
177 }
178
179 pub fn fold_constants(&self) -> bool {
180 self.fold_constants
181 }
182
183 pub fn with_promote_variables_to_registers(
184 mut self,
185 promote_variables_to_registers: bool,
186 ) -> Self {
187 self.promote_variables_to_registers = promote_variables_to_registers;
188 self
189 }
190
191 pub fn promote_variables_to_registers(&self) -> bool {
192 self.promote_variables_to_registers
193 }
194}
195
196pub struct ProgramBuilder<P> {
197 provider: P,
198 scripts: Vec<String>,
199 pcodes: Vec<String>,
200 classes: Vec<String>,
201 compile_options: CompileOptions,
202}
203
204impl<P> ProgramBuilder<P> {
205 pub fn add_script(&mut self, path: &str) {
206 self.scripts.push(path.to_owned());
207 }
208
209 pub fn add_pcode(&mut self, path: &str) {
210 self.pcodes.push(path.to_owned());
211 }
212
213 pub fn add_class(&mut self, path: &str) {
214 self.classes.push(path.to_owned());
215 }
216
217 pub fn with_compile_options(mut self, compile_options: CompileOptions) -> Self {
218 self.compile_options = compile_options;
219 self
220 }
221}
222
223impl<P: SourceProvider> ProgramBuilder<P> {
224 pub fn new(provider: P) -> Self {
225 Self {
226 provider,
227 scripts: vec![],
228 pcodes: vec![],
229 classes: vec![],
230 compile_options: CompileOptions::default(),
231 }
232 }
233
234 pub fn build(self) -> Result<Program, Error> {
235 let mut root_scope = Scope::default();
236 let mut initial_script = vec![];
237 let mut interfaces = vec![];
238 let mut classes = vec![];
239 let mut errors = ErrorSet::new();
240 let mut loaded_classes = self.classes.iter().cloned().collect();
241 let mut pending_classes = self.classes;
242 let mut custom_pcodes = vec![];
243 let known_script_paths = self.scripts.iter().cloned().collect();
244 let mut source_set = SourceSet::new();
245
246 #[expect(clippy::too_many_arguments)]
247 fn load_actionscript<P: SourceProvider>(
248 provider: &P,
249 errors: &mut ErrorSet,
250 loaded_classes: &mut IndexSet<String>,
251 pending_classes: &mut Vec<String>,
252 path: &str,
253 type_name: &str,
254 is_script: bool,
255 known_script_paths: &IndexSet<String>,
256 compile_options: &CompileOptions,
257 source_set: &mut SourceSet,
258 ) -> Option<hir::Document> {
259 let source = match source_set.get_or_load(path.to_owned(), provider) {
260 Ok(source) => source,
261 Err(e) => {
262 errors.add_io_error(path, e);
263 return None;
264 }
265 };
266 let tokens = Lexer::new(&source.source, source.file_id).into_vec();
267 let ast = match parser::parse_document(&tokens) {
268 Ok(ast) => ast,
269 Err(e) => {
270 errors.add_parsing_error(e.into());
271 return None;
272 }
273 };
274 let (mut hir, hir_errors, dependencies) = resolve_hir(
275 provider,
276 source_set,
277 ast,
278 is_script,
279 type_name,
280 known_script_paths,
281 );
282 for error in hir_errors {
283 errors.add_parsing_error(error);
284 }
285 for name in dependencies {
286 if loaded_classes.insert(name.to_owned()) {
287 pending_classes.push(name);
288 }
289 }
290 if compile_options.optimizations.fold_constants {
291 while fold_constants(&mut hir) {
292 }
294 }
295 Some(hir)
296 }
297
298 fn load_pcode<P: SourceProvider>(
299 provider: &P,
300 errors: &mut ErrorSet,
301 path: &str,
302 custom_pcodes: &mut Vec<Actions>,
303 source_set: &mut SourceSet,
304 ) {
305 let source = match source_set.get_or_load(path.to_owned(), provider) {
306 Ok(source) => source,
307 Err(e) => {
308 errors.add_io_error(path, e);
309 return;
310 }
311 };
312 let tokens =
313 crate::internal::as2_pcode::Lexer::new(&source.source, source.file_id).into_vec();
314 match crate::internal::as2_pcode::parse_actions(&tokens) {
315 Ok(actions) => custom_pcodes.push(actions),
316 Err(e) => errors.add_parsing_error(e.into()),
317 }
318 }
319
320 for path in self.pcodes {
321 load_pcode(
322 &self.provider,
323 &mut errors,
324 &path,
325 &mut custom_pcodes,
326 &mut source_set,
327 );
328 }
329
330 for path in self.scripts {
331 if let Some(document) = load_actionscript(
332 &self.provider,
333 &mut errors,
334 &mut loaded_classes,
335 &mut pending_classes,
336 &path,
337 "",
338 true,
339 &known_script_paths,
340 &self.compile_options,
341 &mut source_set,
342 ) && let hir::Document::Script { statements, scope } = document
343 {
344 root_scope.defined_variables.extend(scope.defined_variables);
345 root_scope
346 .referenced_variables
347 .extend(scope.referenced_variables);
348 root_scope.could_reference_anything |= scope.could_reference_anything;
349 initial_script.extend(statements);
350 }
351 }
352
353 let mut entry_point_class = vec![];
354
355 while let Some(type_name) = pending_classes.pop() {
356 let filename = type_path_to_file_path(&type_name);
357 if let Some(document) = load_actionscript(
358 &self.provider,
359 &mut errors,
360 &mut loaded_classes,
361 &mut pending_classes,
362 &filename,
363 &type_name,
364 false,
365 &known_script_paths,
366 &self.compile_options,
367 &mut source_set,
368 ) {
369 match document {
370 hir::Document::Interface(interface) => {
371 interfaces.push(interface);
372 }
373 hir::Document::Class(class) => {
374 if let Some(main) = class.functions.get("main")
375 && main.is_static
376 {
377 entry_point_class.push(class.name.clone());
378 }
379 classes.push(*class);
380 }
381 _ => {}
382 }
383 }
384 }
385
386 match entry_point_class.len() {
387 0 => {} 1 => initial_script.push(call_main_method(entry_point_class.first().unwrap())),
389 _ => errors.add_misc_error(format!(
390 "Conflicting entry points found on classes: {}",
391 entry_point_class.join(", ")
392 )),
393 };
394
395 errors.error_unless_empty(source_set)?;
396
397 let mut program = Program {
398 initial_script,
399 interfaces,
400 classes,
401 custom_pcodes,
402 compile_options: self.compile_options,
403 };
404 program.optimize();
405 Ok(program)
406 }
407}
408
409fn call_main_method(class: &str) -> hir::StatementKind {
410 let mut path = class.split(".").collect::<Vec<&str>>();
411 path.push("main");
412 let path: Vec<String> = path.into_iter().map(|s| s.to_owned()).collect();
413
414 let mut name = None;
415 for part in path.into_iter() {
416 if let Some(prev) = name.take() {
417 name = Some(hir::Expr::new(
418 Span::default(),
419 hir::ExprKind::Field(
420 Box::new(prev),
421 Box::new(hir::Expr::new(
422 Span::default(),
423 hir::ExprKind::Constant(hir::ConstantKind::String(part)),
424 )),
425 ),
426 ));
427 } else {
428 name = Some(hir::Expr::new(
429 Span::default(),
430 hir::ExprKind::Constant(hir::ConstantKind::Identifier(part)),
431 ));
432 }
433 }
434 let name = name.unwrap();
436
437 hir::StatementKind::Expr(hir::Expr::new(
438 Span::default(),
439 hir::ExprKind::Call {
440 name: Box::new(name),
441 args: vec![hir::Expr::new(
442 Span::default(),
443 hir::ExprKind::Constant(hir::ConstantKind::Identifier("this".to_owned())),
444 )],
445 },
446 ))
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452 use crate::provider::FileSystemSourceProvider;
453
454 #[test]
455 fn test_all_samples() {
456 insta::glob!("../../../samples/as2", "**/*.as", |path| {
457 let mut builder = ProgramBuilder::new(FileSystemSourceProvider::with_root(
458 path.parent().unwrap().to_owned(),
459 ));
460 builder.add_script(path.file_name().unwrap().to_str().unwrap());
461 let parsed = builder.build();
462 insta::assert_yaml_snapshot!(parsed);
463 });
464 insta::glob!("../../../samples/as2_classes", "*.as", |path| {
465 let mut builder = ProgramBuilder::new(FileSystemSourceProvider::with_root(
466 path.parent().unwrap().to_owned(),
467 ));
468 builder.add_class(
469 path.file_name()
470 .unwrap()
471 .to_str()
472 .unwrap()
473 .strip_suffix(".as")
474 .unwrap(),
475 );
476 let parsed = builder.build();
477 insta::assert_yaml_snapshot!(parsed);
478 });
479 }
480
481 #[test]
482 fn test_fail_samples() {
483 insta::glob!("../../../samples/as2_errors", "**/*.as", |path| {
484 let mut builder = ProgramBuilder::new(FileSystemSourceProvider::with_root(
485 path.parent().unwrap().to_owned(),
486 ));
487 builder.add_script(path.file_name().unwrap().to_str().unwrap());
488 let parsed = builder.build().unwrap_err();
489 insta::assert_snapshot!(parsed);
490 });
491 }
492
493 #[test]
494 fn test_main_method_simple() {
495 assert_eq!(
496 call_main_method("foo"),
497 hir::StatementKind::Expr(hir::Expr::new(
498 Span::default(),
499 hir::ExprKind::Call {
500 name: Box::new(hir::Expr::new(
501 Span::default(),
502 hir::ExprKind::Field(
503 Box::new(hir::Expr::new(
504 Span::default(),
505 hir::ExprKind::Constant(hir::ConstantKind::Identifier(
506 "foo".to_owned()
507 ))
508 )),
509 Box::new(hir::Expr::new(
510 Span::default(),
511 hir::ExprKind::Constant(hir::ConstantKind::String(
512 "main".to_owned()
513 ))
514 ))
515 )
516 )),
517 args: vec![hir::Expr::new(
518 Span::default(),
519 hir::ExprKind::Constant(hir::ConstantKind::Identifier("this".to_owned()))
520 )]
521 }
522 ))
523 );
524 }
525
526 #[test]
527 fn test_main_method_path() {
528 assert_eq!(
529 call_main_method("foo.bar.baz"),
530 hir::StatementKind::Expr(hir::Expr::new(
531 Span::default(),
532 hir::ExprKind::Call {
533 name: Box::new(hir::Expr::new(
534 Span::default(),
535 hir::ExprKind::Field(
536 Box::new(hir::Expr::new(
537 Span::default(),
538 hir::ExprKind::Field(
539 Box::new(hir::Expr::new(
540 Span::default(),
541 hir::ExprKind::Field(
542 Box::new(hir::Expr::new(
543 Span::default(),
544 hir::ExprKind::Constant(
545 hir::ConstantKind::Identifier("foo".to_owned())
546 )
547 )),
548 Box::new(hir::Expr::new(
549 Span::default(),
550 hir::ExprKind::Constant(hir::ConstantKind::String(
551 "bar".to_owned()
552 ))
553 ))
554 )
555 )),
556 Box::new(hir::Expr::new(
557 Span::default(),
558 hir::ExprKind::Constant(hir::ConstantKind::String(
559 "baz".to_owned()
560 ))
561 )),
562 )
563 )),
564 Box::new(hir::Expr::new(
565 Span::default(),
566 hir::ExprKind::Constant(hir::ConstantKind::String(
567 "main".to_owned()
568 ))
569 ))
570 )
571 )),
572 args: vec![hir::Expr::new(
573 Span::default(),
574 hir::ExprKind::Constant(hir::ConstantKind::Identifier("this".to_owned()))
575 )]
576 }
577 ))
578 );
579 }
580}