This is a walkthrough for using the Rust language to parse a simple ActionScript 3 program and visit a few of its syntactic structures.
It is also useful to have a symbol solver in the future. I can't tell whether I'll provide one given that there is Flex MXML to handle and it should be huge. Or are just the Flex SWCs huge, and MXML itself is simple?
Enhancements
- ASDoc: ASDoc main body and tags now have their respective source location in their syntactic structures.
- Error recovery: now you receive a syntactic structure and diagnostics together. Currently, except for a few cases, when an error occurs at the end of file (EOF), it may not recover from that error and produce only diagnostics.
- Bugs: fixed several bugs or missing details within the new parser.
- Include directive: circular
include
directives are now detected. For example, include "./Main.as"
from Main.as
itself now reports a circular inclusion error and does not proceed into a runtime cycle.
Note about annotatable directives
The public
and internal
namespaces are treated as valid everywhere, but you can report a verify error where public
has no enclosing package in the scope chain.
Install Rust
See https://www.rust-lang.org/tools/install
Definitions:
- "Cargo" is the package manager that comes within the Rust installation.
- "Rustup" is their installation manager.
Getting Started
See https://github.com/hydroper/as3parser/blob/master/docs/getting-started.md
Test utility
See https://github.com/hydroper/as3parser/tree/master/crates/parser_test
Examples for running it:
cargo run -- --source-path path/to/File.as --file-log
It outputs two files, File.ast.json
and File.diag
.
ASDoc Example
Task: print all the ASDoc @private
and main body tags belonging to variable definitions within a class block within a package block.
Create the project through running this in the command line:
cargo new --bin asdoc-example
cd asdoc-example
cargo add as3_parser
Write this to src/main.rs
:
use std::env;
use as3_parser::ns::*;
fn main() {
// Define source path
let source_path = env::current_dir().unwrap().join("../Example.as").to_string_lossy().into_owned();
// Read source content
let source_content = include_str!("../Example.as").to_owned();
// Create compilation unit
let compilation_unit = CompilationUnit::new(Some(source_path), source_content, &CompilerOptions::new());
// Parse program
if let Some(program) = ParserFacade::parse_program(&compilation_unit) {
visit_program(&program);
}
// Report diagnostics
compilation_unit.sort_diagnostics();
for diagnostic in compilation_unit.nested_diagnostics() {
println!("{}", diagnostic.format_english());
}
}
fn visit_program(program: &Rc<Program>) {
for package in program.packages.iter() {
for directive in package.block.directives.iter() {
// directive: Rc<Directive>
match directive.as_ref() {
Directive::ClassDefinition(defn) => {
visit_class(&defn);
},
_ => {},
}
}
}
}
fn visit_class(defn: &ClassDefinition) {
for directive in defn.block.directives.iter() {
if let Directive::VariableDefinition(defn) = directive.as_ref() {
// Print any found main body and @private tags
if let Some(asdoc) = &defn.asdoc {
print_asdoc(asdoc);
}
}
}
}
fn print_asdoc(asdoc: &Rc<AsDoc>) {
if let Some((text, loc)) = &asdoc.main_body {
println!("Found main body at {}:{}\n\n{}\n\n", loc.first_line_number(), loc.first_column() + 1, text);
}
for (tag, loc) in &asdoc.tags {
if matches!(tag, AsDocTag::Private) {
println!("Found @private tag at {}:{}\n\n", loc.first_line_number(), loc.first_column() + 1);
}
}
}
Write this to Example.as
in the project root:
package q1 {
public namespace q1ns
public class C1 {
/**
* @private
* Comment for q1.q1ns::x
*/
q1.q1ns var x
}
}
Run it with:
cargo run
It should produce:
Found main body at 6:12
Comment for q1.q1ns::x
Found @private tag at 5:12
And no errors or warnings.
Rust Tips
In Rust you may sometimes wonder if you need to insert a &
operator (called borrow) or if you need to insert a .as_ref()
, .clone()
, or .borrow()
call. It's very common in Rust to do these things, and using the rust-analyzer extension in your editor will help with autocompletion and solving Rust issues.
In Rust you may sometimes use Cell
(for stack resources) or RefCell
(for heap resources) for mutable variables, Rc
for reference counting (similiar to shared_ptr
in C++), among others,
Finally
Check out https://github.com/hydroper/as3parser?tab=readme-ov-file#actionscript-3-parser for full information.
Have fun and thanks for reading