├── .gitignore ├── tests └── parser │ ├── ASDoc.diag │ ├── E4X.diag │ ├── MXML1.diag │ ├── MXML2.diag │ ├── Misc.diag │ ├── Attributes.diag │ ├── Qualifier.diag │ ├── Configuration.diag │ ├── css │ ├── mediaQuery.diag │ ├── selectors.diag │ ├── mediaQuery.css │ ├── selectors.css │ ├── mediaQuery.tree │ └── selectors.tree │ ├── MXML2.mxml │ ├── Qualifier.as │ ├── Super.diag │ ├── Misc.as │ ├── Super.as │ ├── Attributes.as │ ├── E4X.as │ ├── ASDoc.as │ ├── README.md │ ├── MXML1.mxml │ ├── Configuration.as │ ├── MXML2.tree │ ├── MXML1.tree │ ├── ASDoc.tree │ ├── Super.tree │ └── Attributes.tree ├── examples └── asdoc │ ├── .gitignore │ ├── Example.as │ ├── Cargo.toml │ └── example.rs ├── demo ├── css │ ├── index.css │ └── variables.css ├── icons │ ├── error.png │ └── warning.png ├── ace │ ├── README.md │ ├── LICENSE │ └── mode-json.js ├── Cargo.toml ├── src │ └── lib.rs └── index.html ├── crates ├── parser │ ├── README.md │ ├── operator.rs │ ├── diagnostics.rs │ ├── tree │ │ ├── null_literal.rs │ │ ├── this_literal.rs │ │ ├── empty_statement.rs │ │ ├── boolean_literal.rs │ │ ├── string_literal.rs │ │ ├── import_meta.rs │ │ ├── throw_statement.rs │ │ ├── paren_expression.rs │ │ ├── super_statement.rs │ │ ├── block.rs │ │ ├── break_statement.rs │ │ ├── expression_statement.rs │ │ ├── return_statement.rs │ │ ├── continue_statement.rs │ │ ├── invalidated_node.rs │ │ ├── regexp_literal.rs │ │ ├── use_namespace_directive.rs │ │ ├── default_xml_namespace_statement.rs │ │ ├── while_statement.rs │ │ ├── with_statement.rs │ │ ├── unary_expression.rs │ │ ├── call_expression.rs │ │ ├── program.rs │ │ ├── vector_literal.rs │ │ ├── labeled_statement.rs │ │ ├── member_expression.rs │ │ ├── new_expression.rs │ │ ├── descendants_expression.rs │ │ ├── function_expression.rs │ │ ├── destructuring.rs │ │ ├── type_parameter.rs │ │ ├── do_statement.rs │ │ ├── binary_expression.rs │ │ ├── filter_expression.rs │ │ ├── if_statement.rs │ │ ├── package_definition.rs │ │ ├── sequence_expression.rs │ │ ├── directive_injection_node.rs │ │ ├── conditional_expression.rs │ │ ├── expression_with_type_arguments.rs │ │ ├── type_definition.rs │ │ ├── package_concat_directive.rs │ │ ├── namespace_definition.rs │ │ ├── computed_member_expression.rs │ │ ├── enum_definition.rs │ │ ├── include_directive.rs │ │ ├── interface_definition.rs │ │ ├── class_definition.rs │ │ ├── normal_configuration_directive.rs │ │ ├── array_literal.rs │ │ ├── assignment_expression.rs │ │ ├── configuration_directive.rs │ │ ├── super_expression.rs │ │ ├── try_statement.rs │ │ ├── optional_chaining_expression.rs │ │ ├── import_directive.rs │ │ ├── for_statement.rs │ │ ├── variable_definition.rs │ │ ├── switch_statement.rs │ │ ├── reserved_namespace_expression.rs │ │ ├── asdoc.rs │ │ ├── qualified_identifier.rs │ │ ├── xml_expression.rs │ │ ├── type_expression.rs │ │ ├── object_initializer.rs │ │ ├── function_definition.rs │ │ ├── directive.rs │ │ ├── mxml.rs │ │ ├── numeric_literal.rs │ │ └── attributes.rs │ ├── compilation_unit.rs │ ├── lib.rs │ ├── ns.rs │ ├── util │ │ ├── arena.rs │ │ ├── escaping.rs │ │ ├── character_reader.rs │ │ └── css.rs │ ├── parser.rs │ ├── parser │ │ ├── parser_error.rs │ │ ├── reserved_word.rs │ │ ├── character_validator.rs │ │ └── context.rs │ ├── Cargo.toml │ ├── util.rs │ ├── compilation_unit │ │ ├── comment.rs │ │ └── location.rs │ ├── operator │ │ ├── operator_precedence.rs │ │ └── operator.rs │ ├── diagnostics │ │ ├── diagnostic_kind.rs │ │ ├── diagnostics.rs │ │ └── diagnostics_english_resources.rs │ └── tree.rs └── parser_test │ ├── Cargo.toml │ └── main.rs ├── Cargo.toml ├── as3 └── promise │ ├── AggregateError.as │ └── PromiseHandler.as ├── README.md └── .github └── workflows └── demo.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /tests/parser/ASDoc.diag: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/parser/E4X.diag: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/parser/MXML1.diag: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/parser/MXML2.diag: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/parser/Misc.diag: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/parser/Attributes.diag: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/parser/Qualifier.diag: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/parser/Configuration.diag: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/parser/css/mediaQuery.diag: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/parser/css/selectors.diag: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/asdoc/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /demo/css/index.css: -------------------------------------------------------------------------------- 1 | @import url("./variables.css"); 2 | @import url("./general.css"); -------------------------------------------------------------------------------- /demo/icons/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hydroper/as3parser/HEAD/demo/icons/error.png -------------------------------------------------------------------------------- /demo/icons/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hydroper/as3parser/HEAD/demo/icons/warning.png -------------------------------------------------------------------------------- /tests/parser/MXML2.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | & Text & 4 | -------------------------------------------------------------------------------- /tests/parser/Qualifier.as: -------------------------------------------------------------------------------- 1 | * 2 | ; (q)::x 3 | o.* 4 | o.public 5 | o.public::x 6 | o.(q)::x 7 | o.q::x 8 | o.q::[k] 9 | o.@x; -------------------------------------------------------------------------------- /tests/parser/Super.diag: -------------------------------------------------------------------------------- 1 | C:\Users\mathe\UnsyncDocuments\as3parser\tests\parser\Super.as:1:1: Syntax error #1036: 'super' not allowed here -------------------------------------------------------------------------------- /crates/parser/README.md: -------------------------------------------------------------------------------- 1 | # ActionScript 3 Parser 2 | 3 | Refer to the [crate repository](https://github.com/hydroper/as3parser) for full details. 4 | -------------------------------------------------------------------------------- /demo/ace/README.md: -------------------------------------------------------------------------------- 1 | # Ace Editor 2 | 3 | The Ace Editor is not my property but a property of Ajax.org B.V. See the LICENSE file for license details. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/parser", 4 | "crates/parser_test", 5 | "demo", 6 | "examples/asdoc", 7 | ] 8 | resolver = "2" -------------------------------------------------------------------------------- /tests/parser/css/mediaQuery.css: -------------------------------------------------------------------------------- 1 | @media (application-dpi: 240) and (os-platform: "Windows"),,,,,,,,, { 2 | .myStyleName1 { 3 | fontSize: 30 4 | } 5 | } -------------------------------------------------------------------------------- /crates/parser/operator.rs: -------------------------------------------------------------------------------- 1 | //! Defines the ActionScript 3 operators. 2 | 3 | mod operator; 4 | pub use operator::*; 5 | mod operator_precedence; 6 | pub use operator_precedence::*; -------------------------------------------------------------------------------- /crates/parser/diagnostics.rs: -------------------------------------------------------------------------------- 1 | //! Defines the diagnostics produced by the parser. 2 | 3 | mod diagnostics; 4 | pub use diagnostics::*; 5 | mod diagnostic_kind; 6 | pub use diagnostic_kind::*; -------------------------------------------------------------------------------- /tests/parser/css/selectors.css: -------------------------------------------------------------------------------- 1 | .myStyleName1.anotherOne s|VGroup, #id1 { 2 | fontSize: 24 3 | } 4 | 5 | .myStyleName1[attr^="."] { 6 | color: red 7 | } 8 | 9 | #myId1 #myId2 #myId3 {} -------------------------------------------------------------------------------- /crates/parser/tree/null_literal.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct NullLiteral { 6 | pub location: Location, 7 | } -------------------------------------------------------------------------------- /crates/parser/tree/this_literal.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct ThisLiteral { 6 | pub location: Location, 7 | } -------------------------------------------------------------------------------- /crates/parser/tree/empty_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct EmptyStatement { 6 | pub location: Location, 7 | } -------------------------------------------------------------------------------- /crates/parser/tree/boolean_literal.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct BooleanLiteral { 6 | pub location: Location, 7 | pub value: bool, 8 | } -------------------------------------------------------------------------------- /crates/parser/tree/string_literal.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct StringLiteral { 6 | pub location: Location, 7 | pub value: String, 8 | } -------------------------------------------------------------------------------- /examples/asdoc/Example.as: -------------------------------------------------------------------------------- 1 | package q1 { 2 | public namespace q1ns 3 | public class C1 { 4 | /** 5 | * @private 6 | * Comment for q1.q1ns::x 7 | */ 8 | q1.q1ns var x 9 | } 10 | } -------------------------------------------------------------------------------- /crates/parser/compilation_unit.rs: -------------------------------------------------------------------------------- 1 | //! Defines the compilation unit, comments, and source locations. 2 | 3 | mod compilation_unit; 4 | pub use compilation_unit::*; 5 | mod comment; 6 | pub use comment::*; 7 | mod location; 8 | pub use location::*; -------------------------------------------------------------------------------- /crates/parser/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(decl_macro)] 2 | #![feature(try_blocks)] 3 | 4 | pub mod tree; 5 | pub mod compilation_unit; 6 | pub mod diagnostics; 7 | pub mod operator; 8 | pub mod parser; 9 | pub mod util; 10 | 11 | pub mod ns; -------------------------------------------------------------------------------- /crates/parser/tree/import_meta.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// The `import.meta` expression. 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub struct ImportMeta { 7 | pub location: Location, 8 | } -------------------------------------------------------------------------------- /crates/parser/tree/throw_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct ThrowStatement { 6 | pub location: Location, 7 | pub expression: Rc, 8 | } -------------------------------------------------------------------------------- /crates/parser/tree/paren_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct ParenExpression { 6 | pub location: Location, 7 | pub expression: Rc, 8 | } -------------------------------------------------------------------------------- /crates/parser/tree/super_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct SuperStatement { 6 | pub location: Location, 7 | pub arguments: Vec>, 8 | } -------------------------------------------------------------------------------- /examples/asdoc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "asdoc-example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | as3_parser = { path = "../../crates/parser", version = "1" } 8 | 9 | [[example]] 10 | name = "asdoc" 11 | path = "example.rs" -------------------------------------------------------------------------------- /crates/parser/tree/block.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// Block statement. 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub struct Block { 7 | pub location: Location, 8 | pub directives: Vec>, 9 | } -------------------------------------------------------------------------------- /crates/parser/tree/break_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct BreakStatement { 6 | pub location: Location, 7 | pub label: Option<(String, Location)>, 8 | } -------------------------------------------------------------------------------- /crates/parser/tree/expression_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct ExpressionStatement { 6 | pub location: Location, 7 | pub expression: Rc, 8 | } -------------------------------------------------------------------------------- /crates/parser/tree/return_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct ReturnStatement { 6 | pub location: Location, 7 | pub expression: Option>, 8 | } -------------------------------------------------------------------------------- /crates/parser/tree/continue_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct ContinueStatement { 6 | pub location: Location, 7 | pub label: Option<(String, Location)>, 8 | } -------------------------------------------------------------------------------- /crates/parser/tree/invalidated_node.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// Represents a construct that failed to parse. 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub struct InvalidatedNode { 7 | pub location: Location, 8 | } -------------------------------------------------------------------------------- /crates/parser/tree/regexp_literal.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct RegExpLiteral { 6 | pub location: Location, 7 | pub body: String, 8 | pub flags: String, 9 | } -------------------------------------------------------------------------------- /crates/parser/tree/use_namespace_directive.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct UseNamespaceDirective { 6 | pub location: Location, 7 | pub expression: Rc, 8 | } -------------------------------------------------------------------------------- /crates/parser/ns.rs: -------------------------------------------------------------------------------- 1 | //! The `ns` module is an union of all of the parser modules. 2 | 3 | pub use crate::tree::*; 4 | pub use crate::compilation_unit::*; 5 | pub use crate::diagnostics::*; 6 | pub use crate::operator::*; 7 | pub use crate::parser::*; 8 | pub use crate::util::*; -------------------------------------------------------------------------------- /crates/parser/tree/default_xml_namespace_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct DefaultXmlNamespaceStatement { 6 | pub location: Location, 7 | pub right: Rc, 8 | } -------------------------------------------------------------------------------- /crates/parser/tree/while_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct WhileStatement { 6 | pub location: Location, 7 | pub test: Rc, 8 | pub body: Rc, 9 | } -------------------------------------------------------------------------------- /crates/parser/tree/with_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct WithStatement { 6 | pub location: Location, 7 | pub object: Rc, 8 | pub body: Rc, 9 | } -------------------------------------------------------------------------------- /tests/parser/Misc.as: -------------------------------------------------------------------------------- 1 | namespace ns1 2 | namespace ns2 = "http://www.samsung.com" 3 | 4 | new [] 5 | 6 | v is T 7 | v is not T 8 | v instanceof T 9 | k in o 10 | k not in o 11 | 12 | x ? y : z 13 | 14 | 10 ; 15 | 10f ; 16 | 17 | /(?:)/gi ; 18 | 19 | /=/ -------------------------------------------------------------------------------- /crates/parser/tree/unary_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct UnaryExpression { 6 | pub location: Location, 7 | pub operator: Operator, 8 | pub expression: Rc, 9 | } -------------------------------------------------------------------------------- /tests/parser/Super.as: -------------------------------------------------------------------------------- 1 | super() // ERROR 2 | class C2 extends C1 { 3 | function C2() { 4 | if (true) { 5 | super() 6 | } 7 | } 8 | override protected function m(): void { 9 | super.m() 10 | super(this).m() 11 | } 12 | } -------------------------------------------------------------------------------- /crates/parser/tree/call_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct CallExpression { 6 | pub location: Location, 7 | pub base: Rc, 8 | pub arguments: Vec>, 9 | } -------------------------------------------------------------------------------- /crates/parser/tree/program.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct Program { 6 | pub location: Location, 7 | pub packages: Vec>, 8 | pub directives: Vec>, 9 | } -------------------------------------------------------------------------------- /crates/parser/tree/vector_literal.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct VectorLiteral { 6 | pub location: Location, 7 | pub element_type: Rc, 8 | pub elements: Vec, 9 | } -------------------------------------------------------------------------------- /crates/parser/tree/labeled_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct LabeledStatement { 6 | pub location: Location, 7 | pub label: (String, Location), 8 | pub substatement: Rc, 9 | } -------------------------------------------------------------------------------- /crates/parser/tree/member_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct MemberExpression { 6 | pub location: Location, 7 | pub base: Rc, 8 | pub identifier: QualifiedIdentifier, 9 | } -------------------------------------------------------------------------------- /crates/parser/tree/new_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct NewExpression { 6 | pub location: Location, 7 | pub base: Rc, 8 | pub arguments: Option>>, 9 | } -------------------------------------------------------------------------------- /tests/parser/Attributes.as: -------------------------------------------------------------------------------- 1 | q.x var y 2 | 3 | [N1] internal var z 4 | 5 | [N1(x = "y")] {} 6 | 7 | dynamic final class C1 {} 8 | 9 | /** 10 | * Foo 11 | */ 12 | [N1] 13 | /** 14 | * @eventType FooEvent.FOO 15 | */ 16 | [Event(name = "foo", type = "FooEvent")] 17 | class C1 {} -------------------------------------------------------------------------------- /crates/parser/tree/descendants_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct DescendantsExpression { 6 | pub location: Location, 7 | pub base: Rc, 8 | pub identifier: QualifiedIdentifier, 9 | } -------------------------------------------------------------------------------- /crates/parser/tree/function_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct FunctionExpression { 6 | pub location: Location, 7 | pub name: Option<(String, Location)>, 8 | pub common: Rc, 9 | } -------------------------------------------------------------------------------- /crates/parser/tree/destructuring.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct TypedDestructuring { 6 | pub location: Location, 7 | pub destructuring: Rc, 8 | pub type_annotation: Option>, 9 | } -------------------------------------------------------------------------------- /crates/parser/tree/type_parameter.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// A type parameter as in `function f.(): void {}`. 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub struct TypeParameter { 7 | pub location: Location, 8 | pub name: (String, Location), 9 | } -------------------------------------------------------------------------------- /crates/parser/tree/do_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// The `do..while` statement. 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub struct DoStatement { 7 | pub location: Location, 8 | pub body: Rc, 9 | pub test: Rc, 10 | } -------------------------------------------------------------------------------- /as3/promise/AggregateError.as: -------------------------------------------------------------------------------- 1 | package { 2 | public class AggregateError extends Error { 3 | public var errors: Array; 4 | 5 | public function AggregateError(errors: Array, message: String = "") { 6 | super(message); 7 | this.errors = errors.slice(0); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /crates/parser/tree/binary_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct BinaryExpression { 6 | pub location: Location, 7 | pub operator: Operator, 8 | pub left: Rc, 9 | pub right: Rc, 10 | } -------------------------------------------------------------------------------- /crates/parser/tree/filter_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// Filter operation `o.(condition)`. 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub struct FilterExpression { 7 | pub location: Location, 8 | pub base: Rc, 9 | pub test: Rc, 10 | } -------------------------------------------------------------------------------- /crates/parser/tree/if_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct IfStatement { 6 | pub location: Location, 7 | pub test: Rc, 8 | pub consequent: Rc, 9 | pub alternative: Option>, 10 | } -------------------------------------------------------------------------------- /crates/parser/tree/package_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct PackageDefinition { 6 | pub location: Location, 7 | pub asdoc: Option>, 8 | pub name: Vec<(String, Location)>, 9 | pub block: Rc, 10 | } -------------------------------------------------------------------------------- /crates/parser/tree/sequence_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// Sequence expression (`x, y`). 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub struct SequenceExpression { 7 | pub location: Location, 8 | pub left: Rc, 9 | pub right: Rc, 10 | } -------------------------------------------------------------------------------- /crates/parser/tree/directive_injection_node.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// Node that allows modification to the directive sequence. 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub struct DirectiveInjectionNode { 7 | pub location: Location, 8 | pub directives: RefCell>>, 9 | } -------------------------------------------------------------------------------- /tests/parser/E4X.as: -------------------------------------------------------------------------------- 1 | default xml namespace = myxmlns; 2 | 3 | ; 4 | ; 5 | ; 6 | 7 | 8 | 9 | 10 | 11 | Lorem ipsum 12 | Lorem ipsum 13 | {c} 14 | 15 | .(filterCondition) 16 | ..d; 17 | 18 | <>; -------------------------------------------------------------------------------- /crates/parser/tree/conditional_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct ConditionalExpression { 6 | pub location: Location, 7 | pub test: Rc, 8 | pub consequent: Rc, 9 | pub alternative: Rc, 10 | } -------------------------------------------------------------------------------- /crates/parser/tree/expression_with_type_arguments.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// The `o.<...>` expression. 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub struct ExpressionWithTypeArguments { 7 | pub location: Location, 8 | pub base: Rc, 9 | pub arguments: Vec>, 10 | } -------------------------------------------------------------------------------- /tests/parser/ASDoc.as: -------------------------------------------------------------------------------- 1 | /** 2 | * Main body. 3 | * @copy C1#x 4 | * @inheritDoc 5 | * @see C1#x X description 6 | */ 7 | var x 8 | 9 | /** 10 | * N1. 11 | */ 12 | [N1] 13 | /** 14 | * N2. Lorem 15 | * ipsum. 16 | */ 17 | [N2] 18 | var y 19 | 20 | /** 21 | * @throws SyntaxError If a syntax error occurs. 22 | */ 23 | function m() { 24 | } -------------------------------------------------------------------------------- /crates/parser/tree/type_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct TypeDefinition { 6 | pub location: Location, 7 | pub asdoc: Option>, 8 | pub attributes: Vec, 9 | pub left: (String, Location), 10 | pub right: Rc, 11 | } -------------------------------------------------------------------------------- /crates/parser/tree/package_concat_directive.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// The `public += ns.*;` directive. 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub struct PackageConcatDirective { 7 | pub location: Location, 8 | pub package_name: Vec<(String, Location)>, 9 | pub import_specifier: ImportSpecifier, 10 | } -------------------------------------------------------------------------------- /crates/parser/tree/namespace_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct NamespaceDefinition { 6 | pub location: Location, 7 | pub asdoc: Option>, 8 | pub attributes: Vec, 9 | pub left: (String, Location), 10 | pub right: Option>, 11 | } -------------------------------------------------------------------------------- /crates/parser/tree/computed_member_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct ComputedMemberExpression { 6 | pub location: Location, 7 | pub base: Rc, 8 | /// ASDoc. Always ignore this field; it is used solely 9 | /// when parsing meta-data. 10 | pub asdoc: Option>, 11 | pub key: Rc, 12 | } -------------------------------------------------------------------------------- /crates/parser/tree/enum_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct EnumDefinition { 6 | pub location: Location, 7 | pub asdoc: Option>, 8 | pub attributes: Vec, 9 | pub is_set: bool, 10 | pub name: (String, Location), 11 | pub as_clause: Option>, 12 | pub block: Rc, 13 | } -------------------------------------------------------------------------------- /crates/parser/tree/include_directive.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct IncludeDirective { 6 | pub location: Location, 7 | pub source: String, 8 | #[serde(skip)] 9 | pub nested_compilation_unit: Rc, 10 | pub nested_packages: Vec>, 11 | pub nested_directives: Vec>, 12 | } -------------------------------------------------------------------------------- /demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "as3_parser_demo" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | as3_parser = { path = "../crates/parser", version = "1" } 11 | maplit = "1.0.2" 12 | serde = { version = "1.0.192", features = ["rc", "derive"] } 13 | serde_json = "1.0.108" 14 | wasm-bindgen = "0.2.92" 15 | 16 | [dependencies.web-sys] 17 | version = "0.3.4" 18 | features = ["Document", "Window"] -------------------------------------------------------------------------------- /crates/parser/tree/interface_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct InterfaceDefinition { 6 | pub location: Location, 7 | pub asdoc: Option>, 8 | pub attributes: Vec, 9 | pub name: (String, Location), 10 | pub type_parameters: Option>>, 11 | pub extends_clause: Option>>, 12 | pub block: Rc, 13 | } -------------------------------------------------------------------------------- /crates/parser/util/arena.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::{Rc, Weak}}; 2 | 3 | pub struct Arena { 4 | data: RefCell>>, 5 | } 6 | 7 | impl Arena { 8 | pub fn new() -> Self { 9 | Self { 10 | data: RefCell::new(vec![]), 11 | } 12 | } 13 | 14 | pub fn allocate(&self, value: T) -> Weak { 15 | let obj = Rc::new(value); 16 | self.data.borrow_mut().push(obj.clone()); 17 | Rc::downgrade(&obj) 18 | } 19 | } -------------------------------------------------------------------------------- /tests/parser/README.md: -------------------------------------------------------------------------------- 1 | # Parsing tests 2 | 3 | To test parsing a program producing output to the command line, run: 4 | 5 | ``` 6 | cargo run --bin as3_parser_test -- --source-path tests/parser/Demo.as 7 | ``` 8 | 9 | To test parsing a program producing output to two files `.ast.json` and `.diag`, run: 10 | 11 | ``` 12 | cargo run --bin as3_parser_test -- --source-path tests/parser/Demo.as --file-log 13 | ``` 14 | 15 | For parsing MXML, pass the `--mxml` flag. 16 | 17 | For parsing CSS, pass the `--css` flag. -------------------------------------------------------------------------------- /crates/parser/tree/class_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct ClassDefinition { 6 | pub location: Location, 7 | pub asdoc: Option>, 8 | pub attributes: Vec, 9 | pub name: (String, Location), 10 | pub type_parameters: Option>>, 11 | pub extends_clause: Option>, 12 | pub implements_clause: Option>>, 13 | pub block: Rc, 14 | } 15 | -------------------------------------------------------------------------------- /crates/parser/tree/normal_configuration_directive.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// Represents a `CONFIG::x ...` directive. 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub struct NormalConfigurationDirective { 7 | pub location: Location, 8 | /// The namespace, most commonly the `CONFIG` identifier. 9 | pub namespace: (String, Location), 10 | /// The constant name without including the qualifier. 11 | pub constant_name: (String, Location), 12 | pub directive: Rc, 13 | } -------------------------------------------------------------------------------- /as3/promise/PromiseHandler.as: -------------------------------------------------------------------------------- 1 | package __AS3__.promise { 2 | /** 3 | * @private 4 | */ 5 | public final class PromiseHandler { 6 | public var onFulfilled: Function; 7 | public var onRejected: Function; 8 | public var promise: Promise; 9 | 10 | public function PromiseHandler(onFulfilled: Function, onRejected: Function, promise: Promise) { 11 | this.onFulfilled = onFulfilled; 12 | this.onRejected = onRejected; 13 | this.promise = promise; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /crates/parser/tree/array_literal.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct ArrayLiteral { 6 | pub location: Location, 7 | /// ASDoc. Always ignore this field; it is used solely 8 | /// when parsing meta-data. 9 | pub asdoc: Option>, 10 | pub elements: Vec, 11 | } 12 | 13 | #[derive(Clone, Serialize, Deserialize)] 14 | pub enum Element { 15 | Elision, 16 | Expression(Rc), 17 | Rest((Rc, Location)), 18 | } -------------------------------------------------------------------------------- /tests/parser/MXML1.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /crates/parser/tree/assignment_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct AssignmentExpression { 6 | pub location: Location, 7 | pub compound: Option, 8 | /// Assignment left-hand side. 9 | /// 10 | /// If the left-hand side is an `ObjectInitializer` or an `ArrayLiteral`, 11 | /// possibly followed by a non-null operator, 12 | /// and there is no compound assignment, it is a destructuring pattern. 13 | pub left: Rc, 14 | pub right: Rc, 15 | } -------------------------------------------------------------------------------- /crates/parser/tree/configuration_directive.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// The `configuration {}` directive. 5 | /// 6 | /// # Syntax 7 | /// 8 | /// The directive consists of a block of `if..else` branches. 9 | /// The top-level if statement takes a block, as well as its 10 | /// optional else clause. The `configuration` directive 11 | /// may consist of solely a block, in which case it is redundant. 12 | #[derive(Clone, Serialize, Deserialize)] 13 | pub struct ConfigurationDirective { 14 | pub location: Location, 15 | pub directive: Rc, 16 | } -------------------------------------------------------------------------------- /crates/parser/util/escaping.rs: -------------------------------------------------------------------------------- 1 | /// Escapes XML special characters. 2 | pub fn escape_xml(characters: &str) -> String { 3 | let escaped = htmlentity::entity::encode(characters.as_ref(), &htmlentity::entity::EncodeType::NamedOrHex, &htmlentity::entity::CharacterSet::SpecialChars); 4 | String::from_utf8_lossy(&escaped.bytes().into_owned()).into_owned() 5 | } 6 | 7 | /// Unescapes XML entities conforming to HTML entities. 8 | pub fn unescape_xml(input: &str) -> String { 9 | let unescaped = htmlentity::entity::decode(input.as_ref()); 10 | String::from_utf8_lossy(&unescaped.bytes().into_owned()).into_owned() 11 | } -------------------------------------------------------------------------------- /crates/parser/tree/super_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// Super expression. 5 | /// 6 | /// The super expression must always be followed by a property operator. 7 | /// When the super expression appears in evaluation, the immediately 8 | /// following property operator is limited to access a property from the base class 9 | /// or invoke a method of the base class. 10 | /// 11 | /// ``` 12 | /// super.f() 13 | /// ``` 14 | #[derive(Clone, Serialize, Deserialize)] 15 | pub struct SuperExpression { 16 | pub location: Location, 17 | pub object: Option>>, 18 | } -------------------------------------------------------------------------------- /crates/parser/parser.rs: -------------------------------------------------------------------------------- 1 | //! Defines the parser and the tokenizer. 2 | //! 3 | //! Using the methods of the `ParserFacade` structure is the most common way of parsing 4 | //! programs until end-of-file. 5 | 6 | mod character_validator; 7 | pub use character_validator::*; 8 | mod context; 9 | pub use context::*; 10 | mod reserved_word; 11 | pub use reserved_word::*; 12 | mod parser; 13 | pub use parser::*; 14 | mod css_parser; 15 | pub use css_parser::*; 16 | mod css_tokenizer; 17 | pub use css_tokenizer::*; 18 | mod parser_error; 19 | pub use parser_error::*; 20 | mod token; 21 | pub use token::*; 22 | mod tokenizer; 23 | pub use tokenizer::*; -------------------------------------------------------------------------------- /crates/parser/parser/parser_error.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | 3 | /// Indicates a fatal syntax error that leads the parser 4 | /// to complete without a resulting node. 5 | #[derive(Copy, Clone, Debug)] 6 | pub enum ParserError { 7 | Common, 8 | } 9 | 10 | /// Returns the identifier name that is specially reserved 11 | /// for invalidated identifiers that could not be parsed. 12 | pub const INVALIDATED_IDENTIFIER: &'static str = "\x00"; 13 | 14 | #[derive(Clone)] 15 | pub(crate) enum MetadataRefineError { 16 | Syntax, 17 | } 18 | 19 | #[derive(Clone)] 20 | pub(crate) struct MetadataRefineError1(pub MetadataRefineError, pub Location); 21 | -------------------------------------------------------------------------------- /crates/parser/tree/try_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct TryStatement { 6 | pub location: Location, 7 | pub block: Rc, 8 | pub catch_clauses: Vec, 9 | pub finally_clause: Option, 10 | } 11 | 12 | #[derive(Clone, Serialize, Deserialize)] 13 | pub struct CatchClause { 14 | pub location: Location, 15 | pub parameter: TypedDestructuring, 16 | pub block: Rc, 17 | } 18 | 19 | #[derive(Clone, Serialize, Deserialize)] 20 | pub struct FinallyClause { 21 | pub location: Location, 22 | pub block: Rc, 23 | } -------------------------------------------------------------------------------- /tests/parser/Configuration.as: -------------------------------------------------------------------------------- 1 | package foo.bar { 2 | /** 3 | * Comment 1 (overriden by the "Communication facility" comment). 4 | */ 5 | CONFIG::DEBUG 6 | /** 7 | * Communication facility. 8 | */ 9 | [Adherence(type = "efficient")] 10 | /** 11 | * Dispatched when a message is received. 12 | */ 13 | [Event(name = "received", type = "foo.bar.MessageEvent")] 14 | public class CommunicationCenter extends EventDispatcher { 15 | CONFIG::DEBUG { 16 | protected var x: T1, y: T2, z: T3; 17 | protected function f1(): void {} 18 | } 19 | CONFIG::RELEASE protected var w: T4; 20 | } 21 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ActionScript 3 Parser 2 | 3 |

4 | 5 | 6 | 7 |

8 | 9 | ActionScript 3 parser in the Rust language. 10 | 11 | [Online demo](https://hydroper.github.io/as3parser/demo) 12 | 13 | ## Documentation 14 | 15 | Refer to the [wiki](https://github.com/hydroper/as3parser/wiki) of this repository for various informations, including a getting started point. 16 | 17 | ## License 18 | 19 | Apache License 2.0, copyright 2024 Hydroper 20 | -------------------------------------------------------------------------------- /crates/parser_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "as3_parser_test" 3 | version = "0.4.0" 4 | edition = "2021" 5 | authors = ["hydroper "] 6 | repository = "https://github.com/hydroper/as3_parser" 7 | keywords = ["actionscript", "as3", "parser"] 8 | description = "ActionScript 3 Parser Test" 9 | license = "Apache-2.0" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [[bin]] 14 | name = "as3_parser_test" 15 | path = "main.rs" 16 | 17 | [dependencies] 18 | clap = { version = "4.4.8", features = ["derive"] } 19 | file_paths = "1.0.0" 20 | as3_parser = { path = "../parser", version = "1" } 21 | maplit = "1.0.2" 22 | serde = { version = "1.0.192", features = ["rc", "derive"] } 23 | serde_json = "1.0.108" 24 | -------------------------------------------------------------------------------- /crates/parser/tree/optional_chaining_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// An expression followed by optional chaining operations. 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub struct OptionalChainingExpression { 7 | pub location: Location, 8 | pub base: Rc, 9 | /// Optional chaining operations. 10 | /// 11 | /// An `OptionalChainingPlaceholder` node is is the topmost expression 12 | /// in the `expression` field. 13 | pub expression: Rc, 14 | } 15 | 16 | /// Internal expression used as the topmost expression 17 | /// of a sequence of optional chaining operations. 18 | #[derive(Clone, Serialize, Deserialize)] 19 | pub struct OptionalChainingPlaceholder { 20 | pub location: Location, 21 | } -------------------------------------------------------------------------------- /crates/parser/tree/import_directive.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct ImportDirective { 6 | pub location: Location, 7 | pub alias: Option<(String, Location)>, 8 | pub package_name: Vec<(String, Location)>, 9 | pub import_specifier: ImportSpecifier, 10 | } 11 | 12 | #[derive(Clone, Serialize, Deserialize)] 13 | pub enum ImportSpecifier { 14 | Wildcard(Location), 15 | Recursive(Location), 16 | Identifier((String, Location)), 17 | } 18 | 19 | impl ImportSpecifier { 20 | pub fn location(&self) -> Location { 21 | match self { 22 | Self::Wildcard(l) | 23 | Self::Recursive(l) => l.clone(), 24 | Self::Identifier((_, l)) => l.clone(), 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /tests/parser/MXML2.tree: -------------------------------------------------------------------------------- 1 | { 2 | "location": "1:1-4:11", 3 | "version": "Version10", 4 | "encoding": "utf-8", 5 | "content": [ 6 | { 7 | "ProcessingInstruction": { 8 | "location": "1:1-1:22", 9 | "name": "xml", 10 | "data": " version=\"1.0\"" 11 | } 12 | }, 13 | { 14 | "Element": { 15 | "location": "2:2-4:11", 16 | "name": { 17 | "location": "2:2-2:9", 18 | "prefix": null, 19 | "name": "invalid" 20 | }, 21 | "attributes": [], 22 | "content": [ 23 | { 24 | "Characters": [ 25 | "\r\n & Text &\r\n", 26 | "2:10-4:1" 27 | ] 28 | } 29 | ], 30 | "closing_name": { 31 | "location": "4:3-4:10", 32 | "prefix": null, 33 | "name": "invalid" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /crates/parser/tree/for_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct ForStatement { 6 | pub location: Location, 7 | pub init: Option, 8 | pub test: Option>, 9 | pub update: Option>, 10 | pub body: Rc, 11 | } 12 | 13 | #[derive(Clone, Serialize, Deserialize)] 14 | pub enum ForInitializer { 15 | Expression(Rc), 16 | VariableDefinition(Rc), 17 | } 18 | 19 | #[derive(Clone, Serialize, Deserialize)] 20 | pub struct ForInStatement { 21 | pub location: Location, 22 | pub each: bool, 23 | pub left: ForInBinding, 24 | pub right: Rc, 25 | pub body: Rc, 26 | } 27 | 28 | #[derive(Clone, Serialize, Deserialize)] 29 | pub enum ForInBinding { 30 | Expression(Rc), 31 | VariableDefinition(Rc), 32 | } -------------------------------------------------------------------------------- /crates/parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "as3_parser" 3 | version = "1.0.25" 4 | edition = "2021" 5 | authors = ["hydroper "] 6 | repository = "https://github.com/hydroper/as3parser" 7 | keywords = ["actionscript", "as3", "parser"] 8 | description = "ActionScript 3 parser" 9 | license = "Apache-2.0" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [lib] 14 | name = "as3_parser" 15 | path = "lib.rs" 16 | 17 | [dependencies] 18 | bitflags = { version = "2.4.1", features = ["serde"] } 19 | bytes = "1" 20 | file_paths = "1.0.0" 21 | conv = "0.3.3" 22 | htmlentity = "1.3.1" 23 | late_format = "1.0.0" 24 | maplit = "1.0.2" 25 | num-bigint = "0.4" 26 | num-derive = "0.4.1" 27 | num-traits = "0.2.17" 28 | lazy-regex = "3.0.2" 29 | lazy_static = "1.4.0" 30 | unicode-general-category = "0.6.0" 31 | by_address = "1.1.0" 32 | serde = { version = "1.0.192", features = ["rc", "derive"] } 33 | serde_json = "1.0.108" 34 | hydroper_source_text = "1.0.3" 35 | -------------------------------------------------------------------------------- /crates/parser/util.rs: -------------------------------------------------------------------------------- 1 | //! Miscellaneous. 2 | 3 | mod arena; 4 | pub use arena::*; 5 | 6 | pub use by_address::ByAddress as NodeAsKey; 7 | 8 | mod character_reader; 9 | pub use character_reader::*; 10 | 11 | mod escaping; 12 | pub use escaping::*; 13 | 14 | mod css; 15 | pub use css::*; 16 | 17 | pub use std::cell::{Cell, RefCell}; 18 | pub use std::collections::{HashMap, HashSet}; 19 | pub use std::rc::{Rc, Weak}; 20 | 21 | pub fn default() -> T { 22 | T::default() 23 | } 24 | 25 | /// Counts the first whitespace characters of a string. 26 | pub fn count_first_whitespace_characters(input: &str) -> usize { 27 | input.chars().count() - input.trim_start().chars().count() 28 | } 29 | 30 | /// Decreases the last offset of a range without ever going behind the first offset. 31 | pub fn decrease_last_offset(first_offset: usize, mut last_offset: usize, count: usize) -> usize { 32 | for _ in 0..count { 33 | last_offset = if first_offset < last_offset { last_offset - 1 } else { last_offset }; 34 | } 35 | last_offset 36 | } -------------------------------------------------------------------------------- /crates/parser/tree/variable_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct VariableDefinition { 6 | pub location: Location, 7 | pub asdoc: Option>, 8 | pub attributes: Vec, 9 | pub kind: (VariableDefinitionKind, Location), 10 | pub bindings: Vec>, 11 | } 12 | 13 | #[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] 14 | pub enum VariableDefinitionKind { 15 | Var, 16 | Const, 17 | } 18 | 19 | #[derive(Clone, Serialize, Deserialize)] 20 | pub struct SimpleVariableDefinition { 21 | pub location: Location, 22 | pub kind: (VariableDefinitionKind, Location), 23 | pub bindings: Vec>, 24 | } 25 | 26 | #[derive(Clone, Serialize, Deserialize)] 27 | pub struct VariableBinding { 28 | pub destructuring: TypedDestructuring, 29 | pub initializer: Option>, 30 | } 31 | 32 | impl VariableBinding { 33 | pub fn location(&self) -> Location { 34 | self.initializer.as_ref().map_or(self.destructuring.location.clone(), |init| self.destructuring.location.combine_with(init.location())) 35 | } 36 | } -------------------------------------------------------------------------------- /.github/workflows/demo.yml: -------------------------------------------------------------------------------- 1 | name: Demo 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | permissions: 10 | contents: write 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Use nightly Rust 22 | run: rustup default nightly 23 | - name: Add wasm32 target 24 | run: rustup target add wasm32-unknown-unknown 25 | - name: Install wasm-bindgen CLI 26 | run: cargo install wasm-bindgen-cli 27 | - name: Build demo 28 | run: cargo build -p as3_parser_demo --release --target wasm32-unknown-unknown 29 | - name: Update demo/dist 30 | run: wasm-bindgen --target web --out-dir demo/dist target/wasm32-unknown-unknown/release/as3_parser_demo.wasm 31 | - name: Git commit report 32 | run: | 33 | git config --global user.name 'hydroper' 34 | git config --global user.email 'matheusdiasdesouzads@gmail.com' 35 | git switch -C demo 36 | git rm --cached -r . 37 | git add demo 38 | git rm --cached -r demo/src 39 | git rm --cached demo/Cargo.toml 40 | git commit -m "Automated report" 41 | git push origin -f demo 42 | -------------------------------------------------------------------------------- /crates/parser/tree/switch_statement.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct SwitchStatement { 6 | pub location: Location, 7 | pub discriminant: Rc, 8 | pub cases: Vec, 9 | } 10 | 11 | #[derive(Clone, Serialize, Deserialize)] 12 | pub struct Case { 13 | pub location: Location, 14 | pub labels: Vec, 15 | pub directives: Vec>, 16 | } 17 | 18 | #[derive(Clone, Serialize, Deserialize)] 19 | pub enum CaseLabel { 20 | Case((Rc, Location)), 21 | Default(Location), 22 | } 23 | 24 | impl CaseLabel { 25 | pub fn location(&self) -> Location { 26 | match self { 27 | Self::Case((_, l)) => l.clone(), 28 | Self::Default(l) => l.clone(), 29 | } 30 | } 31 | } 32 | 33 | #[derive(Clone, Serialize, Deserialize)] 34 | pub struct SwitchTypeStatement { 35 | pub location: Location, 36 | pub discriminant: Rc, 37 | pub cases: Vec, 38 | } 39 | 40 | #[derive(Clone, Serialize, Deserialize)] 41 | pub struct TypeCase { 42 | pub location: Location, 43 | /// Case parameter. If `None`, designates a `default {}` case. 44 | pub parameter: Option, 45 | pub block: Rc, 46 | } -------------------------------------------------------------------------------- /crates/parser/tree/reserved_namespace_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub enum ReservedNamespaceExpression { 6 | Public(Location), 7 | Private(Location), 8 | Protected(Location), 9 | Internal(Location), 10 | } 11 | 12 | impl ReservedNamespaceExpression { 13 | pub fn location(&self) -> Location { 14 | match self { 15 | Self::Public(l) => l.clone(), 16 | Self::Private(l) => l.clone(), 17 | Self::Protected(l) => l.clone(), 18 | Self::Internal(l) => l.clone(), 19 | } 20 | } 21 | 22 | pub fn to_attribute(&self) -> Attribute { 23 | match self { 24 | Self::Public(l) => Attribute::Public(l.clone()), 25 | Self::Private(l) => Attribute::Private(l.clone()), 26 | Self::Protected(l) => Attribute::Protected(l.clone()), 27 | Self::Internal(l) => Attribute::Internal(l.clone()), 28 | } 29 | } 30 | } 31 | 32 | impl ToString for ReservedNamespaceExpression { 33 | fn to_string(&self) -> String { 34 | match self { 35 | Self::Public(_) => "public".into(), 36 | Self::Private(_) => "private".into(), 37 | Self::Protected(_) => "protected".into(), 38 | Self::Internal(_) => "internal".into(), 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /demo/css/variables.css: -------------------------------------------------------------------------------- 1 | /* Globals */ 2 | 3 | :root { 4 | --menubar-height: 38px; 5 | --content-max-width: 750px; 6 | 7 | --common-font: Verdana, Geneva, Arial, Helvetica, sans-serif; 8 | --common-font-size: 0.68rem; 9 | --code-font: Courier New, monospace; 10 | --code-font-size: 0.8rem; 11 | } 12 | 13 | html[data-dark="true"] { 14 | --page-background: #aeaeae; 15 | --background: #444; 16 | --foreground: #eee; 17 | 18 | --code-block-background: #949494; 19 | --code-block-foreground: #000; 20 | --code-block-selection: #24F; 21 | 22 | --heading-letter-spacing: 0rem; 23 | --header-1: #eee; 24 | --header-2: #eee; 25 | --header-3: var(--header-2); 26 | 27 | --scroll-thumb: #585858; 28 | 29 | --button-background: linear-gradient(-180deg, rgba(145,145,145,1) 0%, rgba(96,96,96,1) 100%); 30 | --button-hover-background: linear-gradient(-180deg, rgba(82,82,82,1) 0%, rgba(61,61,61,1) 100%); 31 | --button-foreground: #eee; 32 | 33 | --table-border: #222; 34 | --table-header-background: #444; 35 | --table-header-foreground: #fff; 36 | 37 | --links: var(--foreground); 38 | 39 | --textarea-focus: #7af; 40 | } 41 | 42 | /* Portrait */ 43 | 44 | @media (min-width: 320px) { 45 | :root { 46 | --sidebar-width: 280px; 47 | } 48 | } 49 | 50 | /* Landscape */ 51 | 52 | @media (min-width: 961px) { 53 | :root { 54 | --sidebar-width: 25rem; 55 | } 56 | } -------------------------------------------------------------------------------- /crates/parser/tree/asdoc.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct AsDoc { 6 | pub location: Location, 7 | pub main_body: Option<(String, Location)>, 8 | pub tags: Vec<(AsDocTag, Location)>, 9 | } 10 | 11 | #[derive(Clone, Serialize, Deserialize)] 12 | pub enum AsDocTag { 13 | Author(String), 14 | Copy(Rc), 15 | Created(String), 16 | Default(String), 17 | Deprecated { 18 | message: Option, 19 | }, 20 | EventType(Rc), 21 | Example(String), 22 | InheritDoc, 23 | Internal(String), 24 | Langversion(String), 25 | Param { 26 | name: String, 27 | description: String, 28 | }, 29 | Playerversion(String), 30 | Private, 31 | Productversion(String), 32 | Return(String), 33 | See { 34 | reference: Rc, 35 | display_text: Option, 36 | }, 37 | Throws { 38 | class_reference: Rc, 39 | description: Option, 40 | }, 41 | Version(String), 42 | } 43 | 44 | /// An ASDoc reference consisting of an optional base and 45 | /// an optional instance property fragment (`#x`). 46 | #[derive(Clone, Serialize, Deserialize)] 47 | pub struct AsDocReference { 48 | /// Base expression. 49 | pub base: Option>, 50 | /// Instance property fragment following the hash character. 51 | pub instance_property: Option>, 52 | } -------------------------------------------------------------------------------- /demo/ace/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Ajax.org B.V. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of Ajax.org B.V. nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /crates/parser/tree/qualified_identifier.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct QualifiedIdentifier { 6 | pub location: Location, 7 | pub attribute: bool, 8 | pub qualifier: Option>, 9 | pub id: QualifiedIdentifierIdentifier, 10 | } 11 | 12 | #[derive(Clone, Serialize, Deserialize)] 13 | pub enum QualifiedIdentifierIdentifier { 14 | Id((String, Location)), 15 | Brackets(Rc), 16 | } 17 | 18 | impl QualifiedIdentifier { 19 | pub fn to_identifier_name_or_asterisk(&self) -> Option<(String, Location)> { 20 | if self.attribute || self.qualifier.is_some() { 21 | None 22 | } else { 23 | if let QualifiedIdentifierIdentifier::Id(id) = &self.id { 24 | Some(id.clone()) 25 | } else { 26 | None 27 | } 28 | } 29 | } 30 | 31 | pub fn to_identifier_name(&self) -> Option<(String, Location)> { 32 | if self.attribute || self.qualifier.is_some() { 33 | None 34 | } else { 35 | if let QualifiedIdentifierIdentifier::Id(id) = &self.id { 36 | if id.0 == "*" { None } else { Some(id.clone()) } 37 | } else { 38 | None 39 | } 40 | } 41 | } 42 | 43 | pub fn is_identifier_token(&self) -> bool { 44 | self.qualifier.is_none() && !self.attribute && match &self.id { 45 | QualifiedIdentifierIdentifier::Id((id, _)) => id != "*", 46 | _ => false, 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /crates/parser/tree/xml_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct XmlExpression { 6 | pub location: Location, 7 | pub element: Rc, 8 | } 9 | 10 | #[derive(Clone, Serialize, Deserialize)] 11 | pub struct XmlMarkupExpression { 12 | pub location: Location, 13 | pub markup: String, 14 | } 15 | 16 | #[derive(Clone, Serialize, Deserialize)] 17 | pub struct XmlListExpression { 18 | pub location: Location, 19 | pub content: Vec>, 20 | } 21 | 22 | #[derive(Clone, Serialize, Deserialize)] 23 | pub struct XmlElement { 24 | pub location: Location, 25 | pub name: XmlTagName, 26 | pub attributes: Vec>, 27 | pub attribute_expression: Option>, 28 | pub content: Option>>, 29 | pub closing_name: Option, 30 | } 31 | 32 | #[derive(Clone, Serialize, Deserialize)] 33 | pub enum XmlTagName { 34 | Name((String, Location)), 35 | Expression(Rc), 36 | } 37 | 38 | #[derive(Clone, Serialize, Deserialize)] 39 | pub struct XmlAttribute { 40 | pub location: Location, 41 | pub name: (String, Location), 42 | pub value: XmlAttributeValue, 43 | } 44 | 45 | #[derive(Clone, Serialize, Deserialize)] 46 | pub enum XmlAttributeValue { 47 | Value((String, Location)), 48 | Expression(Rc), 49 | } 50 | 51 | #[derive(Clone, Serialize, Deserialize)] 52 | pub enum XmlContent { 53 | Characters((String, Location)), 54 | Markup((String, Location)), 55 | Element(Rc), 56 | Expression(Rc), 57 | } -------------------------------------------------------------------------------- /crates/parser/tree/type_expression.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct NullableTypeExpression { 6 | pub location: Location, 7 | pub base: Rc, 8 | } 9 | 10 | #[derive(Clone, Serialize, Deserialize)] 11 | pub struct NonNullableTypeExpression { 12 | pub location: Location, 13 | pub base: Rc, 14 | } 15 | 16 | #[derive(Clone, Serialize, Deserialize)] 17 | pub struct AnyTypeExpression { 18 | pub location: Location, 19 | } 20 | 21 | #[derive(Clone, Serialize, Deserialize)] 22 | pub struct VoidTypeExpression { 23 | pub location: Location, 24 | } 25 | 26 | #[derive(Clone, Serialize, Deserialize)] 27 | pub struct ArrayTypeExpression { 28 | pub location: Location, 29 | pub expression: Rc, 30 | } 31 | 32 | /// A tuple type expression consisting of at least two elements. 33 | #[derive(Clone, Serialize, Deserialize)] 34 | pub struct TupleTypeExpression { 35 | pub location: Location, 36 | pub expressions: Vec>, 37 | } 38 | 39 | #[derive(Clone, Serialize, Deserialize)] 40 | pub struct FunctionTypeExpression { 41 | pub location: Location, 42 | pub parameters: Vec>, 43 | pub result_type: Option>, 44 | } 45 | 46 | /// ```plain 47 | /// function(T, T=, ...) 48 | /// function(...[T]) 49 | /// ``` 50 | #[derive(Clone, Serialize, Deserialize)] 51 | pub struct FunctionTypeParameter { 52 | pub location: Location, 53 | pub kind: ParameterKind, 54 | /// Possibly `None` for the rest parameter. 55 | pub type_expression: Option>, 56 | } -------------------------------------------------------------------------------- /crates/parser/tree/object_initializer.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct ObjectInitializer { 6 | pub location: Location, 7 | pub fields: Vec>, 8 | } 9 | 10 | #[derive(Clone, Serialize, Deserialize)] 11 | pub enum InitializerField { 12 | Field { 13 | name: (FieldName, Location), 14 | /// Non-null operator used for destructuring. 15 | non_null: bool, 16 | value: Option>, 17 | }, 18 | Rest((Rc, Location)), 19 | } 20 | 21 | impl InitializerField { 22 | pub fn location(&self) -> Location { 23 | match self { 24 | Self::Field { ref name, ref value, .. } => { 25 | value.clone().map_or(name.1.clone(), |v| name.1.combine_with(v.location())) 26 | }, 27 | Self::Rest((_, ref l)) => l.clone(), 28 | } 29 | } 30 | 31 | pub fn shorthand(&self) -> Option<&QualifiedIdentifier> { 32 | if let Self::Field { name, .. } = self { 33 | if let FieldName::Identifier(qid) = &name.0 { 34 | Some(qid) 35 | } else { 36 | None 37 | } 38 | } else { 39 | None 40 | } 41 | } 42 | } 43 | 44 | #[derive(Clone, Serialize, Deserialize)] 45 | pub enum FieldName { 46 | Identifier(QualifiedIdentifier), 47 | Brackets(Rc), 48 | StringLiteral(Rc), 49 | NumericLiteral(Rc), 50 | } 51 | 52 | impl FieldName { 53 | pub(crate) fn id(&self) -> Option<&QualifiedIdentifier> { 54 | let Self::Identifier(id) = &self else { 55 | return None; 56 | }; 57 | Some(id) 58 | } 59 | 60 | pub fn id_equals(&self, name: &str) -> bool { 61 | self.id().map(|name1| name == name1.to_identifier_name_or_asterisk().map(|id| id.0.clone()).unwrap_or("".into())).unwrap_or(false) 62 | } 63 | } -------------------------------------------------------------------------------- /demo/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use wasm_bindgen::prelude::*; 3 | use as3_parser::ns::*; 4 | 5 | #[derive(Serialize, Deserialize)] 6 | struct ParserResult { 7 | program: Option>, 8 | mxml: Option>, 9 | css: Option>, 10 | diagnostics: Vec, 11 | } 12 | 13 | #[derive(Serialize, Deserialize)] 14 | struct ParserDiagnosticResult { 15 | warning: bool, 16 | column1: usize, 17 | column2: usize, 18 | line1: usize, 19 | line2: usize, 20 | message: String, 21 | } 22 | 23 | #[wasm_bindgen] 24 | pub fn parse(input: &str, source_type: &str) -> String { 25 | let compilation_unit = CompilationUnit::new(None, input.to_owned()); 26 | 27 | let mut program: Option> = None; 28 | let mut mxml: Option> = None; 29 | let mut css: Option> = None; 30 | 31 | let source_type = source_type.to_lowercase(); 32 | 33 | if source_type == "mxml" { 34 | mxml = Some(ParserFacade(&compilation_unit, default()).parse_mxml()); 35 | } else if source_type == "css" { 36 | css = Some(CssParserFacade(&compilation_unit, default()).parse_document()); 37 | } else { 38 | program = Some(ParserFacade(&compilation_unit, default()).parse_program()); 39 | } 40 | let mut diagnostics = vec![]; 41 | compilation_unit.sort_diagnostics(); 42 | for diagnostic in compilation_unit.nested_diagnostics() { 43 | diagnostics.push(ParserDiagnosticResult { 44 | warning: diagnostic.is_warning(), 45 | column1: diagnostic.location().first_column() + 1, 46 | column2: diagnostic.location().last_column() + 1, 47 | line1: diagnostic.location().first_line_number(), 48 | line2: diagnostic.location().last_line_number(), 49 | message: diagnostic.format_message_english(), 50 | }); 51 | } 52 | serde_json::to_string_pretty(&ParserResult { 53 | program, 54 | mxml, 55 | css, 56 | diagnostics, 57 | }).unwrap() 58 | } -------------------------------------------------------------------------------- /examples/asdoc/example.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use as3_parser::ns::*; 3 | 4 | fn main() { 5 | // Define source path 6 | let source_path = env::current_dir().unwrap().join("Example.as").to_string_lossy().into_owned(); 7 | 8 | // Read source content 9 | let source_content = include_str!("Example.as").to_owned(); 10 | 11 | // Create compilation unit 12 | let compilation_unit = CompilationUnit::new(Some(source_path), source_content); 13 | 14 | // Parse program 15 | let program = ParserFacade(&compilation_unit, default()).parse_program(); 16 | visit_program(&program); 17 | 18 | // Report diagnostics 19 | compilation_unit.sort_diagnostics(); 20 | for diagnostic in compilation_unit.nested_diagnostics() { 21 | println!("{}", diagnostic.format_english()); 22 | } 23 | } 24 | 25 | fn visit_program(program: &Rc) { 26 | for package in program.packages.iter() { 27 | for directive in package.block.directives.iter() { 28 | // directive: Rc 29 | 30 | match directive.as_ref() { 31 | Directive::ClassDefinition(defn) => { 32 | visit_class(&defn); 33 | }, 34 | 35 | _ => {}, 36 | } 37 | } 38 | } 39 | } 40 | 41 | fn visit_class(defn: &ClassDefinition) { 42 | for directive in defn.block.directives.iter() { 43 | if let Directive::VariableDefinition(defn) = directive.as_ref() { 44 | // Print any found main body and @private tags 45 | if let Some(asdoc) = &defn.asdoc { 46 | print_asdoc(asdoc); 47 | } 48 | } 49 | } 50 | } 51 | 52 | fn print_asdoc(asdoc: &Rc) { 53 | if let Some((text, loc)) = &asdoc.main_body { 54 | println!("Found main body at {}:{}\n\n{}\n\n", loc.first_line_number(), loc.first_column() + 1, text); 55 | } 56 | for (tag, loc) in &asdoc.tags { 57 | if matches!(tag, AsDocTag::Private) { 58 | println!("Found @private tag at {}:{}\n\n", loc.first_line_number(), loc.first_column() + 1); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /crates/parser/compilation_unit/comment.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | 3 | pub struct Comment { 4 | pub(crate) multiline: bool, 5 | pub(crate) content: RefCell, 6 | pub(crate) location: RefCell, 7 | } 8 | 9 | impl Comment { 10 | pub fn new(multiline: bool, content: String, location: Location) -> Self { 11 | Self { 12 | multiline, 13 | content: RefCell::new(content), 14 | location: RefCell::new(location), 15 | } 16 | } 17 | 18 | pub fn multiline(&self) -> bool { 19 | self.multiline 20 | } 21 | 22 | /// The content of the comment. 23 | /// * If it is a multi-line comment, it includes all the characters after `/*` until `*/` (exclusive). 24 | /// * If it is a single-line comment, it includes all the characters after `//` 25 | /// until the next line terminator (exclusive) or end of program. 26 | pub fn content(&self) -> String { 27 | self.content.borrow().clone() 28 | } 29 | 30 | pub fn set_content(&self, content: String) { 31 | self.content.replace(content); 32 | } 33 | 34 | pub fn location(&self) -> Location { 35 | self.location.borrow().clone() 36 | } 37 | 38 | pub fn set_location(&self, location: Location) { 39 | self.location.replace(location); 40 | } 41 | 42 | /// Indicates whether the comment is an AsDoc comment preceding 43 | /// a specific location. 44 | pub fn is_asdoc(&self, location_to_precede: &Location) -> bool { 45 | if self.multiline && self.content.borrow().starts_with('*') { 46 | let mut i: usize = self.location.borrow().last_offset; 47 | for (i_1, ch) in self.location.borrow().compilation_unit().text()[i..].char_indices() { 48 | i = i_1; 49 | if !(CharacterValidator::is_whitespace(ch) || CharacterValidator::is_line_terminator(ch)) { 50 | break; 51 | } 52 | } 53 | i += self.location.borrow().last_offset; 54 | location_to_precede.first_offset == i 55 | } else { 56 | false 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /crates/parser/operator/operator_precedence.rs: -------------------------------------------------------------------------------- 1 | use num_derive::FromPrimitive; 2 | use num_traits::FromPrimitive; 3 | 4 | #[derive(FromPrimitive)] 5 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 6 | #[repr(u32)] 7 | pub enum OperatorPrecedence { 8 | Postfix = 16, 9 | Unary = 15, 10 | Exponentiation = 14, 11 | Multiplicative = 13, 12 | Additive = 12, 13 | Shift = 11, 14 | Relational = 10, 15 | Equality = 9, 16 | BitwiseAnd = 8, 17 | BitwiseXor = 7, 18 | BitwiseOr = 6, 19 | LogicalAnd = 5, 20 | LogicalXor = 4, 21 | /// Includes logical OR and nullish coalescing (`??`). 22 | LogicalOrAndOther = 3, 23 | /// Includes assignment operators, conditional operator, function expression and `yield` operator. 24 | AssignmentAndOther = 2, 25 | List = 1, 26 | } 27 | 28 | impl OperatorPrecedence { 29 | pub fn add(&self, value: u32) -> Option { 30 | FromPrimitive::from_u32(*self as u32 + value) 31 | } 32 | 33 | pub fn value_of(&self) -> u32 { 34 | *self as u32 35 | } 36 | 37 | pub fn includes(&self, other: &Self) -> bool { 38 | *self <= *other 39 | } 40 | } 41 | 42 | impl TryFrom for OperatorPrecedence { 43 | type Error = (); 44 | fn try_from(value: u32) -> Result { 45 | if let Some(v) = FromPrimitive::from_u32(value as u32) { Ok(v) } else { Err(()) } 46 | } 47 | } 48 | 49 | #[derive(FromPrimitive)] 50 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 51 | #[repr(u32)] 52 | pub enum CssOperatorPrecedence { 53 | Unary = 3, 54 | MultiValue = 2, 55 | Array = 1, 56 | } 57 | 58 | impl CssOperatorPrecedence { 59 | pub fn add(&self, value: u32) -> Option { 60 | FromPrimitive::from_u32(*self as u32 + value) 61 | } 62 | 63 | pub fn value_of(&self) -> u32 { 64 | *self as u32 65 | } 66 | 67 | pub fn includes(&self, other: &Self) -> bool { 68 | *self <= *other 69 | } 70 | } 71 | 72 | impl TryFrom for CssOperatorPrecedence { 73 | type Error = (); 74 | fn try_from(value: u32) -> Result { 75 | if let Some(v) = FromPrimitive::from_u32(value as u32) { Ok(v) } else { Err(()) } 76 | } 77 | } -------------------------------------------------------------------------------- /crates/parser_test/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use file_paths::FlexPath; 3 | use std::{env, fs, io}; 4 | use as3_parser::ns::*; 5 | 6 | #[derive(Parser, Debug)] 7 | #[command(author, version, about, long_about = None)] 8 | struct Arguments { 9 | #[arg(short, long)] 10 | source_path: String, 11 | 12 | #[arg(short, long)] 13 | file_log: bool, 14 | 15 | #[arg(short, long)] 16 | mxml: bool, 17 | 18 | #[arg(short, long)] 19 | css: bool, 20 | } 21 | 22 | fn main() -> io::Result<()> { 23 | let arguments = Arguments::parse(); 24 | let source_path = FlexPath::from_n_native([env::current_dir().unwrap().to_string_lossy().into_owned().as_ref(), arguments.source_path.as_ref()]).to_string_with_flex_separator(); 25 | 26 | // Canonicalize path 27 | // let source_path = std::path::Path::new(&source_path).canonicalize().unwrap().to_string_lossy().into_owned(); 28 | 29 | let source_path_ast_json = FlexPath::new_native(&source_path).change_extension(".tree").to_string_with_flex_separator(); 30 | let source_path_diagnostics = FlexPath::new_native(&source_path).change_extension(".diag").to_string_with_flex_separator(); 31 | let source_content = fs::read_to_string(&source_path)?; 32 | let compilation_unit = CompilationUnit::new(Some(source_path), source_content); 33 | if arguments.mxml { 34 | let document = ParserFacade(&compilation_unit, default()).parse_mxml(); 35 | if arguments.file_log { 36 | fs::write(&source_path_ast_json, serde_json::to_string_pretty(&document).unwrap())?; 37 | } 38 | } else if arguments.css { 39 | let document = CssParserFacade(&compilation_unit, default()).parse_document(); 40 | if arguments.file_log { 41 | fs::write(&source_path_ast_json, serde_json::to_string_pretty(&document).unwrap())?; 42 | } 43 | } else { 44 | let program = ParserFacade(&compilation_unit, default()).parse_program(); 45 | if arguments.file_log { 46 | fs::write(&source_path_ast_json, serde_json::to_string_pretty(&program).unwrap())?; 47 | } 48 | } 49 | let mut diagnostics = vec![]; 50 | compilation_unit.sort_diagnostics(); 51 | for diagnostic in compilation_unit.nested_diagnostics() { 52 | diagnostics.push(diagnostic.format_english()); 53 | } 54 | if arguments.file_log { 55 | fs::write(&source_path_diagnostics, diagnostics.join("\n"))?; 56 | } else { 57 | for diagnostic in diagnostics { 58 | println!("{diagnostic}"); 59 | } 60 | } 61 | Ok(()) 62 | } -------------------------------------------------------------------------------- /tests/parser/css/mediaQuery.tree: -------------------------------------------------------------------------------- 1 | { 2 | "location": "1:1-5:2", 3 | "directives": [ 4 | { 5 | "MediaQuery": { 6 | "location": "1:1-5:2", 7 | "conditions": [ 8 | { 9 | "And": { 10 | "location": "1:8-1:59", 11 | "left": { 12 | "ParenProperty": [ 13 | { 14 | "location": "1:9-1:29", 15 | "name": [ 16 | "applicationDpi", 17 | "1:9-1:24" 18 | ], 19 | "value": { 20 | "Number": { 21 | "location": "1:26-1:29", 22 | "value": 240.0, 23 | "unit": null 24 | } 25 | } 26 | }, 27 | "1:8-1:30" 28 | ] 29 | }, 30 | "right": { 31 | "ParenProperty": [ 32 | { 33 | "location": "1:36-1:58", 34 | "name": [ 35 | "osPlatform", 36 | "1:36-1:47" 37 | ], 38 | "value": { 39 | "String": { 40 | "location": "1:49-1:58", 41 | "value": "Windows" 42 | } 43 | } 44 | }, 45 | "1:35-1:59" 46 | ] 47 | } 48 | } 49 | } 50 | ], 51 | "rules": [ 52 | { 53 | "location": "2:5-4:6", 54 | "selectors": [ 55 | { 56 | "Base": { 57 | "location": "2:5-2:18", 58 | "namespace_prefix": null, 59 | "element_name": null, 60 | "conditions": [ 61 | { 62 | "Class": [ 63 | "myStyleName1", 64 | "2:5-2:18" 65 | ] 66 | } 67 | ] 68 | } 69 | } 70 | ], 71 | "properties": [ 72 | { 73 | "location": "3:9-3:21", 74 | "name": [ 75 | "fontSize", 76 | "3:9-3:17" 77 | ], 78 | "value": { 79 | "Number": { 80 | "location": "3:19-3:21", 81 | "value": 30.0, 82 | "unit": null 83 | } 84 | } 85 | } 86 | ] 87 | } 88 | ] 89 | } 90 | } 91 | ] 92 | } -------------------------------------------------------------------------------- /crates/parser/diagnostics/diagnostic_kind.rs: -------------------------------------------------------------------------------- 1 | #[repr(i32)] 2 | #[derive(Eq, PartialEq, Clone, Copy)] 3 | pub enum DiagnosticKind { 4 | InvalidEscapeValue = 1024, 5 | UnexpectedEnd = 1025, 6 | UnallowedNumericSuffix = 1026, 7 | StringLiteralMustBeTerminatedBeforeLineBreak = 1027, 8 | Expecting = 1028, 9 | ExpectingIdentifier = 1029, 10 | ExpectingExpression = 1030, 11 | ExpectingXmlName = 1031, 12 | ExpectingXmlAttributeValue = 1032, 13 | IllegalNullishCoalescingLeftOperand = 1033, 14 | WrongParameterPosition = 1034, 15 | DuplicateRestParameter = 1035, 16 | NotAllowedHere = 1036, 17 | MalformedRestParameter = 1037, 18 | IllegalForInInitializer = 1038, 19 | MultipleForInBindings = 1039, 20 | UndefinedLabel = 1040, 21 | IllegalContinue = 1041, 22 | IllegalBreak = 1042, 23 | ExpressionMustNotFollowLineBreak = 1043, 24 | TokenMustNotFollowLineBreak = 1044, 25 | ExpectingStringLiteral = 1045, 26 | DuplicateAttribute = 1046, 27 | DuplicateAccessModifier = 1047, 28 | ExpectingDirectiveKeyword = 1048, 29 | UnallowedAttribute = 1049, 30 | UseDirectiveMustContainPublic = 1050, 31 | MalformedEnumMember = 1051, 32 | FunctionMayNotBeGenerator = 1052, 33 | FunctionMayNotBeAsynchronous = 1053, 34 | FunctionMustNotContainBody = 1054, 35 | FunctionMustContainBody = 1055, 36 | FunctionMustNotContainAnnotations = 1056, 37 | NestedClassesNotAllowed = 1057, 38 | UnexpectedDirective = 1058, 39 | FailedParsingAsDocTag = 1059, 40 | UnrecognizedAsDocTag = 1060, 41 | UnrecognizedProxy = 1061, 42 | EnumMembersMustBeConst = 1062, 43 | ConstructorMustNotSpecifyResultType = 1063, 44 | UnrecognizedMetadataSyntax = 1064, 45 | FailedToIncludeFile = 1065, 46 | ParentSourceIsNotAFile = 1066, 47 | CircularIncludeDirective = 1067, 48 | MalformedDestructuring = 1068, 49 | XmlPrefixNotDefined = 1069, 50 | RedefiningXmlAttribute = 1070, 51 | InvalidXmlPi = 1071, 52 | XmlPiUnknownAttribute = 1072, 53 | XmlPiVersion = 1073, 54 | XmlPiEncoding = 1074, 55 | XmlMustConsistOfExactly1Element = 1075, 56 | XmlNameAtMostOneColon = 1076, 57 | UnexpectedCharacter = 1077, 58 | InputEndedBeforeReachingClosingQuoteForString = 1078, 59 | InputEndedBeforeReachingClosingSeqForCData = 1079, 60 | InputEndedBeforeReachingClosingSeqForPi = 1080, 61 | InputEndedBeforeReachingClosingSeqForXmlComment = 1081, 62 | InputEndedBeforeReachingClosingSeqForMultiLineComment = 1082, 63 | InputEndedBeforeReachingClosingSlashForRegExp = 1083, 64 | InputEndedBeforeReachingClosingQuoteForAttributeValue = 1084, 65 | ExpectingEitherSemicolonOrNewLineHere = 1085, 66 | CssInvalidHexEscape = 1086, 67 | ExpectingDirective = 1087, 68 | ExpectingStatement = 1088, 69 | Unexpected = 1089, 70 | XmlClosingTagNameMustBeEquals = 1090, 71 | } 72 | 73 | impl DiagnosticKind { 74 | pub fn id(&self) -> i32 { 75 | *self as i32 76 | } 77 | } -------------------------------------------------------------------------------- /crates/parser/tree/function_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub struct FunctionDefinition { 6 | pub location: Location, 7 | pub asdoc: Option>, 8 | pub attributes: Vec, 9 | pub name: FunctionName, 10 | pub common: Rc, 11 | } 12 | 13 | impl FunctionDefinition { 14 | /// Indicates whether the function definition is not a getter, setter, 15 | /// or constructor. 16 | pub fn is_normal(&self) -> bool { 17 | matches!(self.name, FunctionName::Identifier(_)) 18 | } 19 | pub fn is_getter(&self) -> bool { 20 | matches!(self.name, FunctionName::Getter(_)) 21 | } 22 | pub fn is_setter(&self) -> bool { 23 | matches!(self.name, FunctionName::Setter(_)) 24 | } 25 | pub fn is_constructor(&self) -> bool { 26 | matches!(self.name, FunctionName::Constructor(_)) 27 | } 28 | pub fn name_identifier(&self) -> (String, Location) { 29 | match &self.name { 30 | FunctionName::Identifier(name) => name.clone(), 31 | FunctionName::Getter(name) => name.clone(), 32 | FunctionName::Setter(name) => name.clone(), 33 | FunctionName::Constructor(name) => name.clone(), 34 | } 35 | } 36 | } 37 | 38 | #[derive(Clone, Serialize, Deserialize)] 39 | pub enum FunctionName { 40 | Identifier((String, Location)), 41 | Getter((String, Location)), 42 | Setter((String, Location)), 43 | /// A `FunctionName` is a `Constructor` variant 44 | /// when the corresponding function definition is a constructor. 45 | Constructor((String, Location)), 46 | } 47 | 48 | impl FunctionName { 49 | pub fn location(&self) -> Location { 50 | match self { 51 | Self::Identifier((_, l)) => l.clone(), 52 | Self::Getter((_, l)) => l.clone(), 53 | Self::Setter((_, l)) => l.clone(), 54 | Self::Constructor((_, l)) => l.clone(), 55 | } 56 | } 57 | } 58 | 59 | #[derive(Clone, Serialize, Deserialize)] 60 | pub struct FunctionCommon { 61 | pub location: Location, 62 | /// Indicates whether the corresponding function 63 | /// contains the `yield` operator. 64 | pub contains_yield: bool, 65 | /// Indicates whether the corresponding function 66 | /// contains the `await` operator. 67 | pub contains_await: bool, 68 | pub signature: FunctionSignature, 69 | pub body: Option, 70 | } 71 | 72 | impl FunctionCommon { 73 | pub(crate) fn has_block_body(&self) -> bool { 74 | if let Some(ref body) = self.body { matches!(body, FunctionBody::Block(_)) } else { false } 75 | } 76 | } 77 | 78 | #[derive(Clone, Serialize, Deserialize)] 79 | pub struct FunctionSignature { 80 | pub location: Location, 81 | pub parameters: Vec>, 82 | pub result_type: Option>, 83 | } 84 | 85 | #[derive(Clone, Serialize, Deserialize)] 86 | pub struct Parameter { 87 | pub location: Location, 88 | pub kind: ParameterKind, 89 | pub destructuring: TypedDestructuring, 90 | pub default_value: Option>, 91 | } 92 | 93 | #[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] 94 | #[repr(u32)] 95 | pub enum ParameterKind { 96 | Required = 1, 97 | Optional = 2, 98 | Rest = 3, 99 | } 100 | 101 | impl ParameterKind { 102 | pub fn may_be_followed_by(&self, other: Self) -> bool { 103 | (*self as u32) <= (other as u32) 104 | } 105 | } 106 | 107 | #[derive(Clone, Serialize, Deserialize)] 108 | pub enum FunctionBody { 109 | Expression(Rc), 110 | Block(Rc), 111 | } -------------------------------------------------------------------------------- /crates/parser/parser/reserved_word.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | 3 | /// ActionScript reserved word validation. 4 | pub struct As3ReservedWord; 5 | 6 | impl As3ReservedWord { 7 | /// Checks if an *IdentifierName* is a reserved word. 8 | pub fn test(name: &str) -> bool { 9 | As3ReservedWord::token(name).is_some() 10 | } 11 | 12 | /// Attempts to convert an *IdentifierName* into a reserved word token. 13 | pub fn token(name: &str) -> Option { 14 | match name.len() { 15 | 1 => None, 16 | 2 => { 17 | match name { 18 | "as" => Some(Token::As), 19 | "do" => Some(Token::Do), 20 | "if" => Some(Token::If), 21 | "in" => Some(Token::In), 22 | "is" => Some(Token::Is), 23 | _ => None, 24 | } 25 | }, 26 | 3 => { 27 | match name { 28 | "for" => Some(Token::For), 29 | "new" => Some(Token::New), 30 | "not" => Some(Token::Not), 31 | "try" => Some(Token::Try), 32 | "use" => Some(Token::Use), 33 | "var" => Some(Token::Var), 34 | _ => None, 35 | } 36 | }, 37 | 4 => { 38 | match name { 39 | "case" => Some(Token::Case), 40 | "else" => Some(Token::Else), 41 | "null" => Some(Token::Null), 42 | "this" => Some(Token::This), 43 | "true" => Some(Token::True), 44 | "void" => Some(Token::Void), 45 | "with" => Some(Token::With), 46 | _ => None, 47 | } 48 | }, 49 | 5 => { 50 | match name { 51 | "await" => Some(Token::Await), 52 | "break" => Some(Token::Break), 53 | "catch" => Some(Token::Catch), 54 | "class" => Some(Token::Class), 55 | "const" => Some(Token::Const), 56 | "false" => Some(Token::False), 57 | "super" => Some(Token::Super), 58 | "throw" => Some(Token::Throw), 59 | "while" => Some(Token::While), 60 | "yield" => Some(Token::Yield), 61 | _ => None, 62 | } 63 | }, 64 | 6 => { 65 | match name { 66 | "delete" => Some(Token::Delete), 67 | "import" => Some(Token::Import), 68 | "public" => Some(Token::Public), 69 | "return" => Some(Token::Return), 70 | "switch" => Some(Token::Switch), 71 | "typeof" => Some(Token::Typeof), 72 | _ => None, 73 | } 74 | }, 75 | 7 => { 76 | match name { 77 | "default" => Some(Token::Default), 78 | "extends" => Some(Token::Extends), 79 | "finally" => Some(Token::Finally), 80 | "package" => Some(Token::Package), 81 | "private" => Some(Token::Private), 82 | _ => None, 83 | } 84 | }, 85 | 8 => { 86 | match name { 87 | "continue" => Some(Token::Continue), 88 | "function" => Some(Token::Function), 89 | "internal" => Some(Token::Internal), 90 | _ => None, 91 | } 92 | }, 93 | 9 => { 94 | match name { 95 | "interface" => Some(Token::Interface), 96 | "protected" => Some(Token::Protected), 97 | _ => None, 98 | } 99 | }, 100 | 10 => { 101 | match name { 102 | "implements" => Some(Token::Implements), 103 | "instanceof" => Some(Token::Instanceof), 104 | _ => None, 105 | } 106 | }, 107 | _ => None, 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /crates/parser/tree.rs: -------------------------------------------------------------------------------- 1 | //! Defines the syntactic nodes produced by the parser. 2 | 3 | mod invalidated_node; 4 | pub use invalidated_node::*; 5 | 6 | // Expressions 7 | mod expression; 8 | pub use expression::*; 9 | mod qualified_identifier; 10 | pub use qualified_identifier::*; 11 | mod paren_expression; 12 | pub use paren_expression::*; 13 | mod null_literal; 14 | pub use null_literal::*; 15 | mod boolean_literal; 16 | pub use boolean_literal::*; 17 | mod numeric_literal; 18 | pub use numeric_literal::*; 19 | mod string_literal; 20 | pub use string_literal::*; 21 | mod this_literal; 22 | pub use this_literal::*; 23 | mod regexp_literal; 24 | pub use regexp_literal::*; 25 | mod xml_expression; 26 | pub use xml_expression::*; 27 | mod array_literal; 28 | pub use array_literal::*; 29 | mod vector_literal; 30 | pub use vector_literal::*; 31 | mod object_initializer; 32 | pub use object_initializer::*; 33 | mod function_expression; 34 | pub use function_expression::*; 35 | mod import_meta; 36 | pub use import_meta::*; 37 | mod new_expression; 38 | pub use new_expression::*; 39 | mod member_expression; 40 | pub use member_expression::*; 41 | mod computed_member_expression; 42 | pub use computed_member_expression::*; 43 | mod descendants_expression; 44 | pub use descendants_expression::*; 45 | mod filter_expression; 46 | pub use filter_expression::*; 47 | mod super_expression; 48 | pub use super_expression::*; 49 | mod call_expression; 50 | pub use call_expression::*; 51 | mod expression_with_type_arguments; 52 | pub use expression_with_type_arguments::*; 53 | mod unary_expression; 54 | pub use unary_expression::*; 55 | mod optional_chaining_expression; 56 | pub use optional_chaining_expression::*; 57 | mod binary_expression; 58 | pub use binary_expression::*; 59 | mod conditional_expression; 60 | pub use conditional_expression::*; 61 | mod assignment_expression; 62 | pub use assignment_expression::*; 63 | mod sequence_expression; 64 | pub use sequence_expression::*; 65 | mod type_expression; 66 | pub use type_expression::*; 67 | mod reserved_namespace_expression; 68 | pub use reserved_namespace_expression::*; 69 | 70 | // Destructuring 71 | mod destructuring; 72 | pub use destructuring::*; 73 | 74 | // Statements 75 | mod empty_statement; 76 | pub use empty_statement::*; 77 | mod expression_statement; 78 | pub use expression_statement::*; 79 | mod super_statement; 80 | pub use super_statement::*; 81 | mod block; 82 | pub use block::*; 83 | mod labeled_statement; 84 | pub use labeled_statement::*; 85 | mod if_statement; 86 | pub use if_statement::*; 87 | mod switch_statement; 88 | pub use switch_statement::*; 89 | mod do_statement; 90 | pub use do_statement::*; 91 | mod while_statement; 92 | pub use while_statement::*; 93 | mod for_statement; 94 | pub use for_statement::*; 95 | mod continue_statement; 96 | pub use continue_statement::*; 97 | mod break_statement; 98 | pub use break_statement::*; 99 | mod with_statement; 100 | pub use with_statement::*; 101 | mod return_statement; 102 | pub use return_statement::*; 103 | mod throw_statement; 104 | pub use throw_statement::*; 105 | mod try_statement; 106 | pub use try_statement::*; 107 | mod default_xml_namespace_statement; 108 | pub use default_xml_namespace_statement::*; 109 | 110 | // Directives 111 | mod directive; 112 | pub use directive::*; 113 | mod configuration_directive; 114 | pub use configuration_directive::*; 115 | mod import_directive; 116 | pub use import_directive::*; 117 | mod use_namespace_directive; 118 | pub use use_namespace_directive::*; 119 | mod include_directive; 120 | pub use include_directive::*; 121 | mod normal_configuration_directive; 122 | pub use normal_configuration_directive::*; 123 | mod package_concat_directive; 124 | pub use package_concat_directive::*; 125 | mod directive_injection_node; 126 | pub use directive_injection_node::*; 127 | 128 | // Miscellaneous 129 | mod attributes; 130 | pub use attributes::*; 131 | mod asdoc; 132 | pub use asdoc::*; 133 | mod type_parameter; 134 | pub use type_parameter::*; 135 | 136 | // Definitions 137 | mod variable_definition; 138 | pub use variable_definition::*; 139 | mod function_definition; 140 | pub use function_definition::*; 141 | mod class_definition; 142 | pub use class_definition::*; 143 | mod enum_definition; 144 | pub use enum_definition::*; 145 | mod interface_definition; 146 | pub use interface_definition::*; 147 | mod type_definition; 148 | pub use type_definition::*; 149 | mod namespace_definition; 150 | pub use namespace_definition::*; 151 | mod package_definition; 152 | pub use package_definition::*; 153 | mod program; 154 | pub use program::*; 155 | 156 | // MXML document 157 | mod mxml; 158 | pub use mxml::*; 159 | 160 | // CSS 161 | mod css; 162 | pub use css::*; 163 | 164 | mod tree_semantics; 165 | pub use tree_semantics::*; -------------------------------------------------------------------------------- /crates/parser/tree/directive.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// Directive attached with a source location. 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub enum Directive { 7 | EmptyStatement(EmptyStatement), 8 | ExpressionStatement(ExpressionStatement), 9 | SuperStatement(SuperStatement), 10 | Block(Block), 11 | LabeledStatement(LabeledStatement), 12 | IfStatement(IfStatement), 13 | SwitchStatement(SwitchStatement), 14 | SwitchTypeStatement(SwitchTypeStatement), 15 | DoStatement(DoStatement), 16 | WhileStatement(WhileStatement), 17 | ForStatement(ForStatement), 18 | ForInStatement(ForInStatement), 19 | BreakStatement(BreakStatement), 20 | ContinueStatement(ContinueStatement), 21 | WithStatement(WithStatement), 22 | ReturnStatement(ReturnStatement), 23 | ThrowStatement(ThrowStatement), 24 | DefaultXmlNamespaceStatement(DefaultXmlNamespaceStatement), 25 | TryStatement(TryStatement), 26 | Invalidated(InvalidatedNode), 27 | ConfigurationDirective(ConfigurationDirective), 28 | ImportDirective(ImportDirective), 29 | UseNamespaceDirective(UseNamespaceDirective), 30 | IncludeDirective(IncludeDirective), 31 | NormalConfigurationDirective(NormalConfigurationDirective), 32 | PackageConcatDirective(PackageConcatDirective), 33 | DirectiveInjection(DirectiveInjectionNode), 34 | VariableDefinition(VariableDefinition), 35 | FunctionDefinition(FunctionDefinition), 36 | ClassDefinition(ClassDefinition), 37 | EnumDefinition(EnumDefinition), 38 | InterfaceDefinition(InterfaceDefinition), 39 | TypeDefinition(TypeDefinition), 40 | NamespaceDefinition(NamespaceDefinition), 41 | } 42 | 43 | impl Directive { 44 | pub fn location(&self) -> Location { 45 | match self { 46 | Self::EmptyStatement(d) => d.location.clone(), 47 | Self::ExpressionStatement(d) => d.location.clone(), 48 | Self::SuperStatement(d) => d.location.clone(), 49 | Self::Block(d) => d.location.clone(), 50 | Self::LabeledStatement(d) => d.location.clone(), 51 | Self::IfStatement(d) => d.location.clone(), 52 | Self::SwitchStatement(d) => d.location.clone(), 53 | Self::SwitchTypeStatement(d) => d.location.clone(), 54 | Self::DoStatement(d) => d.location.clone(), 55 | Self::WhileStatement(d) => d.location.clone(), 56 | Self::ForStatement(d) => d.location.clone(), 57 | Self::ForInStatement(d) => d.location.clone(), 58 | Self::BreakStatement(d) => d.location.clone(), 59 | Self::ContinueStatement(d) => d.location.clone(), 60 | Self::WithStatement(d) => d.location.clone(), 61 | Self::ReturnStatement(d) => d.location.clone(), 62 | Self::ThrowStatement(d) => d.location.clone(), 63 | Self::DefaultXmlNamespaceStatement(d) => d.location.clone(), 64 | Self::TryStatement(d) => d.location.clone(), 65 | Self::Invalidated(d) => d.location.clone(), 66 | Self::ConfigurationDirective(d) => d.location.clone(), 67 | Self::ImportDirective(d) => d.location.clone(), 68 | Self::UseNamespaceDirective(d) => d.location.clone(), 69 | Self::IncludeDirective(d) => d.location.clone(), 70 | Self::NormalConfigurationDirective(d) => d.location.clone(), 71 | Self::PackageConcatDirective(d) => d.location.clone(), 72 | Self::DirectiveInjection(d) => d.location.clone(), 73 | Self::VariableDefinition(d) => d.location.clone(), 74 | Self::FunctionDefinition(d) => d.location.clone(), 75 | Self::ClassDefinition(d) => d.location.clone(), 76 | Self::EnumDefinition(d) => d.location.clone(), 77 | Self::InterfaceDefinition(d) => d.location.clone(), 78 | Self::TypeDefinition(d) => d.location.clone(), 79 | Self::NamespaceDefinition(d) => d.location.clone(), 80 | } 81 | } 82 | 83 | #[inline(always)] 84 | pub fn is_statement(&self) -> bool { 85 | !self.is_directive() 86 | } 87 | 88 | pub fn is_directive(&self) -> bool { 89 | matches!( 90 | self, 91 | Self::ConfigurationDirective(_) | 92 | Self::ImportDirective(_) | 93 | Self::UseNamespaceDirective(_) | 94 | Self::IncludeDirective(_) | 95 | Self::NormalConfigurationDirective(_) | 96 | Self::PackageConcatDirective(_) | 97 | Self::VariableDefinition(_) | 98 | Self::FunctionDefinition(_) | 99 | Self::ClassDefinition(_) | 100 | Self::EnumDefinition(_) | 101 | Self::InterfaceDefinition(_) | 102 | Self::TypeDefinition(_) | 103 | Self::NamespaceDefinition(_) 104 | ) 105 | } 106 | } -------------------------------------------------------------------------------- /crates/parser/util/character_reader.rs: -------------------------------------------------------------------------------- 1 | use std::str::CharIndices; 2 | 3 | /// `CharacterReader` may be used for iterating characters from left-to-right 4 | /// in a string with miscellaneous operation methods. 5 | #[derive(Clone)] 6 | pub struct CharacterReader<'a> { 7 | length: usize, 8 | start_offset: usize, 9 | char_indices: CharIndices<'a>, 10 | } 11 | 12 | impl<'a> CharacterReader<'a> { 13 | /// Constructs a `CharacterReader` from a string starting at the given offset. 14 | pub fn from_offset(value: &'a str, offset: usize) -> Self { 15 | CharacterReader { length: value.len(), start_offset: offset, char_indices: value[offset..].char_indices() } 16 | } 17 | 18 | /// Indicates if there are remaining code points to read. 19 | pub fn has_remaining(&self) -> bool { 20 | self.clone().char_indices.next().is_some() 21 | } 22 | 23 | /// Indicates if the reader has reached the end of the string. 24 | pub fn reached_end(&self) -> bool { 25 | self.clone().char_indices.next().is_none() 26 | } 27 | 28 | /// Skips a code point in the reader. This is equivalent to 29 | /// calling `next()`. 30 | pub fn skip_in_place(&mut self) { 31 | self.next(); 32 | } 33 | 34 | /// Skips the given number of code points in the reader. 35 | pub fn skip_count_in_place(&mut self, count: usize) { 36 | for _ in 0..count { 37 | if self.next().is_none() { 38 | break; 39 | } 40 | } 41 | } 42 | 43 | /// Returns the current byte offset in the string. 44 | pub fn index(&self) -> usize { 45 | self.clone().char_indices.next().map_or(self.length, |(i, _)| self.start_offset + i) 46 | } 47 | 48 | /// Returns the next code point. If there are no code points 49 | /// available, returns U+00. 50 | pub fn next_or_zero(&mut self) -> char { 51 | self.char_indices.next().map_or('\x00', |(_, cp)| cp) 52 | } 53 | 54 | /// Peeks the next code point. 55 | pub fn peek(&self) -> Option { 56 | self.clone().char_indices.next().map(|(_, cp)| cp) 57 | } 58 | 59 | /// Peeks the next code point. If there are no code points 60 | /// available, returns U+00. 61 | pub fn peek_or_zero(&self) -> char { 62 | self.clone().next_or_zero() 63 | } 64 | 65 | /// Peeks the next code point at the given zero based code point index. 66 | pub fn peek_at(&self, index: usize) -> Option { 67 | let mut indices = self.clone().char_indices; 68 | for _ in 0..index { 69 | if indices.next().is_none() { 70 | break; 71 | } 72 | } 73 | indices.next().map(|(_, cp)| cp) 74 | } 75 | 76 | /// Peeks the next code point at the given zero based code point index. 77 | /// If there are no code points available, returns U+00. 78 | pub fn peek_at_or_zero(&self, index: usize) -> char { 79 | self.peek_at(index).unwrap_or('\x00') 80 | } 81 | 82 | /// Peeks a number of code points until the string's end. 83 | pub fn peek_seq(&self, num_code_points: u64) -> String { 84 | let mut r = String::new(); 85 | let mut next_indices = self.char_indices.clone(); 86 | for _ in 0..num_code_points { 87 | match next_indices.next() { 88 | None => { 89 | break; 90 | }, 91 | Some(cp) => { 92 | r.push(cp.1); 93 | } 94 | } 95 | } 96 | r 97 | } 98 | } 99 | 100 | impl<'a> From<&'a str> for CharacterReader<'a> { 101 | /// Constructs a `CharacterReader` from a string. 102 | fn from(value: &'a str) -> Self { 103 | CharacterReader { length: value.len(), start_offset: 0, char_indices: value.char_indices() } 104 | } 105 | } 106 | 107 | impl<'a> From<&'a String> for CharacterReader<'a> { 108 | /// Constructs a `CharacterReader` from a string. 109 | fn from(value: &'a String) -> Self { 110 | CharacterReader { length: value.len(), start_offset: 0, char_indices: value.char_indices() } 111 | } 112 | } 113 | 114 | impl<'a> Iterator for CharacterReader<'a> { 115 | type Item = char; 116 | 117 | fn next(&mut self) -> Option { 118 | self.char_indices.next().map(|(_, cp)| cp) 119 | } 120 | } 121 | 122 | #[cfg(test)] 123 | mod test { 124 | use super::CharacterReader; 125 | #[test] 126 | fn test() { 127 | let mut reader = CharacterReader::from("foo"); 128 | assert!(reader.has_remaining()); 129 | assert_eq!(reader.peek_seq(5), "foo"); 130 | assert_eq!(reader.peek_seq(1), "f"); 131 | assert_eq!(reader.peek_or_zero(), 'f'); 132 | for _ in 0..3 { 133 | reader.next(); 134 | } 135 | assert_eq!(reader.peek_or_zero(), '\x00'); 136 | assert!(reader.reached_end()); 137 | } 138 | } -------------------------------------------------------------------------------- /demo/ace/mode-json.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/mode/json_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"variable",regex:'["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]\\s*(?=:)'},{token:"string",regex:'"',next:"string"},{token:"constant.numeric",regex:"0[xX][0-9a-fA-F]+\\b"},{token:"constant.numeric",regex:"[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"},{token:"constant.language.boolean",regex:"(?:true|false)\\b"},{token:"text",regex:"['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"},{token:"comment",regex:"\\/\\/.*$"},{token:"comment.start",regex:"\\/\\*",next:"comment"},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"},{token:"punctuation.operator",regex:/[,]/},{token:"text",regex:"\\s+"}],string:[{token:"constant.language.escape",regex:/\\(?:x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|["\\\/bfnrt])/},{token:"string",regex:'"|$',next:"start"},{defaultToken:"string"}],comment:[{token:"comment.end",regex:"\\*\\/",next:"start"},{defaultToken:"comment"}]}};r.inherits(s,i),t.JsonHighlightRules=s}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/([\{\[\(])[^\}\]\)]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{\(]*([\}\]\)])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),ace.define("ace/mode/json",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/json_highlight_rules","ace/mode/matching_brace_outdent","ace/mode/folding/cstyle","ace/worker/worker_client"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./json_highlight_rules").JsonHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("./folding/cstyle").FoldMode,a=e("../worker/worker_client").WorkerClient,f=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=this.$defaultBehaviour,this.foldingRules=new u};r.inherits(f,i),function(){this.lineCommentStart="//",this.blockComment={start:"/*",end:"*/"},this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t);if(e=="start"){var i=t.match(/^.*[\{\(\[]\s*$/);i&&(r+=n)}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.createWorker=function(e){var t=new a(["ace"],"ace/mode/json_worker","JsonWorker");return t.attachToDocument(e.getDocument()),t.on("annotate",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/json"}.call(f.prototype),t.Mode=f}); (function() { 2 | ace.require(["ace/mode/json"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ActionScript 3 Parser Demo 7 | 8 | 9 | 10 |
11 | 30 |
31 | 32 | 115 | 116 | -------------------------------------------------------------------------------- /crates/parser/parser/character_validator.rs: -------------------------------------------------------------------------------- 1 | //use lazy_regex::{Lazy, Regex, lazy_regex}; 2 | use unicode_general_category::{get_general_category, GeneralCategory}; 3 | 4 | // pub(crate) static CR_OR_CRLF_REGEX: Lazy = lazy_regex!(r"\r\n?"); 5 | 6 | /// The `CharacterValidator` structure defines static methods for character 7 | /// validation. 8 | pub struct CharacterValidator; 9 | 10 | impl CharacterValidator { 11 | /// Returns the count of indentation characters in a string. 12 | pub fn indent_count(string: &str) -> usize { 13 | let mut n: usize = 0; 14 | for ch in string.chars() { 15 | if !CharacterValidator::is_whitespace(ch) { 16 | break; 17 | } 18 | n += 1; 19 | } 20 | n 21 | } 22 | 23 | pub fn is_whitespace(ch: char) -> bool { 24 | if ch == '\x20' || ch == '\x09' || ch == '\x08' 25 | || ch == '\x0C' || ch == '\u{A0}' { 26 | return true; 27 | } 28 | let category = get_general_category(ch); 29 | category == GeneralCategory::SpaceSeparator 30 | } 31 | 32 | pub fn is_line_terminator(ch: char) -> bool { 33 | ch == '\x0A' || ch == '\x0D' || ch == '\u{2028}' || ch == '\u{2029}' 34 | } 35 | 36 | pub fn is_bin_digit(ch: char) -> bool { 37 | ch == '\x30' || ch == '\x31' 38 | } 39 | 40 | pub fn is_dec_digit(ch: char) -> bool { 41 | ch >= '\x30' && ch <= '\x39' 42 | } 43 | 44 | pub fn is_hex_digit(ch: char) -> bool { 45 | CharacterValidator::is_dec_digit(ch) || (ch >= '\x41' && ch <= '\x46') || (ch >= '\x61' && ch <= '\x66') 46 | } 47 | 48 | /// Returns the mathematical value of a hexadecimal digit. 49 | pub fn hex_digit_mv(ch: char) -> Option { 50 | if ch >= 'A' && ch <= 'F' { 51 | Some((ch as u32) - 0x41 + 10) 52 | } else if ch >= 'a' && ch <= 'f' { 53 | Some((ch as u32) - 0x61 + 10) 54 | } else if ch >= '0' && ch <= '9' { 55 | Some((ch as u32) - 0x30) 56 | } else { 57 | None 58 | } 59 | } 60 | 61 | /// Returns the mathematical value of a binary digit. 62 | pub fn bin_digit_mv(ch: char) -> Option { 63 | if ch >= '0' && ch <= '1' { 64 | Some((ch as u32) - 0x30) 65 | } else { 66 | None 67 | } 68 | } 69 | 70 | pub fn is_css_identifier_start(ch: char) -> bool { 71 | (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') 72 | } 73 | 74 | pub fn is_css_identifier_part(ch: char) -> bool { 75 | (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || 76 | (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' 77 | } 78 | 79 | pub fn is_identifier_start(ch: char) -> bool { 80 | if ch == '\x5f' || ch == '\x24' { 81 | return true; 82 | } 83 | let category = get_general_category(ch); 84 | [ 85 | GeneralCategory::LowercaseLetter, 86 | GeneralCategory::UppercaseLetter, 87 | GeneralCategory::ModifierLetter, 88 | GeneralCategory::OtherLetter, 89 | GeneralCategory::TitlecaseLetter, 90 | GeneralCategory::LetterNumber, 91 | ].contains(&category) 92 | } 93 | 94 | pub fn is_identifier_part(ch: char) -> bool { 95 | if ch == '\x5f' || ch == '\x24' { 96 | return true; 97 | } 98 | let category = get_general_category(ch); 99 | [ 100 | GeneralCategory::LowercaseLetter, 101 | GeneralCategory::UppercaseLetter, 102 | GeneralCategory::ModifierLetter, 103 | GeneralCategory::OtherLetter, 104 | GeneralCategory::TitlecaseLetter, 105 | GeneralCategory::LetterNumber, 106 | GeneralCategory::NonspacingMark, 107 | GeneralCategory::SpacingMark, 108 | GeneralCategory::ConnectorPunctuation, 109 | GeneralCategory::DecimalNumber, 110 | ].contains(&category) 111 | } 112 | 113 | pub fn is_xml_name_start(ch: char) -> bool { 114 | if ch == '\x5f' || ch == ':' { 115 | return true; 116 | } 117 | let category = get_general_category(ch); 118 | [ 119 | GeneralCategory::LowercaseLetter, 120 | GeneralCategory::UppercaseLetter, 121 | GeneralCategory::ModifierLetter, 122 | GeneralCategory::OtherLetter, 123 | GeneralCategory::TitlecaseLetter, 124 | GeneralCategory::LetterNumber, 125 | ].contains(&category) 126 | } 127 | 128 | pub fn is_xml_name_part(ch: char) -> bool { 129 | if ch == '\x5f' || ch == ':' || ch == '.' || ch == '-' { 130 | return true; 131 | } 132 | let category = get_general_category(ch); 133 | [ 134 | GeneralCategory::LowercaseLetter, 135 | GeneralCategory::UppercaseLetter, 136 | GeneralCategory::ModifierLetter, 137 | GeneralCategory::OtherLetter, 138 | GeneralCategory::TitlecaseLetter, 139 | GeneralCategory::LetterNumber, 140 | GeneralCategory::DecimalNumber, 141 | ].contains(&category) 142 | } 143 | 144 | pub fn is_xml_whitespace(ch: char) -> bool { 145 | ch == '\x20' || ch == '\x09' || ch == '\x0A' || ch == '\x0D' 146 | } 147 | } -------------------------------------------------------------------------------- /crates/parser/diagnostics/diagnostics.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use maplit::hashmap; 4 | use crate::ns::*; 5 | 6 | #[path = "diagnostics_english_resources.rs"] 7 | mod diagnostics_english_resources; 8 | 9 | /// Represents a diagnostic originated from a compilation unit. 10 | /// 11 | /// Arguments are formatted using integer keys counted from 1 (one). 12 | #[derive(Clone)] 13 | pub struct Diagnostic { 14 | pub(crate) location: Location, 15 | pub(crate) kind: DiagnosticKind, 16 | pub(crate) is_warning: bool, 17 | pub(crate) is_verify_error: bool, 18 | pub(crate) arguments: Vec>, 19 | pub(crate) custom_kind: RefCell>>, 20 | } 21 | 22 | impl Eq for Diagnostic {} 23 | 24 | impl PartialEq for Diagnostic { 25 | fn eq(&self, other: &Self) -> bool { 26 | self.location == other.location && 27 | self.kind == other.kind 28 | } 29 | } 30 | 31 | impl Ord for Diagnostic { 32 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 33 | self.location.cmp(&other.location) 34 | } 35 | } 36 | 37 | impl PartialOrd for Diagnostic { 38 | fn partial_cmp(&self, other: &Self) -> Option { 39 | self.location.partial_cmp(&other.location) 40 | } 41 | } 42 | 43 | impl Diagnostic { 44 | pub fn new_syntax_error(location: &Location, kind: DiagnosticKind, arguments: Vec>) -> Self { 45 | Self { 46 | location: location.clone(), 47 | kind, 48 | is_verify_error: false, 49 | is_warning: false, 50 | arguments, 51 | custom_kind: RefCell::new(None), 52 | } 53 | } 54 | 55 | pub fn new_verify_error(location: &Location, kind: DiagnosticKind, arguments: Vec>) -> Self { 56 | Self { 57 | location: location.clone(), 58 | kind, 59 | is_verify_error: true, 60 | is_warning: false, 61 | arguments, 62 | custom_kind: RefCell::new(None), 63 | } 64 | } 65 | 66 | pub fn new_warning(location: &Location, kind: DiagnosticKind, arguments: Vec>) -> Self { 67 | Self { 68 | location: location.clone(), 69 | kind, 70 | is_verify_error: false, 71 | is_warning: true, 72 | arguments, 73 | custom_kind: RefCell::new(None), 74 | } 75 | } 76 | 77 | pub fn location(&self) -> Location { 78 | self.location.clone() 79 | } 80 | 81 | pub fn kind(&self) -> DiagnosticKind { 82 | self.kind.clone() 83 | } 84 | 85 | pub fn is_warning(&self) -> bool { 86 | self.is_warning 87 | } 88 | 89 | pub fn is_error(&self) -> bool { 90 | !self.is_warning 91 | } 92 | 93 | pub fn is_syntax_error(&self) -> bool { 94 | !self.is_verify_error && !self.is_warning 95 | } 96 | 97 | pub fn is_verify_error(&self) -> bool { 98 | self.is_verify_error 99 | } 100 | 101 | pub fn arguments(&self) -> Vec> { 102 | self.arguments.clone() 103 | } 104 | 105 | pub fn id(&self) -> i32 { 106 | self.kind.id() 107 | } 108 | 109 | pub fn custom_kind(&self) -> Option> { 110 | self.custom_kind.borrow().clone() 111 | } 112 | 113 | pub fn set_custom_kind(&self, id: Option>) { 114 | self.custom_kind.replace(id); 115 | } 116 | 117 | /// Formats the diagnostic by overriding the message text. 118 | pub fn format_with_message(&self, message: &str, id: Option) -> String { 119 | let category = (if self.is_verify_error { 120 | "Verify error" 121 | } else if self.is_warning { 122 | "Warning" 123 | } else { 124 | "Syntax error" 125 | }).to_owned(); 126 | 127 | let file_path = self.location.compilation_unit.file_path.clone().map_or("".to_owned(), |s| format!("{s}:")); 128 | let line = self.location.first_line_number(); 129 | let column = self.location.first_column() + 1; 130 | if let Some(id) = id { 131 | format!("{file_path}{line}:{column}: {category} #{}: {message}", id.to_string()) 132 | } else { 133 | format!("{file_path}{line}:{column}: {category}: {message}") 134 | } 135 | } 136 | 137 | /// Formats the diagnostic in English. 138 | pub fn format_english(&self) -> String { 139 | self.format_with_message(&self.format_message_english(), Some(self.id())) 140 | } 141 | 142 | pub fn format_message_english(&self) -> String { 143 | self.format_message(&diagnostics_english_resources::DATA) 144 | } 145 | 146 | pub fn format_message(&self, messages: &HashMap) -> String { 147 | let mut string_arguments: HashMap = hashmap!{}; 148 | let mut i = 1; 149 | for argument in &self.arguments { 150 | string_arguments.insert(i.to_string(), argument.to_string()); 151 | i += 1; 152 | } 153 | use late_format::LateFormat; 154 | let Some(msg) = messages.get(&self.id()) else { 155 | let id = self.id(); 156 | panic!("Message resource is missing for ID {id}"); 157 | }; 158 | msg.late_format(string_arguments) 159 | } 160 | } 161 | 162 | /// The `diagarg![...]` literal is used for initializing 163 | /// diagnostic arguments. 164 | /// 165 | /// For example: `diagarg![token, "foo".to_owned()]`. 166 | pub macro diagarg { 167 | ($($value:expr),*) => { vec![ $(Rc::new($value)),* ] }, 168 | } 169 | 170 | pub trait DiagnosticArgument: Any + ToString + 'static { 171 | } 172 | 173 | impl DiagnosticArgument for String {} 174 | 175 | impl DiagnosticArgument for Token {} -------------------------------------------------------------------------------- /tests/parser/css/selectors.tree: -------------------------------------------------------------------------------- 1 | { 2 | "location": "1:1-9:24", 3 | "directives": [ 4 | { 5 | "Rule": { 6 | "location": "1:1-3:2", 7 | "selectors": [ 8 | { 9 | "Combinator": { 10 | "location": "1:1-1:34", 11 | "left": { 12 | "Base": { 13 | "location": "1:1-1:25", 14 | "namespace_prefix": null, 15 | "element_name": null, 16 | "conditions": [ 17 | { 18 | "Class": [ 19 | "myStyleName1", 20 | "1:1-1:14" 21 | ] 22 | }, 23 | { 24 | "Class": [ 25 | "anotherOne", 26 | "1:14-1:25" 27 | ] 28 | } 29 | ] 30 | } 31 | }, 32 | "right": { 33 | "Base": { 34 | "location": "1:26-1:34", 35 | "namespace_prefix": [ 36 | "s", 37 | "1:26-1:27" 38 | ], 39 | "element_name": [ 40 | "VGroup", 41 | "1:28-1:34" 42 | ], 43 | "conditions": [] 44 | } 45 | }, 46 | "combinator_type": "Descendant" 47 | } 48 | }, 49 | { 50 | "Base": { 51 | "location": "1:36-1:40", 52 | "namespace_prefix": null, 53 | "element_name": null, 54 | "conditions": [ 55 | { 56 | "Id": [ 57 | "id1", 58 | "1:36-1:40" 59 | ] 60 | } 61 | ] 62 | } 63 | } 64 | ], 65 | "properties": [ 66 | { 67 | "location": "2:5-2:17", 68 | "name": [ 69 | "fontSize", 70 | "2:5-2:13" 71 | ], 72 | "value": { 73 | "Number": { 74 | "location": "2:15-2:17", 75 | "value": 24.0, 76 | "unit": null 77 | } 78 | } 79 | } 80 | ] 81 | } 82 | }, 83 | { 84 | "Rule": { 85 | "location": "5:1-7:2", 86 | "selectors": [ 87 | { 88 | "Base": { 89 | "location": "5:1-5:25", 90 | "namespace_prefix": null, 91 | "element_name": null, 92 | "conditions": [ 93 | { 94 | "Class": [ 95 | "myStyleName1", 96 | "5:1-5:14" 97 | ] 98 | }, 99 | { 100 | "Attribute": { 101 | "location": "5:14-5:25", 102 | "name": [ 103 | "attr", 104 | "5:15-5:19" 105 | ], 106 | "operator": "BeginsWith", 107 | "value": [ 108 | ".", 109 | "5:21-5:24" 110 | ] 111 | } 112 | } 113 | ] 114 | } 115 | } 116 | ], 117 | "properties": [ 118 | { 119 | "location": "6:5-6:15", 120 | "name": [ 121 | "color", 122 | "6:5-6:10" 123 | ], 124 | "value": { 125 | "Color": { 126 | "location": "6:12-6:15", 127 | "color_int": 16711680 128 | } 129 | } 130 | } 131 | ] 132 | } 133 | }, 134 | { 135 | "Rule": { 136 | "location": "9:1-9:24", 137 | "selectors": [ 138 | { 139 | "Combinator": { 140 | "location": "9:1-9:21", 141 | "left": { 142 | "Combinator": { 143 | "location": "9:1-9:14", 144 | "left": { 145 | "Base": { 146 | "location": "9:1-9:7", 147 | "namespace_prefix": null, 148 | "element_name": null, 149 | "conditions": [ 150 | { 151 | "Id": [ 152 | "myId1", 153 | "9:1-9:7" 154 | ] 155 | } 156 | ] 157 | } 158 | }, 159 | "right": { 160 | "Base": { 161 | "location": "9:8-9:14", 162 | "namespace_prefix": null, 163 | "element_name": null, 164 | "conditions": [ 165 | { 166 | "Id": [ 167 | "myId2", 168 | "9:8-9:14" 169 | ] 170 | } 171 | ] 172 | } 173 | }, 174 | "combinator_type": "Descendant" 175 | } 176 | }, 177 | "right": { 178 | "Base": { 179 | "location": "9:15-9:21", 180 | "namespace_prefix": null, 181 | "element_name": null, 182 | "conditions": [ 183 | { 184 | "Id": [ 185 | "myId3", 186 | "9:15-9:21" 187 | ] 188 | } 189 | ] 190 | } 191 | }, 192 | "combinator_type": "Descendant" 193 | } 194 | } 195 | ], 196 | "properties": [] 197 | } 198 | } 199 | ] 200 | } -------------------------------------------------------------------------------- /crates/parser/operator/operator.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | /// Represents an ActionScript operator. 5 | #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] 6 | pub enum Operator { 7 | PostIncrement, 8 | PostDecrement, 9 | NonNull, 10 | Delete, 11 | Void, 12 | Typeof, 13 | Await, 14 | Yield, 15 | PreIncrement, 16 | PreDecrement, 17 | Positive, 18 | Negative, 19 | BitwiseNot, 20 | LogicalNot, 21 | 22 | Power, 23 | Multiply, 24 | Divide, 25 | Remainder, 26 | Add, 27 | Subtract, 28 | ShiftLeft, 29 | ShiftRight, 30 | ShiftRightUnsigned, 31 | Lt, 32 | Gt, 33 | Le, 34 | Ge, 35 | Instanceof, 36 | In, 37 | NotIn, 38 | Is, 39 | IsNot, 40 | As, 41 | Equals, 42 | NotEquals, 43 | StrictEquals, 44 | StrictNotEquals, 45 | BitwiseAnd, 46 | BitwiseXor, 47 | BitwiseOr, 48 | LogicalAnd, 49 | LogicalXor, 50 | LogicalOr, 51 | NullCoalescing, 52 | } 53 | 54 | /// Represents binary operator associativity. 55 | #[derive(Copy, Clone, PartialEq, Eq)] 56 | pub enum BinaryAssociativity { 57 | LeftToRight, 58 | RightToLeft, 59 | } 60 | 61 | /// Represents an ActionScript binary operator. 62 | #[derive(Copy, Clone, PartialEq, Eq)] 63 | pub struct BinaryOperator(pub Operator, pub OperatorPrecedence, pub BinaryAssociativity); 64 | 65 | impl BinaryOperator { 66 | pub fn operator(&self) -> Operator { 67 | self.0 68 | } 69 | 70 | pub fn precedence(&self) -> OperatorPrecedence { 71 | self.1 72 | } 73 | 74 | pub fn associativity(&self) -> BinaryAssociativity { 75 | self.2 76 | } 77 | 78 | pub fn right_precedence(&self) -> OperatorPrecedence { 79 | if self.operator() == Operator::NullCoalescing { 80 | OperatorPrecedence::BitwiseOr 81 | } else { 82 | self.precedence().add(if self.associativity() == BinaryAssociativity::LeftToRight { 1 } else { 0 }).unwrap() 83 | } 84 | } 85 | } 86 | 87 | impl TryFrom for BinaryOperator { 88 | type Error = (); 89 | /// Constructs `BinaryOperator` from abstract operator. 90 | fn try_from(value: Operator) -> Result { 91 | match value { 92 | Operator::Multiply => Ok(BinaryOperator(value, OperatorPrecedence::Multiplicative, BinaryAssociativity::LeftToRight)), 93 | Operator::Divide => Ok(BinaryOperator(value, OperatorPrecedence::Multiplicative, BinaryAssociativity::LeftToRight)), 94 | Operator::Remainder => Ok(BinaryOperator(value, OperatorPrecedence::Multiplicative, BinaryAssociativity::LeftToRight)), 95 | Operator::Add => Ok(BinaryOperator(value, OperatorPrecedence::Additive, BinaryAssociativity::LeftToRight)), 96 | Operator::Subtract => Ok(BinaryOperator(value, OperatorPrecedence::Additive, BinaryAssociativity::LeftToRight)), 97 | Operator::ShiftLeft => Ok(BinaryOperator(value, OperatorPrecedence::Shift, BinaryAssociativity::LeftToRight)), 98 | Operator::ShiftRight => Ok(BinaryOperator(value, OperatorPrecedence::Shift, BinaryAssociativity::LeftToRight)), 99 | Operator::ShiftRightUnsigned => Ok(BinaryOperator(value, OperatorPrecedence::Shift, BinaryAssociativity::LeftToRight)), 100 | Operator::Lt => Ok(BinaryOperator(value, OperatorPrecedence::Relational, BinaryAssociativity::LeftToRight)), 101 | Operator::Gt => Ok(BinaryOperator(value, OperatorPrecedence::Relational, BinaryAssociativity::LeftToRight)), 102 | Operator::Le => Ok(BinaryOperator(value, OperatorPrecedence::Relational, BinaryAssociativity::LeftToRight)), 103 | Operator::Ge => Ok(BinaryOperator(value, OperatorPrecedence::Relational, BinaryAssociativity::LeftToRight)), 104 | Operator::As => Ok(BinaryOperator(value, OperatorPrecedence::Relational, BinaryAssociativity::LeftToRight)), 105 | Operator::In => Ok(BinaryOperator(value, OperatorPrecedence::Relational, BinaryAssociativity::LeftToRight)), 106 | Operator::NotIn => Ok(BinaryOperator(value, OperatorPrecedence::Relational, BinaryAssociativity::LeftToRight)), 107 | Operator::Instanceof => Ok(BinaryOperator(value, OperatorPrecedence::Relational, BinaryAssociativity::LeftToRight)), 108 | Operator::Is => Ok(BinaryOperator(value, OperatorPrecedence::Relational, BinaryAssociativity::LeftToRight)), 109 | Operator::IsNot => Ok(BinaryOperator(value, OperatorPrecedence::Relational, BinaryAssociativity::LeftToRight)), 110 | Operator::Equals => Ok(BinaryOperator(value, OperatorPrecedence::Equality, BinaryAssociativity::LeftToRight)), 111 | Operator::NotEquals => Ok(BinaryOperator(value, OperatorPrecedence::Equality, BinaryAssociativity::LeftToRight)), 112 | Operator::StrictEquals => Ok(BinaryOperator(value, OperatorPrecedence::Equality, BinaryAssociativity::LeftToRight)), 113 | Operator::StrictNotEquals => Ok(BinaryOperator(value, OperatorPrecedence::Equality, BinaryAssociativity::LeftToRight)), 114 | Operator::BitwiseAnd => Ok(BinaryOperator(value, OperatorPrecedence::BitwiseAnd, BinaryAssociativity::LeftToRight)), 115 | Operator::BitwiseXor => Ok(BinaryOperator(value, OperatorPrecedence::BitwiseXor, BinaryAssociativity::LeftToRight)), 116 | Operator::BitwiseOr => Ok(BinaryOperator(value, OperatorPrecedence::BitwiseOr, BinaryAssociativity::LeftToRight)), 117 | Operator::LogicalAnd => Ok(BinaryOperator(value, OperatorPrecedence::LogicalAnd, BinaryAssociativity::LeftToRight)), 118 | Operator::LogicalXor => Ok(BinaryOperator(value, OperatorPrecedence::LogicalXor, BinaryAssociativity::LeftToRight)), 119 | Operator::LogicalOr => Ok(BinaryOperator(value, OperatorPrecedence::LogicalOrAndOther, BinaryAssociativity::LeftToRight)), 120 | Operator::NullCoalescing => Ok(BinaryOperator(value, OperatorPrecedence::LogicalOrAndOther, BinaryAssociativity::LeftToRight)), 121 | 122 | Operator::Power => Ok(BinaryOperator(value, OperatorPrecedence::Exponentiation, BinaryAssociativity::RightToLeft)), 123 | 124 | _ => Err(()), 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /crates/parser/compilation_unit/location.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | use std::fmt::Debug; 3 | use serde::{Serialize, Deserialize, Serializer}; 4 | use std::rc::Rc; 5 | use crate::compilation_unit::*; 6 | use crate::util::{CharacterReader, count_first_whitespace_characters}; 7 | 8 | /// Represents a source location. This location includes 9 | /// spanning lines and columns and the reference compilation unit. 10 | #[derive(Clone, Deserialize)] 11 | pub struct Location { 12 | /// The compilation unit that this location belongs to. 13 | #[serde(skip)] 14 | pub(crate) compilation_unit: Rc, 15 | 16 | /// First UTF-8 offset. 17 | #[serde(skip)] 18 | pub(crate) first_offset: usize, 19 | 20 | /// Last UTF-8 offset. 21 | #[serde(skip)] 22 | pub(crate) last_offset: usize, 23 | } 24 | 25 | impl Serialize for Location { 26 | fn serialize(&self, serializer: S) -> Result 27 | where 28 | S: Serializer, 29 | { 30 | serializer.serialize_str(&format!("{}:{}-{}:{}", self.first_line_number(), self.first_column() + 1, self.last_line_number(), self.last_column() + 1)) 31 | } 32 | } 33 | 34 | impl Debug for Location { 35 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 36 | write!(f, 37 | "Location(first_line_number={}, first_column={}, first_offset={}, last_line_number={}, last_column={}, last_offset={})", 38 | self.first_line_number(), 39 | self.first_column(), 40 | self.first_offset, 41 | self.last_line_number(), 42 | self.last_column(), 43 | self.last_offset 44 | ) 45 | } 46 | } 47 | 48 | impl Eq for Location {} 49 | 50 | impl PartialEq for Location { 51 | fn eq(&self, other: &Self) -> bool { 52 | Rc::ptr_eq(&self.compilation_unit, &other.compilation_unit) && 53 | self.first_offset == other.first_offset && 54 | self.last_offset == other.last_offset 55 | } 56 | } 57 | 58 | impl Ord for Location { 59 | fn cmp(&self, other: &Self) -> Ordering { 60 | self.partial_cmp(other).unwrap_or(Ordering::Equal) 61 | } 62 | } 63 | 64 | impl PartialOrd for Location { 65 | fn partial_cmp(&self, other: &Self) -> Option { 66 | self.first_offset.partial_cmp(&other.first_offset) 67 | } 68 | } 69 | 70 | impl Location { 71 | /// Builds a location. 72 | pub fn with_offsets( 73 | compilation_unit: &Rc, 74 | first_offset: usize, 75 | last_offset: usize, 76 | ) -> Self { 77 | Self { 78 | compilation_unit: compilation_unit.clone(), 79 | first_offset, 80 | last_offset, 81 | } 82 | } 83 | 84 | /// Builds a location. 85 | pub fn with_offset(compilation_unit: &Rc, offset: usize) -> Self { 86 | Self::with_offsets(compilation_unit, offset, offset) 87 | } 88 | 89 | /// Build a location by combining two locations. `self` 90 | /// serves as the first location, while `other` serves as the 91 | /// last location. 92 | pub fn combine_with(&self, other: Location) -> Self { 93 | Self { 94 | compilation_unit: self.compilation_unit.clone(), 95 | first_offset: self.first_offset, 96 | last_offset: other.last_offset, 97 | } 98 | } 99 | 100 | /// Build a location by combining two locations. `self` 101 | /// serves as the first location, while the first column and first line 102 | /// of `other` serve as the last location. 103 | pub fn combine_with_start_of(&self, other: Location) -> Self { 104 | Self { 105 | compilation_unit: self.compilation_unit.clone(), 106 | first_offset: self.first_offset, 107 | last_offset: other.first_offset, 108 | } 109 | } 110 | 111 | /// The compilation unit that this location belongs to. 112 | pub fn compilation_unit(&self) -> Rc { 113 | self.compilation_unit.clone() 114 | } 115 | 116 | /// First line number, counted from one. 117 | pub fn first_line_number(&self) -> usize { 118 | self.compilation_unit.get_line_number(self.first_offset) 119 | } 120 | 121 | /// Last line number, counted from one. 122 | pub fn last_line_number(&self) -> usize { 123 | self.compilation_unit.get_line_number(self.last_offset) 124 | } 125 | 126 | // The first byte offset of this location. 127 | pub fn first_offset(&self) -> usize { 128 | self.first_offset 129 | } 130 | 131 | // The last byte offset of this location. 132 | pub fn last_offset(&self) -> usize { 133 | self.last_offset 134 | } 135 | 136 | /// Zero based first column of the location in code points. 137 | pub fn first_column(&self) -> usize { 138 | self.compilation_unit.get_column(self.first_offset) 139 | } 140 | 141 | /// Zero based last column of the location in code points. 142 | pub fn last_column(&self) -> usize { 143 | self.compilation_unit.get_column(self.last_offset) 144 | } 145 | 146 | pub fn character_count(&self) -> usize { 147 | self.compilation_unit.text()[self.first_offset..self.last_offset].chars().count() 148 | } 149 | 150 | /// Indicates whether a previous location and a next location 151 | /// have a line break in between. 152 | pub fn line_break(&self, other: &Self) -> bool { 153 | self.last_line_number() != other.first_line_number() 154 | } 155 | 156 | /// Returns the source text comprising the source location. 157 | pub fn text(&self) -> String { 158 | self.compilation_unit.text()[self.first_offset..self.last_offset].to_owned() 159 | } 160 | 161 | /// Shifts a count of characters off this location until end-of-file. 162 | pub fn shift_until_eof(&self, count: usize) -> Location { 163 | let mut ch = CharacterReader::from(&self.compilation_unit.text()[self.first_offset..]); 164 | for _ in 0..count { 165 | if ch.next().is_none() { 166 | break; 167 | } 168 | } 169 | Self::with_offsets(&self.compilation_unit, self.first_offset + ch.index(), self.last_offset) 170 | } 171 | 172 | /// Shifts the count of whitespace characters in a text off this location. 173 | pub fn shift_whitespace(&self, text: &str) -> Location { 174 | self.shift_until_eof(count_first_whitespace_characters(text)) 175 | } 176 | } -------------------------------------------------------------------------------- /tests/parser/MXML1.tree: -------------------------------------------------------------------------------- 1 | { 2 | "location": "1:1-16:17", 3 | "version": "Version10", 4 | "encoding": "utf-8", 5 | "content": [ 6 | { 7 | "ProcessingInstruction": { 8 | "location": "1:1-1:22", 9 | "name": "xml", 10 | "data": " version=\"1.0\"" 11 | } 12 | }, 13 | { 14 | "Comment": [ 15 | "", 16 | "2:1-2:37" 17 | ] 18 | }, 19 | { 20 | "Element": { 21 | "location": "3:2-16:17", 22 | "name": { 23 | "location": "3:2-3:15", 24 | "prefix": "s", 25 | "name": "Application" 26 | }, 27 | "attributes": [ 28 | { 29 | "location": "3:16-3:56", 30 | "xmlns": true, 31 | "name": { 32 | "location": "3:16-3:24", 33 | "prefix": "xmlns", 34 | "name": "fx" 35 | }, 36 | "value": [ 37 | "http://ns.adobe.com/mxml/2009", 38 | "3:25-3:56" 39 | ] 40 | }, 41 | { 42 | "location": "4:5-4:48", 43 | "xmlns": true, 44 | "name": { 45 | "location": "4:5-4:12", 46 | "prefix": "xmlns", 47 | "name": "s" 48 | }, 49 | "value": [ 50 | "library://ns.adobe.com/flex/spark", 51 | "4:13-4:48" 52 | ] 53 | }, 54 | { 55 | "location": "5:5-5:46", 56 | "xmlns": true, 57 | "name": { 58 | "location": "5:5-5:13", 59 | "prefix": "xmlns", 60 | "name": "mx" 61 | }, 62 | "value": [ 63 | "library://ns.adobe.com/flex/mx", 64 | "5:14-5:46" 65 | ] 66 | } 67 | ], 68 | "content": [ 69 | { 70 | "Element": { 71 | "location": "6:6-8:16", 72 | "name": { 73 | "location": "6:6-6:14", 74 | "prefix": "s", 75 | "name": "layout" 76 | }, 77 | "attributes": [], 78 | "content": [ 79 | { 80 | "Element": { 81 | "location": "7:10-7:28", 82 | "name": { 83 | "location": "7:10-7:26", 84 | "prefix": "s", 85 | "name": "VerticalLayout" 86 | }, 87 | "attributes": [], 88 | "content": null, 89 | "closing_name": null 90 | } 91 | } 92 | ], 93 | "closing_name": { 94 | "location": "8:7-8:15", 95 | "prefix": "s", 96 | "name": "layout" 97 | } 98 | } 99 | }, 100 | { 101 | "Element": { 102 | "location": "9:6-14:65", 103 | "name": { 104 | "location": "9:6-9:14", 105 | "prefix": "s", 106 | "name": "Button" 107 | }, 108 | "attributes": [ 109 | { 110 | "location": "10:9-10:21", 111 | "xmlns": false, 112 | "name": { 113 | "location": "10:9-10:11", 114 | "prefix": null, 115 | "name": "id" 116 | }, 117 | "value": [ 118 | "button1", 119 | "10:12-10:21" 120 | ] 121 | }, 122 | { 123 | "location": "11:9-11:28", 124 | "xmlns": false, 125 | "name": { 126 | "location": "11:9-11:14", 127 | "prefix": null, 128 | "name": "label" 129 | }, 130 | "value": [ 131 | "Click here!", 132 | "11:15-11:28" 133 | ] 134 | }, 135 | { 136 | "location": "12:9-12:20", 137 | "xmlns": false, 138 | "name": { 139 | "location": "12:9-12:14", 140 | "prefix": null, 141 | "name": "width" 142 | }, 143 | "value": [ 144 | "100", 145 | "12:15-12:20" 146 | ] 147 | }, 148 | { 149 | "location": "13:9-13:22", 150 | "xmlns": false, 151 | "name": { 152 | "location": "13:9-13:17", 153 | "prefix": null, 154 | "name": "fontSize" 155 | }, 156 | "value": [ 157 | "12", 158 | "13:18-13:22" 159 | ] 160 | }, 161 | { 162 | "location": "14:9-14:63", 163 | "xmlns": false, 164 | "name": { 165 | "location": "14:9-14:14", 166 | "prefix": null, 167 | "name": "click" 168 | }, 169 | "value": [ 170 | "text1.text='&& Thanks for the click!';", 171 | "14:15-14:63" 172 | ] 173 | } 174 | ], 175 | "content": null, 176 | "closing_name": null 177 | } 178 | }, 179 | { 180 | "Element": { 181 | "location": "15:6-15:29", 182 | "name": { 183 | "location": "15:6-15:16", 184 | "prefix": "s", 185 | "name": "TextArea" 186 | }, 187 | "attributes": [ 188 | { 189 | "location": "15:17-15:27", 190 | "xmlns": false, 191 | "name": { 192 | "location": "15:17-15:19", 193 | "prefix": null, 194 | "name": "id" 195 | }, 196 | "value": [ 197 | "text1", 198 | "15:20-15:27" 199 | ] 200 | } 201 | ], 202 | "content": null, 203 | "closing_name": null 204 | } 205 | } 206 | ], 207 | "closing_name": { 208 | "location": "16:3-16:16", 209 | "prefix": "s", 210 | "name": "Application" 211 | } 212 | } 213 | } 214 | ] 215 | } -------------------------------------------------------------------------------- /crates/parser/parser/context.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | 3 | /// Context used to control the parsing of an expression. 4 | #[derive(Clone)] 5 | pub struct ParserExpressionContext { 6 | pub min_precedence: OperatorPrecedence, 7 | pub allow_in: bool, 8 | pub allow_assignment: bool, 9 | } 10 | 11 | impl Default for ParserExpressionContext { 12 | fn default() -> Self { 13 | Self { 14 | min_precedence: OperatorPrecedence::List, 15 | allow_in: true, 16 | allow_assignment: true, 17 | } 18 | } 19 | } 20 | 21 | #[derive(Clone)] 22 | pub enum ParserDirectiveContext { 23 | Default, 24 | TopLevel, 25 | PackageBlock, 26 | ClassBlock { 27 | name: String, 28 | }, 29 | InterfaceBlock, 30 | EnumBlock, 31 | ConstructorBlock { 32 | super_statement_found: Rc>, 33 | }, 34 | WithControl { 35 | super_statement_found: Option>>, 36 | to_be_labeled: Option, 37 | control_context: ParserControlFlowContext, 38 | labels: HashMap, 39 | }, 40 | } 41 | 42 | impl ParserDirectiveContext { 43 | pub fn may_contain_super_statement(&self) -> bool { 44 | matches!(self, Self::ConstructorBlock { .. }) || matches!(self, Self::WithControl { .. }) 45 | } 46 | 47 | pub fn super_statement_found(&self) -> bool { 48 | match self { 49 | Self::ConstructorBlock { super_statement_found } => super_statement_found.get(), 50 | Self::WithControl { super_statement_found, .. } => super_statement_found.as_ref().or(Some(&Rc::new(Cell::new(false)))).unwrap().get(), 51 | _ => false, 52 | } 53 | } 54 | 55 | pub fn set_super_statement_found(&self, value: bool) { 56 | match self { 57 | Self::ConstructorBlock { super_statement_found } => { super_statement_found.set(value) }, 58 | Self::WithControl { super_statement_found, .. } => { 59 | if let Some(found) = super_statement_found.as_ref() { 60 | found.set(value); 61 | } 62 | }, 63 | _ => {}, 64 | } 65 | } 66 | 67 | pub fn function_name_is_constructor(&self, name: &(String, Location)) -> bool { 68 | if let ParserDirectiveContext::ClassBlock { name: ref name_1 } = self { 69 | &name.0 == name_1 70 | } else { 71 | false 72 | } 73 | } 74 | 75 | pub fn is_top_level_or_package(&self) -> bool { 76 | matches!(self, ParserDirectiveContext::TopLevel) || matches!(self, ParserDirectiveContext::PackageBlock) 77 | } 78 | 79 | pub fn is_type_block(&self) -> bool { 80 | match self { 81 | Self::ClassBlock { .. } | 82 | Self::InterfaceBlock | 83 | Self::EnumBlock => true, 84 | _ => false, 85 | } 86 | } 87 | 88 | pub fn clone_control(&self) -> Self { 89 | match self { 90 | Self::WithControl { .. } => self.clone(), 91 | _ => Self::Default, 92 | } 93 | } 94 | 95 | pub fn override_control_context(&self, label_only: bool, mut context: ParserControlFlowContext) -> Self { 96 | let mut prev_context = None; 97 | let mut label = None; 98 | let mut super_statement_found: Option>> = None; 99 | let mut labels = match self { 100 | Self::WithControl { control_context, labels, to_be_labeled: label1, super_statement_found: super_found_1 } => { 101 | prev_context = Some(control_context.clone()); 102 | label = label1.clone(); 103 | super_statement_found = super_found_1.clone(); 104 | labels.clone() 105 | }, 106 | _ => HashMap::new(), 107 | }; 108 | if let Some(label) = label.clone() { 109 | labels.insert(label, context.clone()); 110 | } 111 | if label_only { 112 | context = prev_context.unwrap_or(ParserControlFlowContext { 113 | breakable: false, 114 | iteration: false, 115 | }); 116 | } 117 | Self::WithControl { control_context: context, labels, to_be_labeled: None, super_statement_found } 118 | } 119 | 120 | pub fn put_label(&self, label: String) -> Self { 121 | match self { 122 | Self::WithControl { control_context, labels, to_be_labeled: _, super_statement_found } => Self::WithControl { 123 | to_be_labeled: Some(label), 124 | control_context: control_context.clone(), 125 | labels: labels.clone(), 126 | super_statement_found: super_statement_found.clone(), 127 | }, 128 | _ => Self::WithControl { 129 | to_be_labeled: Some(label), 130 | control_context: ParserControlFlowContext { 131 | breakable: false, 132 | iteration: false, 133 | }, 134 | labels: HashMap::new(), 135 | super_statement_found: match self { 136 | Self::ConstructorBlock { super_statement_found } => Some(super_statement_found.clone()), 137 | _ => None, 138 | }, 139 | }, 140 | } 141 | } 142 | 143 | pub fn is_label_defined(&self, label: String) -> bool { 144 | self.resolve_label(label).is_some() 145 | } 146 | 147 | pub fn resolve_label(&self, label: String) -> Option { 148 | if let Self::WithControl { labels, .. } = &self { labels.get(&label).map(|c| c.clone()) } else { None } 149 | } 150 | 151 | pub fn is_break_allowed(&self, label: Option) -> bool { 152 | if let Some(label) = label { 153 | let context = self.resolve_label(label); 154 | if let Some(context) = context { context.breakable } else { false } 155 | } else { 156 | if let Self::WithControl { control_context, .. } = &self { control_context.breakable } else { false } 157 | } 158 | } 159 | 160 | pub fn is_continue_allowed(&self, label: Option) -> bool { 161 | if let Some(label) = label { 162 | let context = self.resolve_label(label); 163 | if let Some(context) = context { context.iteration } else { false } 164 | } else { 165 | if let Self::WithControl { control_context, .. } = &self { control_context.iteration } else { false } 166 | } 167 | } 168 | } 169 | 170 | #[derive(Clone)] 171 | pub struct ParserControlFlowContext { 172 | pub breakable: bool, 173 | pub iteration: bool, 174 | } -------------------------------------------------------------------------------- /tests/parser/ASDoc.tree: -------------------------------------------------------------------------------- 1 | { 2 | "location": "7:1-24:2", 3 | "packages": [], 4 | "directives": [ 5 | { 6 | "VariableDefinition": { 7 | "location": "7:1-7:6", 8 | "asdoc": { 9 | "location": "1:1-6:4", 10 | "main_body": [ 11 | "Main body.", 12 | "2:4-2:14" 13 | ], 14 | "tags": [ 15 | [ 16 | { 17 | "Copy": { 18 | "base": { 19 | "QualifiedIdentifier": { 20 | "location": "3:10-3:12", 21 | "attribute": false, 22 | "qualifier": null, 23 | "id": { 24 | "Id": [ 25 | "C1", 26 | "3:10-3:12" 27 | ] 28 | } 29 | } 30 | }, 31 | "instance_property": { 32 | "location": "3:13-3:14", 33 | "attribute": false, 34 | "qualifier": null, 35 | "id": { 36 | "Id": [ 37 | "x", 38 | "3:13-3:14" 39 | ] 40 | } 41 | } 42 | } 43 | }, 44 | "3:4-3:14" 45 | ], 46 | [ 47 | "InheritDoc", 48 | "4:4-4:15" 49 | ], 50 | [ 51 | { 52 | "See": { 53 | "reference": { 54 | "base": { 55 | "QualifiedIdentifier": { 56 | "location": "5:9-5:11", 57 | "attribute": false, 58 | "qualifier": null, 59 | "id": { 60 | "Id": [ 61 | "C1", 62 | "5:9-5:11" 63 | ] 64 | } 65 | } 66 | }, 67 | "instance_property": { 68 | "location": "5:12-5:13", 69 | "attribute": false, 70 | "qualifier": null, 71 | "id": { 72 | "Id": [ 73 | "x", 74 | "5:12-5:13" 75 | ] 76 | } 77 | } 78 | }, 79 | "display_text": "X description" 80 | } 81 | }, 82 | "5:4-5:27" 83 | ] 84 | ] 85 | }, 86 | "attributes": [], 87 | "kind": [ 88 | "Var", 89 | "7:1-7:4" 90 | ], 91 | "bindings": [ 92 | { 93 | "destructuring": { 94 | "location": "7:5-7:6", 95 | "destructuring": { 96 | "QualifiedIdentifier": { 97 | "location": "7:5-7:6", 98 | "attribute": false, 99 | "qualifier": null, 100 | "id": { 101 | "Id": [ 102 | "x", 103 | "7:5-7:6" 104 | ] 105 | } 106 | } 107 | }, 108 | "type_annotation": null 109 | }, 110 | "initializer": null 111 | } 112 | ] 113 | } 114 | }, 115 | { 116 | "VariableDefinition": { 117 | "location": "12:1-18:6", 118 | "asdoc": { 119 | "location": "13:1-16:4", 120 | "main_body": [ 121 | "N2. Lorem\nipsum.", 122 | "14:4-15:10" 123 | ], 124 | "tags": [] 125 | }, 126 | "attributes": [ 127 | { 128 | "Metadata": { 129 | "location": "12:2-12:4", 130 | "asdoc": null, 131 | "name": [ 132 | "N1", 133 | "12:2-12:4" 134 | ], 135 | "entries": null 136 | } 137 | }, 138 | { 139 | "Metadata": { 140 | "location": "17:2-17:4", 141 | "asdoc": null, 142 | "name": [ 143 | "N2", 144 | "17:2-17:4" 145 | ], 146 | "entries": null 147 | } 148 | } 149 | ], 150 | "kind": [ 151 | "Var", 152 | "18:1-18:4" 153 | ], 154 | "bindings": [ 155 | { 156 | "destructuring": { 157 | "location": "18:5-18:6", 158 | "destructuring": { 159 | "QualifiedIdentifier": { 160 | "location": "18:5-18:6", 161 | "attribute": false, 162 | "qualifier": null, 163 | "id": { 164 | "Id": [ 165 | "y", 166 | "18:5-18:6" 167 | ] 168 | } 169 | } 170 | }, 171 | "type_annotation": null 172 | }, 173 | "initializer": null 174 | } 175 | ] 176 | } 177 | }, 178 | { 179 | "FunctionDefinition": { 180 | "location": "23:1-24:2", 181 | "asdoc": { 182 | "location": "20:1-22:4", 183 | "main_body": null, 184 | "tags": [ 185 | [ 186 | { 187 | "Throws": { 188 | "class_reference": { 189 | "QualifiedIdentifier": { 190 | "location": "21:12-21:23", 191 | "attribute": false, 192 | "qualifier": null, 193 | "id": { 194 | "Id": [ 195 | "SyntaxError", 196 | "21:12-21:23" 197 | ] 198 | } 199 | } 200 | }, 201 | "description": "If a syntax error occurs." 202 | } 203 | }, 204 | "21:4-21:49" 205 | ] 206 | ] 207 | }, 208 | "attributes": [], 209 | "name": { 210 | "Identifier": [ 211 | "m", 212 | "23:10-23:11" 213 | ] 214 | }, 215 | "common": { 216 | "location": "23:11-24:2", 217 | "contains_yield": false, 218 | "contains_await": false, 219 | "signature": { 220 | "location": "23:11-23:13", 221 | "parameters": [], 222 | "result_type": null 223 | }, 224 | "body": { 225 | "Block": { 226 | "location": "23:14-24:2", 227 | "metadata": null, 228 | "directives": [] 229 | } 230 | } 231 | } 232 | } 233 | } 234 | ] 235 | } -------------------------------------------------------------------------------- /crates/parser/util/css.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use lazy_static::lazy_static; 3 | use maplit::hashmap; 4 | 5 | /// Converts a CSS [color constant](http://www.w3schools.com/css/css_colorsfull.asp) into an integer. 6 | pub fn css_color_constant_to_int(name: &str) -> Option { 7 | COLOR_MAP.get(&name.to_lowercase()).map(|i| *i) 8 | } 9 | 10 | lazy_static! { 11 | /// Map color constant names to 24-bit RGB integer values. 12 | /// 13 | /// See also: [CSS colors](http://www.w3schools.com/css/css_colorsfull.asp) 14 | static ref COLOR_MAP: HashMap = hashmap! { 15 | "black".into() => 0x000000, 16 | "navy".into() => 0x000080, 17 | "darkblue".into() => 0x00008b, 18 | "mediumblue".into() => 0x0000cd, 19 | "blue".into() => 0x0000ff, 20 | "darkgreen".into() => 0x006400, 21 | "green".into() => 0x008000, 22 | "teal".into() => 0x008080, 23 | "darkcyan".into() => 0x008b8b, 24 | "deepskyblue".into() => 0x00bfff, 25 | "darkturquoise".into() => 0x00ced1, 26 | "mediumspringgreen".into() => 0x00fa9a, 27 | "lime".into() => 0x00ff00, 28 | "springgreen".into() => 0x00ff7f, 29 | "aqua".into() => 0x00ffff, 30 | "cyan".into() => 0x00ffff, 31 | "midnightblue".into() => 0x191970, 32 | "dodgerblue".into() => 0x1e90ff, 33 | "lightseagreen".into() => 0x20b2aa, 34 | "forestgreen".into() => 0x228b22, 35 | "seagreen".into() => 0x2e8b57, 36 | "darkslategray".into() => 0x2f4f4f, 37 | "darkslategrey".into() => 0x2f4f4f, 38 | "limegreen".into() => 0x32cd32, 39 | "mediumseagreen".into() => 0x3cb371, 40 | "turquoise".into() => 0x40e0d0, 41 | "royalblue".into() => 0x4169e1, 42 | "steelblue".into() => 0x4682b4, 43 | "darkslateblue".into() => 0x483d8b, 44 | "mediumturquoise".into() => 0x48d1cc, 45 | "indigo ".into() => 0x4b0082, 46 | "darkolivegreen".into() => 0x556b2f, 47 | "cadetblue".into() => 0x5f9ea0, 48 | "cornflowerblue".into() => 0x6495ed, 49 | "mediumaquamarine".into() => 0x66cdaa, 50 | "dimgray".into() => 0x696969, 51 | "dimgrey".into() => 0x696969, 52 | "slateblue".into() => 0x6a5acd, 53 | "olivedrab".into() => 0x6b8e23, 54 | "slategray".into() => 0x708090, 55 | "slategrey".into() => 0x708090, 56 | "lightslategray".into() => 0x778899, 57 | "lightslategrey".into() => 0x778899, 58 | "mediumslateblue".into() => 0x7b68ee, 59 | "lawngreen".into() => 0x7cfc00, 60 | "chartreuse".into() => 0x7fff00, 61 | "aquamarine".into() => 0x7fffd4, 62 | "maroon".into() => 0x800000, 63 | "purple".into() => 0x800080, 64 | "olive".into() => 0x808000, 65 | "gray".into() => 0x808080, 66 | "grey".into() => 0x808080, 67 | "skyblue".into() => 0x87ceeb, 68 | "lightskyblue".into() => 0x87cefa, 69 | "blueviolet".into() => 0x8a2be2, 70 | "darkred".into() => 0x8b0000, 71 | "darkmagenta".into() => 0x8b008b, 72 | "saddlebrown".into() => 0x8b4513, 73 | "darkseagreen".into() => 0x8fbc8f, 74 | "lightgreen".into() => 0x90ee90, 75 | "mediumpurple".into() => 0x9370d8, 76 | "darkviolet".into() => 0x9400d3, 77 | "palegreen".into() => 0x98fb98, 78 | "darkorchid".into() => 0x9932cc, 79 | "yellowgreen".into() => 0x9acd32, 80 | "sienna".into() => 0xa0522d, 81 | "brown".into() => 0xa52a2a, 82 | "darkgray".into() => 0xa9a9a9, 83 | "darkgrey".into() => 0xa9a9a9, 84 | "lightblue".into() => 0xadd8e6, 85 | "greenyellow".into() => 0xadff2f, 86 | "paleturquoise".into() => 0xafeeee, 87 | "lightsteelblue".into() => 0xb0c4de, 88 | "powderblue".into() => 0xb0e0e6, 89 | "firebrick".into() => 0xb22222, 90 | "darkgoldenrod".into() => 0xb8860b, 91 | "mediumorchid".into() => 0xba55d3, 92 | "rosybrown".into() => 0xbc8f8f, 93 | "darkkhaki".into() => 0xbdb76b, 94 | "silver".into() => 0xc0c0c0, 95 | "mediumvioletred".into() => 0xc71585, 96 | "indianred ".into() => 0xcd5c5c, 97 | "peru".into() => 0xcd853f, 98 | "chocolate".into() => 0xd2691e, 99 | "tan".into() => 0xd2b48c, 100 | "lightgray".into() => 0xd3d3d3, 101 | "lightgrey".into() => 0xd3d3d3, 102 | "palevioletred".into() => 0xd87093, 103 | "thistle".into() => 0xd8bfd8, 104 | "orchid".into() => 0xda70d6, 105 | "goldenrod".into() => 0xdaa520, 106 | "crimson".into() => 0xdc143c, 107 | "gainsboro".into() => 0xdcdcdc, 108 | "plum".into() => 0xdda0dd, 109 | "burlywood".into() => 0xdeb887, 110 | "lightcyan".into() => 0xe0ffff, 111 | "lavender".into() => 0xe6e6fa, 112 | "darksalmon".into() => 0xe9967a, 113 | "violet".into() => 0xee82ee, 114 | "palegoldenrod".into() => 0xeee8aa, 115 | "lightcoral".into() => 0xf08080, 116 | "khaki".into() => 0xf0e68c, 117 | "aliceblue".into() => 0xf0f8ff, 118 | "honeydew".into() => 0xf0fff0, 119 | "azure".into() => 0xf0ffff, 120 | "sandybrown".into() => 0xf4a460, 121 | "wheat".into() => 0xf5deb3, 122 | "beige".into() => 0xf5f5dc, 123 | "whitesmoke".into() => 0xf5f5f5, 124 | "mintcream".into() => 0xf5fffa, 125 | "ghostwhite".into() => 0xf8f8ff, 126 | "salmon".into() => 0xfa8072, 127 | "antiquewhite".into() => 0xfaebd7, 128 | "linen".into() => 0xfaf0e6, 129 | "lightgoldenrodyellow".into() => 0xfafad2, 130 | "oldlace".into() => 0xfdf5e6, 131 | "red".into() => 0xff0000, 132 | "fuchsia".into() => 0xff00ff, 133 | "magenta".into() => 0xff00ff, 134 | "deeppink".into() => 0xff1493, 135 | "orangered".into() => 0xff4500, 136 | "tomato".into() => 0xff6347, 137 | "hotpink".into() => 0xff69b4, 138 | "coral".into() => 0xff7f50, 139 | "darkorange".into() => 0xff8c00, 140 | "lightsalmon".into() => 0xffa07a, 141 | "orange".into() => 0xffa500, 142 | "lightpink".into() => 0xffb6c1, 143 | "pink".into() => 0xffc0cb, 144 | "gold".into() => 0xffd700, 145 | "peachpuff".into() => 0xffdab9, 146 | "navajowhite".into() => 0xffdead, 147 | "moccasin".into() => 0xffe4b5, 148 | "bisque".into() => 0xffe4c4, 149 | "mistyrose".into() => 0xffe4e1, 150 | "blanchedalmond".into() => 0xffebcd, 151 | "papayawhip".into() => 0xffefd5, 152 | "lavenderblush".into() => 0xfff0f5, 153 | "seashell".into() => 0xfff5ee, 154 | "cornsilk".into() => 0xfff8dc, 155 | "lemonchiffon".into() => 0xfffacd, 156 | "floralwhite".into() => 0xfffaf0, 157 | "snow".into() => 0xfffafa, 158 | "yellow".into() => 0xffff00, 159 | "lightyellow".into() => 0xffffe0, 160 | "ivory".into() => 0xfffff0, 161 | "white".into() => 0xffffff, 162 | }; 163 | } -------------------------------------------------------------------------------- /crates/parser/diagnostics/diagnostics_english_resources.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use maplit::hashmap; 3 | use crate::ns::*; 4 | 5 | lazy_static! { 6 | pub static ref DATA: HashMap = hashmap! { 7 | // DiagnosticKind::K.id() => ".".into(), 8 | DiagnosticKind::InvalidEscapeValue.id() => "Invalid escape value.".into(), 9 | DiagnosticKind::UnexpectedEnd.id() => "Unexpected end-of-file.".into(), 10 | DiagnosticKind::UnallowedNumericSuffix.id() => "Unallowed numeric suffix.".into(), 11 | DiagnosticKind::StringLiteralMustBeTerminatedBeforeLineBreak.id() => "A string literal must be terminated before the line break.".into(), 12 | DiagnosticKind::Expecting.id() => "Expecting {1} before {2}.".into(), 13 | DiagnosticKind::ExpectingIdentifier.id() => "Expecting identifier before {1}.".into(), 14 | DiagnosticKind::ExpectingExpression.id() => "Expecting expression before {1}.".into(), 15 | DiagnosticKind::ExpectingXmlName.id() => "Expecting XML name before {1}.".into(), 16 | DiagnosticKind::ExpectingXmlAttributeValue.id() => "Expecting XML attribute value before {1}.".into(), 17 | DiagnosticKind::IllegalNullishCoalescingLeftOperand.id() => "Illegal nullish coalescing left operand.".into(), 18 | DiagnosticKind::WrongParameterPosition.id() => "Wrong parameter position.".into(), 19 | DiagnosticKind::DuplicateRestParameter.id() => "Duplicate rest parameter.".into(), 20 | DiagnosticKind::NotAllowedHere.id() => "{1} not allowed here.".into(), 21 | DiagnosticKind::MalformedRestParameter.id() => "Malformed rest parameter.".into(), 22 | DiagnosticKind::IllegalForInInitializer.id() => "Illegal 'for..in' initializer.".into(), 23 | DiagnosticKind::MultipleForInBindings.id() => "Multiple 'for..in' bindings are not allowed.".into(), 24 | DiagnosticKind::UndefinedLabel.id() => "Undefined label '{1}'.".into(), 25 | DiagnosticKind::IllegalContinue.id() => "Illegal continue statement.".into(), 26 | DiagnosticKind::IllegalBreak.id() => "Illegal break statement.".into(), 27 | DiagnosticKind::ExpressionMustNotFollowLineBreak.id() => "Expression must not follow line break.".into(), 28 | DiagnosticKind::TokenMustNotFollowLineBreak.id() => "Token must not follow line break.".into(), 29 | DiagnosticKind::ExpectingStringLiteral.id() => "Expecting string literal before {1}.".into(), 30 | DiagnosticKind::DuplicateAttribute.id() => "Duplicate attribute.".into(), 31 | DiagnosticKind::DuplicateAccessModifier.id() => "Duplicate access modifier.".into(), 32 | DiagnosticKind::ExpectingDirectiveKeyword.id() => "Expecting either 'var', 'const', 'function', 'class' or 'interface'.".into(), 33 | DiagnosticKind::UnallowedAttribute.id() => "Unallowed attribute.".into(), 34 | DiagnosticKind::UseDirectiveMustContainPublic.id() => "Use directive must contain the 'public' attribute.".into(), 35 | DiagnosticKind::MalformedEnumMember.id() => "Malformed enumeration member.".into(), 36 | DiagnosticKind::FunctionMayNotBeGenerator.id() => "Function may not be generator.".into(), 37 | DiagnosticKind::FunctionMayNotBeAsynchronous.id() => "Function may not be asynchronous.".into(), 38 | DiagnosticKind::FunctionMustNotContainBody.id() => "Function must not contain body.".into(), 39 | DiagnosticKind::FunctionMustContainBody.id() => "Function must contain body.".into(), 40 | DiagnosticKind::FunctionMustNotContainAnnotations.id() => "Function must not contain annotations.".into(), 41 | DiagnosticKind::NestedClassesNotAllowed.id() => "Nested classes are not allowed.".into(), 42 | DiagnosticKind::UnexpectedDirective.id() => "Unexpected directive.".into(), 43 | DiagnosticKind::FailedParsingAsDocTag.id() => "Failed parsing contents of ASDoc tag: '@{1}'.".into(), 44 | DiagnosticKind::UnrecognizedAsDocTag.id() => "Unrecognized ASDoc tag: '@{1}'.".into(), 45 | DiagnosticKind::UnrecognizedProxy.id() => "Unrecognized proxy: '{1}'.".into(), 46 | DiagnosticKind::EnumMembersMustBeConst.id() => "Enumeration members must be 'const'.".into(), 47 | DiagnosticKind::UnrecognizedMetadataSyntax.id() => "Unrecognized meta-data syntax.".into(), 48 | DiagnosticKind::FailedToIncludeFile.id() => "Failed to include file.".into(), 49 | DiagnosticKind::ParentSourceIsNotAFile.id() => "Parent source is not a file.".into(), 50 | DiagnosticKind::CircularIncludeDirective.id() => "Circular include directive.".into(), 51 | DiagnosticKind::MalformedDestructuring.id() => "Malformed destructuring.".into(), 52 | DiagnosticKind::XmlPrefixNotDefined.id() => "Prefix not defined: '{1}'.".into(), 53 | DiagnosticKind::RedefiningXmlAttribute.id() => "Redefining attribute: '{1}'.".into(), 54 | DiagnosticKind::InvalidXmlPi.id() => "Invalid processing instruction.".into(), 55 | DiagnosticKind::XmlPiUnknownAttribute.id() => "Unknown attribute at processing instruction: '{1}'.".into(), 56 | DiagnosticKind::XmlPiVersion.id() => "XML version must be '1.0'.".into(), 57 | DiagnosticKind::XmlPiEncoding.id() => "XML encoding must be either 'utf-8' or 'utf-16'.".into(), 58 | DiagnosticKind::XmlMustConsistOfExactly1Element.id() => "Document must consist of exactly one element.".into(), 59 | DiagnosticKind::XmlNameAtMostOneColon.id() => "XML name may have at most one colon.".into(), 60 | DiagnosticKind::UnexpectedCharacter.id() => "Unexpected character. '{1}' is not allowed here".into(), 61 | DiagnosticKind::InputEndedBeforeReachingClosingQuoteForString.id() => "Input ended before reaching the closing quotation mark for a string literal.".into(), 62 | DiagnosticKind::InputEndedBeforeReachingClosingSeqForCData.id() => "Input ended before reaching the closing ']]>' for a CDATA.".into(), 63 | DiagnosticKind::InputEndedBeforeReachingClosingSeqForPi.id() => "Input ended before reaching the closing '?>' for a processing instruction.".into(), 64 | DiagnosticKind::InputEndedBeforeReachingClosingSeqForXmlComment.id() => "Input ended before reaching the closing '-->' for a comment.".into(), 65 | DiagnosticKind::InputEndedBeforeReachingClosingSeqForMultiLineComment.id() => "Input ended before reaching the closing '*/' for a comment.".into(), 66 | DiagnosticKind::InputEndedBeforeReachingClosingSlashForRegExp.id() => "Input ended before reaching the closing slash for a regular expression.".into(), 67 | DiagnosticKind::InputEndedBeforeReachingClosingQuoteForAttributeValue.id() => "Input ended before reaching the closing quotation mark for an attribute value.".into(), 68 | DiagnosticKind::ExpectingEitherSemicolonOrNewLineHere.id() => "Expecting either a semicolon or a new line here.".into(), 69 | DiagnosticKind::CssInvalidHexEscape.id() => "Invalid hexadecimal escape: '\\{1}'.".into(), 70 | DiagnosticKind::ExpectingDirective.id() => "Expecting directive before {1}.".into(), 71 | DiagnosticKind::ExpectingStatement.id() => "Expecting statement before {1}.".into(), 72 | DiagnosticKind::Unexpected.id() => "Unexpected {1}.".into(), 73 | DiagnosticKind::XmlClosingTagNameMustBeEquals.id() => "Closing tag name must be equals '{1}'.".into(), 74 | // DiagnosticKind::K.id() => ".".into(), 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /crates/parser/tree/mxml.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use crate::ns::*; 4 | use serde::{Serialize, Deserialize}; 5 | 6 | #[derive(Clone, Serialize, Deserialize)] 7 | pub struct Mxml { 8 | pub location: Location, 9 | pub version: XmlVersion, 10 | pub encoding: String, 11 | pub content: Vec>, 12 | } 13 | 14 | #[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] 15 | pub enum XmlVersion { 16 | /// XML version 1.0. 17 | Version10, 18 | } 19 | 20 | #[derive(Clone, Serialize, Deserialize)] 21 | pub struct MxmlElement { 22 | pub location: Location, 23 | pub name: MxmlName, 24 | /// Attribute list, including `xmlns` and `xmlns:` namespace prefixes. 25 | pub attributes: Vec>, 26 | /// The namespace mapping relative to the XML element. 27 | #[serde(skip)] 28 | pub namespace: Rc, 29 | pub content: Option>>, 30 | pub closing_name: Option, 31 | } 32 | 33 | impl MxmlElement { 34 | pub fn inner_text(&self) -> String { 35 | let mut j = String::new(); 36 | if let Some(c) = self.content.as_ref() { 37 | for c1 in c.iter() { 38 | j.push_str(&c1.inner_text()); 39 | } 40 | } 41 | j 42 | } 43 | } 44 | 45 | #[derive(Clone, Serialize, Deserialize)] 46 | pub struct MxmlAttribute { 47 | pub location: Location, 48 | /// Indicates whether the attribute is a `xmlns` or `xmlns:` attribute. 49 | pub xmlns: bool, 50 | pub name: MxmlName, 51 | /// Attribute value. The location data includes the quotes. 52 | pub value: (String, Location), 53 | } 54 | 55 | #[derive(Clone, Serialize, Deserialize)] 56 | pub struct MxmlName { 57 | pub location: Location, 58 | /// The unresolved prefix of the name. 59 | pub prefix: Option, 60 | pub name: String, 61 | } 62 | 63 | impl MxmlName { 64 | pub fn resolve_prefix(&self, namespace: &Rc) -> Result { 65 | let Some(p) = self.prefix.as_ref() else { 66 | return if let Some(v) = namespace.get(MxmlNamespace::DEFAULT_NAMESPACE) { 67 | Ok(v) 68 | } else { 69 | Err(MxmlNameError::PrefixNotDefined(MxmlNamespace::DEFAULT_NAMESPACE.into())) 70 | }; 71 | }; 72 | if let Some(v) = namespace.get(p) { 73 | Ok(v) 74 | } else { 75 | Err(MxmlNameError::PrefixNotDefined(p.clone())) 76 | } 77 | } 78 | 79 | pub fn resolve_name(&self, namespace: &Rc) -> Result<(String, String), MxmlNameError> { 80 | let p = self.resolve_prefix(namespace)?; 81 | Ok((p, self.name.clone())) 82 | } 83 | 84 | pub fn equals_name(&self, other: &Self, namespace: &Rc) -> Result { 85 | if self.name != other.name { 86 | return Ok(false); 87 | } 88 | let p1 = self.resolve_prefix(namespace)?; 89 | let p2 = other.resolve_prefix(namespace)?; 90 | Ok(&p1 == &p2) 91 | } 92 | 93 | pub fn to_string(&self, namespace: &Rc) -> String { 94 | self.resolve_name(namespace).map(|(uri, localname)| { 95 | if uri.is_empty() { 96 | return localname; 97 | } 98 | format!("{uri}:{localname}") 99 | }).unwrap_or("[error]".into()) 100 | } 101 | } 102 | 103 | #[derive(Clone)] 104 | pub enum MxmlNameError { 105 | PrefixNotDefined(String), 106 | } 107 | 108 | #[derive(Clone, Serialize, Deserialize)] 109 | pub enum MxmlContent { 110 | Characters((String, Location)), 111 | /// A CDATA construct, including the first `` characters. 113 | CData((String, Location)), 114 | /// A comment construct, including the first `` characters. 116 | Comment((String, Location)), 117 | ProcessingInstruction { 118 | location: Location, 119 | name: String, 120 | data: Option, 121 | }, 122 | Element(Rc), 123 | } 124 | 125 | impl MxmlContent { 126 | pub fn location(&self) -> Location { 127 | match self { 128 | Self::Characters((_, l)) => l.clone(), 129 | Self::CData((_, l)) => l.clone(), 130 | Self::Comment((_, l)) => l.clone(), 131 | Self::ProcessingInstruction { location: l, .. } => l.clone(), 132 | Self::Element(e) => e.location.clone(), 133 | } 134 | } 135 | 136 | pub fn inner_text(&self) -> String { 137 | match self { 138 | Self::Characters((data, _)) => data.clone(), 139 | Self::CData((data, _)) => data[" String::new(), 141 | Self::ProcessingInstruction { .. } => String::new(), 142 | Self::Element(e) => e.inner_text(), 143 | } 144 | } 145 | } 146 | 147 | /// Mapping of namespace prefixes. 148 | #[derive(Clone, PartialEq)] 149 | pub struct MxmlNamespace { 150 | parent: Option>, 151 | mappings: RefCell>, 152 | } 153 | 154 | impl Default for MxmlNamespace { 155 | fn default() -> Self { 156 | Self::new(None) 157 | } 158 | } 159 | 160 | impl MxmlNamespace { 161 | /// Returns the prefix used for the default XML namespace. 162 | pub const DEFAULT_NAMESPACE: &'static str = ""; 163 | 164 | /// Constructs an empty set of namespace mappings. 165 | pub fn new(parent: Option<&Rc>) -> Self { 166 | let mut ns = Self { 167 | parent: parent.map(|p| p.clone()), 168 | mappings: RefCell::new(BTreeMap::new()), 169 | }; 170 | if ns.parent.is_none() { 171 | ns.mappings.get_mut().insert(Self::DEFAULT_NAMESPACE.into(), "".into()); 172 | ns.mappings.get_mut().insert("xmlns".into(), "http://www.w3.org/xml/xmlns".into()); 173 | } 174 | ns 175 | } 176 | 177 | pub fn includes(&self, prefix: &str) -> bool { 178 | self.mappings.borrow().contains_key(prefix) || match &self.parent { 179 | Some(p) => p.includes(prefix), 180 | None => false, 181 | } 182 | } 183 | 184 | /// Retrieves the value of a prefix either in the actual 185 | /// set of mappings or in the parent set of mappings. 186 | pub fn get(&self, prefix: &str) -> Option { 187 | if let Some(value) = self.mappings.borrow().get(prefix) { 188 | return Some(value.clone()); 189 | } 190 | self.parent.as_ref().and_then(|p| p.get(prefix)) 191 | } 192 | 193 | pub fn set(&self, prefix: &str, value: &str) { 194 | self.mappings.borrow_mut().insert(prefix.to_owned(), value.to_owned()); 195 | } 196 | 197 | pub fn delete(&self, prefix: &str) -> bool { 198 | self.mappings.borrow_mut().remove(prefix).is_some() 199 | } 200 | 201 | pub fn clear(&self) { 202 | self.mappings.borrow_mut().clear(); 203 | } 204 | 205 | /// Returns the actual set of prefix mappings. 206 | pub fn listing(&self) -> BTreeMap { 207 | self.mappings.borrow().clone() 208 | } 209 | 210 | /// Returns a concatenation of the parent set of prefix mappings 211 | /// and the actual set of prefix mappings. 212 | pub fn full_listing(&self) -> BTreeMap { 213 | let mut listing = self.parent.as_ref().map_or(BTreeMap::new(), |p| p.full_listing()); 214 | listing.extend(self.listing()); 215 | listing 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /crates/parser/tree/numeric_literal.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use num_bigint::BigInt; 3 | use serde::{Serialize, Deserialize}; 4 | use std::str::FromStr; 5 | use conv::ValueFrom; 6 | use num_traits::ToPrimitive; 7 | 8 | #[derive(Clone, Serialize, Deserialize)] 9 | pub struct NumericLiteral { 10 | pub location: Location, 11 | /// The numeric value in character representation. Such representation may be parsed 12 | /// through data type specific methods such as [`NumericLiteral::parse_double()`]. 13 | pub value: String, 14 | pub suffix: NumberSuffix, 15 | } 16 | 17 | #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)] 18 | pub enum NumberSuffix { 19 | None, 20 | F, 21 | } 22 | 23 | impl NumericLiteral { 24 | /// Parses a double-precision floating point either in 25 | /// decimal, binary (`0b`) or hexadecimal (`0x`) notation. 26 | pub fn parse_double(&self, negative: bool) -> Result { 27 | let s = self.value.replace('_', ""); 28 | if s.starts_with('0') { 29 | if s[1..].starts_with('x') || s[1..].starts_with('X') { 30 | let n = u64::from_str_radix(&(if negative { "-" } else { "" }.to_owned() + &s[2..]), 16); 31 | return n.map_err(|_| ParserError::Common) 32 | .and_then(|n| f64::value_from(n).map_err(|_| ParserError::Common)); 33 | } else if s[1..].starts_with('b') || s[1..].starts_with('B') { 34 | let n = u64::from_str_radix(&(if negative { "-" } else { "" }.to_owned() + &s[2..]), 2); 35 | return n.map_err(|_| ParserError::Common) 36 | .and_then(|n| f64::value_from(n).map_err(|_| ParserError::Common)); 37 | } 38 | } 39 | f64::from_str(&(if negative { "-" } else { "" }.to_owned() + &s)).map_err(|_| ParserError::Common) 40 | } 41 | 42 | /// Parses a single-precision floating point either in 43 | /// decimal, binary (`0b`) or hexadecimal (`0x`) notation. 44 | pub fn parse_float(&self, negative: bool) -> Result { 45 | let s = self.value.replace('_', ""); 46 | if s.starts_with('0') { 47 | if s[1..].starts_with('x') || s[1..].starts_with('X') { 48 | let n = u64::from_str_radix(&(if negative { "-" } else { "" }.to_owned() + &s[2..]), 16); 49 | return n.map_err(|_| ParserError::Common) 50 | .and_then(|n| f32::value_from(n).map_err(|_| ParserError::Common)); 51 | } else if s[1..].starts_with('b') || s[1..].starts_with('B') { 52 | let n = u64::from_str_radix(&(if negative { "-" } else { "" }.to_owned() + &s[2..]), 2); 53 | return n.map_err(|_| ParserError::Common) 54 | .and_then(|n| f32::value_from(n).map_err(|_| ParserError::Common)); 55 | } 56 | } 57 | f32::from_str(&(if negative { "-" } else { "" }.to_owned() + &s)).map_err(|_| ParserError::Common) 58 | } 59 | 60 | /// Parses a signed long either in 61 | /// decimal, binary (`0b`) or hexadecimal (`0x`) notation. 62 | pub fn parse_long(&self, negative: bool) -> Result { 63 | let s = self.value.replace('_', ""); 64 | if s.starts_with('0') { 65 | if s[1..].starts_with('x') || s[1..].starts_with('X') { 66 | let n = i64::from_str_radix(&(if negative { "-" } else { "" }.to_owned() + &s[2..]), 16); 67 | return n.map_err(|_| ParserError::Common); 68 | } else if s[1..].starts_with('b') || s[1..].starts_with('B') { 69 | let n = i64::from_str_radix(&(if negative { "-" } else { "" }.to_owned() + &s[2..]), 2); 70 | return n.map_err(|_| ParserError::Common); 71 | } 72 | } 73 | i64::from_str(&s).map_err(|_| ParserError::Common) 74 | } 75 | 76 | /// Parses a signed integer either in 77 | /// decimal, binary (`0b`) or hexadecimal (`0x`) notation. 78 | pub fn parse_int(&self, negative: bool) -> Result { 79 | let s = self.value.replace('_', ""); 80 | if s.starts_with('0') { 81 | if s[1..].starts_with('x') || s[1..].starts_with('X') { 82 | let n = i32::from_str_radix(&(if negative { "-" } else { "" }.to_owned() + &s[2..]), 16); 83 | return n.map_err(|_| ParserError::Common); 84 | } else if s[1..].starts_with('b') || s[1..].starts_with('B') { 85 | let n = i32::from_str_radix(&(if negative { "-" } else { "" }.to_owned() + &s[2..]), 2); 86 | return n.map_err(|_| ParserError::Common); 87 | } 88 | } 89 | i32::from_str(&s).map_err(|_| ParserError::Common) 90 | } 91 | 92 | /// Parses an unsigned integer either in 93 | /// decimal, binary (`0b`) or hexadecimal (`0x`) notation. 94 | pub fn parse_uint(&self) -> Result { 95 | let s = self.value.replace('_', ""); 96 | if s.starts_with('0') { 97 | if s[1..].starts_with('x') || s[1..].starts_with('X') { 98 | let n = u32::from_str_radix(&s[2..], 16); 99 | return n.map_err(|_| ParserError::Common); 100 | } else if s[1..].starts_with('b') || s[1..].starts_with('B') { 101 | let n = u32::from_str_radix(&s[2..], 2); 102 | return n.map_err(|_| ParserError::Common); 103 | } 104 | } 105 | u32::from_str(&s).map_err(|_| ParserError::Common) 106 | } 107 | 108 | /// Parses a big integer either in 109 | /// decimal, binary (`0b`) or hexadecimal (`0x`) notation. 110 | pub fn parse_big_int(&self, negative: bool) -> Result { 111 | let s = self.value.replace('_', ""); 112 | if s.starts_with('0') { 113 | if s[1..].starts_with('x') || s[1..].starts_with('X') { 114 | let mut digits: Vec = vec![]; 115 | for ch in s[2..].chars() { 116 | digits.push(CharacterValidator::hex_digit_mv(ch).unwrap().to_u8().unwrap()); 117 | } 118 | let n = BigInt::from_radix_be(if negative { num_bigint::Sign::Minus } else { num_bigint::Sign::Plus }, &digits, 16); 119 | return n.map_or(Err(ParserError::Common), |n| Ok(n)); 120 | } else if s[1..].starts_with('b') || s[1..].starts_with('B') { 121 | let mut digits: Vec = vec![]; 122 | for ch in s[2..].chars() { 123 | digits.push(CharacterValidator::bin_digit_mv(ch).unwrap().to_u8().unwrap()); 124 | } 125 | let n = BigInt::from_radix_be(if negative { num_bigint::Sign::Minus } else { num_bigint::Sign::Plus }, &digits, 2); 126 | return n.map_or(Err(ParserError::Common), |n| Ok(n)); 127 | } 128 | } 129 | BigInt::from_str(&s).map_err(|_| ParserError::Common) 130 | } 131 | } 132 | 133 | mod tests { 134 | #[allow(unused)] 135 | use crate::ns::*; 136 | #[allow(unused)] 137 | use std::rc::Rc; 138 | 139 | #[test] 140 | fn test_minimum_maximum() { 141 | // Long.MIN_VALUE 142 | let literal = NumericLiteral { 143 | location: Location::with_offset(&Rc::new(CompilationUnit::default()), 0), 144 | value: "0x8000_0000_0000_0000".to_owned(), 145 | suffix: NumberSuffix::None, 146 | }; 147 | assert_eq!(i64::MIN, literal.parse_long(true).unwrap()); 148 | 149 | // Long.MAX_VALUE 150 | let literal = NumericLiteral { 151 | location: Location::with_offset(&Rc::new(CompilationUnit::default()), 0), 152 | value: "0x7FFF_FFFF_FFFF_FFFF".to_owned(), 153 | suffix: NumberSuffix::None, 154 | }; 155 | assert_eq!(i64::MAX, literal.parse_long(false).unwrap()); 156 | } 157 | } -------------------------------------------------------------------------------- /crates/parser/tree/attributes.rs: -------------------------------------------------------------------------------- 1 | use crate::ns::*; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | pub enum Attribute { 6 | Metadata(Rc), 7 | Expression(Rc), 8 | Public(Location), 9 | Private(Location), 10 | Protected(Location), 11 | Internal(Location), 12 | Final(Location), 13 | Native(Location), 14 | Static(Location), 15 | Abstract(Location), 16 | Override(Location), 17 | Dynamic(Location), 18 | } 19 | 20 | #[derive(Clone, Serialize, Deserialize)] 21 | pub struct Metadata { 22 | pub location: Location, 23 | pub asdoc: Option>, 24 | pub name: (String, Location), 25 | pub entries: Option>>, 26 | } 27 | 28 | #[derive(Clone, Serialize, Deserialize)] 29 | pub struct MetadataEntry { 30 | pub location: Location, 31 | pub key: Option<(String, Location)>, 32 | pub value: Rc, 33 | } 34 | 35 | #[derive(Clone, Serialize, Deserialize)] 36 | pub enum MetadataValue { 37 | IdentifierString((String, Location)), 38 | String((String, Location)), 39 | } 40 | 41 | impl MetadataValue { 42 | pub fn location(&self) -> Location { 43 | match self { 44 | Self::IdentifierString((_, l)) => l.clone(), 45 | Self::String((_, l)) => l.clone(), 46 | } 47 | } 48 | } 49 | 50 | impl Attribute { 51 | pub fn location(&self) -> Location { 52 | match self { 53 | Self::Expression(m) => m.location(), 54 | Self::Metadata(m) => m.location.clone(), 55 | Self::Public(a) => a.clone(), 56 | Self::Private(a) => a.clone(), 57 | Self::Protected(a) => a.clone(), 58 | Self::Internal(a) => a.clone(), 59 | Self::Final(a) => a.clone(), 60 | Self::Native(a) => a.clone(), 61 | Self::Static(a) => a.clone(), 62 | Self::Abstract(a) => a.clone(), 63 | Self::Override(a) => a.clone(), 64 | Self::Dynamic(a) => a.clone(), 65 | } 66 | } 67 | 68 | pub fn has_access_modifier(list: &[Attribute]) -> bool { 69 | for a in list { 70 | match a { 71 | Self::Expression(_) | 72 | Self::Public(_) | 73 | Self::Private(_) | 74 | Self::Protected(_) | 75 | Self::Internal(_) => return true, 76 | _ => {} 77 | } 78 | } 79 | false 80 | } 81 | 82 | pub fn remove_metadata(list: &mut Vec, metadata: &Rc) { 83 | for i in 0..list.len() { 84 | if let Attribute::Metadata(metadata_1) = &list[i] { 85 | if Rc::ptr_eq(&metadata_1, metadata) { 86 | list.remove(i); 87 | break; 88 | } 89 | } 90 | } 91 | } 92 | 93 | pub fn find_metadata(list: &[Attribute]) -> Vec> { 94 | let mut r = vec![]; 95 | for a in list { 96 | match &a { 97 | Self::Metadata(e) => { 98 | r.push(e.clone()); 99 | }, 100 | _ => {}, 101 | } 102 | } 103 | r 104 | } 105 | pub fn find_expression(list: &[Attribute]) -> Option> { for a in list { match &a { Self::Expression(e) => return Some(e.clone()), _ => {} } }; None } 106 | pub fn find_public(list: &[Attribute]) -> Option { for a in list { match &a { Self::Public(l) => return Some(l.clone()), _ => {} } }; None } 107 | pub fn find_private(list: &[Attribute]) -> Option { for a in list { match &a { Self::Private(l) => return Some(l.clone()), _ => {} } }; None } 108 | pub fn find_protected(list: &[Attribute]) -> Option { for a in list { match &a { Self::Protected(l) => return Some(l.clone()), _ => {} } }; None } 109 | pub fn find_internal(list: &[Attribute]) -> Option { for a in list { match &a { Self::Internal(l) => return Some(l.clone()), _ => {} } }; None } 110 | pub fn find_final(list: &[Attribute]) -> Option { for a in list { match &a { Self::Final(l) => return Some(l.clone()), _ => {} } }; None } 111 | pub fn find_native(list: &[Attribute]) -> Option { for a in list { match &a { Self::Native(l) => return Some(l.clone()), _ => {} } }; None } 112 | pub fn find_static(list: &[Attribute]) -> Option { for a in list { match &a { Self::Static(l) => return Some(l.clone()), _ => {} } }; None } 113 | pub fn find_abstract(list: &[Attribute]) -> Option { for a in list { match &a { Self::Abstract(l) => return Some(l.clone()), _ => {} } }; None } 114 | pub fn find_override(list: &[Attribute]) -> Option { for a in list { match &a { Self::Override(l) => return Some(l.clone()), _ => {} } }; None } 115 | pub fn find_dynamic(list: &[Attribute]) -> Option { for a in list { match &a { Self::Dynamic(l) => return Some(l.clone()), _ => {} } }; None } 116 | 117 | pub fn has(list: &[Attribute], attribute: &Attribute) -> bool { 118 | match attribute { 119 | Self::Public(_) => Self::find_public(list).is_some(), 120 | Self::Private(_) => Self::find_private(list).is_some(), 121 | Self::Protected(_) => Self::find_protected(list).is_some(), 122 | Self::Internal(_) => Self::find_internal(list).is_some(), 123 | Self::Final(_) => Self::find_final(list).is_some(), 124 | Self::Native(_) => Self::find_native(list).is_some(), 125 | Self::Static(_) => Self::find_static(list).is_some(), 126 | Self::Abstract(_) => Self::find_abstract(list).is_some(), 127 | Self::Override(_) => Self::find_override(list).is_some(), 128 | Self::Dynamic(_) => Self::find_dynamic(list).is_some(), 129 | _ => false, 130 | } 131 | } 132 | 133 | pub fn is_duplicate_access_modifier(list: &[Attribute], attribute: &Attribute) -> bool { 134 | match attribute { 135 | Self::Expression(_) | 136 | Self::Public(_) | 137 | Self::Private(_) | 138 | Self::Protected(_) | 139 | Self::Internal(_) => Self::find_expression(list).is_some() || Self::find_public(list).is_some() || Self::find_private(list).is_some() || Self::find_protected(list).is_some() || Self::find_internal(list).is_some(), 140 | _ => false, 141 | } 142 | } 143 | 144 | pub fn is_metadata(&self) -> bool { matches!(self, Self::Metadata(_)) } 145 | pub fn is_public(&self) -> bool { matches!(self, Self::Public(_)) } 146 | pub fn is_private(&self) -> bool { matches!(self, Self::Private(_)) } 147 | pub fn is_protected(&self) -> bool { matches!(self, Self::Protected(_)) } 148 | pub fn is_internal(&self) -> bool { matches!(self, Self::Internal(_)) } 149 | pub fn is_final(&self) -> bool { matches!(self, Self::Final(_)) } 150 | pub fn is_native(&self) -> bool { matches!(self, Self::Native(_)) } 151 | pub fn is_static(&self) -> bool { matches!(self, Self::Static(_)) } 152 | pub fn is_abstract(&self) -> bool { matches!(self, Self::Abstract(_)) } 153 | pub fn is_override(&self) -> bool { matches!(self, Self::Override(_)) } 154 | pub fn is_dynamic(&self) -> bool { matches!(self, Self::Dynamic(_)) } 155 | 156 | pub fn from_identifier_name(name: &str, location: &Location) -> Option { 157 | if location.character_count() != name.chars().count() { 158 | return None; 159 | } 160 | match name.as_ref() { 161 | "final" => Some(Attribute::Final(location.clone())), 162 | "native" => Some(Attribute::Native(location.clone())), 163 | "static" => Some(Attribute::Static(location.clone())), 164 | "abstract" => Some(Attribute::Abstract(location.clone())), 165 | "override" => Some(Attribute::Override(location.clone())), 166 | "dynamic" => Some(Attribute::Dynamic(location.clone())), 167 | _ => None, 168 | } 169 | } 170 | } -------------------------------------------------------------------------------- /tests/parser/Super.tree: -------------------------------------------------------------------------------- 1 | { 2 | "location": "1:1-12:2", 3 | "packages": [], 4 | "directives": [ 5 | { 6 | "SuperStatement": { 7 | "location": "1:1-1:8", 8 | "arguments": [] 9 | } 10 | }, 11 | { 12 | "ClassDefinition": { 13 | "location": "2:1-12:2", 14 | "asdoc": null, 15 | "attributes": [], 16 | "name": [ 17 | "C2", 18 | "2:7-2:9" 19 | ], 20 | "type_parameters": null, 21 | "extends_clause": { 22 | "QualifiedIdentifier": { 23 | "location": "2:18-2:20", 24 | "attribute": false, 25 | "qualifier": null, 26 | "id": { 27 | "Id": [ 28 | "C1", 29 | "2:18-2:20" 30 | ] 31 | } 32 | } 33 | }, 34 | "implements_clause": null, 35 | "block": { 36 | "location": "2:21-12:2", 37 | "metadata": null, 38 | "directives": [ 39 | { 40 | "FunctionDefinition": { 41 | "location": "3:5-7:6", 42 | "asdoc": null, 43 | "attributes": [], 44 | "name": { 45 | "Constructor": [ 46 | "C2", 47 | "3:14-3:16" 48 | ] 49 | }, 50 | "common": { 51 | "location": "3:16-7:6", 52 | "contains_yield": false, 53 | "contains_await": false, 54 | "signature": { 55 | "location": "3:16-3:18", 56 | "parameters": [], 57 | "result_type": null 58 | }, 59 | "body": { 60 | "Block": { 61 | "location": "3:19-7:6", 62 | "metadata": null, 63 | "directives": [ 64 | { 65 | "IfStatement": { 66 | "location": "4:9-6:10", 67 | "test": { 68 | "BooleanLiteral": { 69 | "location": "4:13-4:17", 70 | "value": true 71 | } 72 | }, 73 | "consequent": { 74 | "Block": { 75 | "location": "4:19-6:10", 76 | "metadata": null, 77 | "directives": [ 78 | { 79 | "SuperStatement": { 80 | "location": "5:13-5:20", 81 | "arguments": [] 82 | } 83 | } 84 | ] 85 | } 86 | }, 87 | "alternative": null 88 | } 89 | } 90 | ] 91 | } 92 | } 93 | } 94 | } 95 | }, 96 | { 97 | "FunctionDefinition": { 98 | "location": "8:5-11:6", 99 | "asdoc": null, 100 | "attributes": [ 101 | { 102 | "Override": "8:5-8:13" 103 | }, 104 | { 105 | "Protected": "8:14-8:23" 106 | } 107 | ], 108 | "name": { 109 | "Identifier": [ 110 | "m", 111 | "8:33-8:34" 112 | ] 113 | }, 114 | "common": { 115 | "location": "8:34-11:6", 116 | "contains_yield": false, 117 | "contains_await": false, 118 | "signature": { 119 | "location": "8:34-8:42", 120 | "parameters": [], 121 | "result_type": { 122 | "VoidType": { 123 | "location": "8:38-8:42" 124 | } 125 | } 126 | }, 127 | "body": { 128 | "Block": { 129 | "location": "8:43-11:6", 130 | "metadata": null, 131 | "directives": [ 132 | { 133 | "ExpressionStatement": { 134 | "location": "9:9-9:18", 135 | "expression": { 136 | "Call": { 137 | "location": "9:9-9:18", 138 | "base": { 139 | "Member": { 140 | "location": "9:9-9:16", 141 | "base": { 142 | "Super": { 143 | "location": "9:9-9:14", 144 | "object": null 145 | } 146 | }, 147 | "identifier": { 148 | "location": "9:15-9:16", 149 | "attribute": false, 150 | "qualifier": null, 151 | "id": { 152 | "Id": [ 153 | "m", 154 | "9:15-9:16" 155 | ] 156 | } 157 | } 158 | } 159 | }, 160 | "arguments": [] 161 | } 162 | } 163 | } 164 | }, 165 | { 166 | "ExpressionStatement": { 167 | "location": "10:9-10:24", 168 | "expression": { 169 | "Call": { 170 | "location": "10:9-10:24", 171 | "base": { 172 | "Member": { 173 | "location": "10:9-10:22", 174 | "base": { 175 | "Super": { 176 | "location": "10:9-10:20", 177 | "object": [ 178 | { 179 | "ThisLiteral": { 180 | "location": "10:15-10:19" 181 | } 182 | } 183 | ] 184 | } 185 | }, 186 | "identifier": { 187 | "location": "10:21-10:22", 188 | "attribute": false, 189 | "qualifier": null, 190 | "id": { 191 | "Id": [ 192 | "m", 193 | "10:21-10:22" 194 | ] 195 | } 196 | } 197 | } 198 | }, 199 | "arguments": [] 200 | } 201 | } 202 | } 203 | } 204 | ] 205 | } 206 | } 207 | } 208 | } 209 | } 210 | ] 211 | } 212 | } 213 | } 214 | ] 215 | } -------------------------------------------------------------------------------- /tests/parser/Attributes.tree: -------------------------------------------------------------------------------- 1 | { 2 | "location": "5:1-17:12", 3 | "packages": [], 4 | "directives": [ 5 | { 6 | "VariableDefinition": { 7 | "location": "1:1-1:10", 8 | "asdoc": null, 9 | "attributes": [ 10 | { 11 | "Expression": { 12 | "Member": { 13 | "location": "1:1-1:4", 14 | "base": { 15 | "QualifiedIdentifier": { 16 | "location": "1:1-1:2", 17 | "attribute": false, 18 | "qualifier": null, 19 | "id": { 20 | "Id": [ 21 | "q", 22 | "1:1-1:2" 23 | ] 24 | } 25 | } 26 | }, 27 | "identifier": { 28 | "location": "1:3-1:4", 29 | "attribute": false, 30 | "qualifier": null, 31 | "id": { 32 | "Id": [ 33 | "x", 34 | "1:3-1:4" 35 | ] 36 | } 37 | } 38 | } 39 | } 40 | } 41 | ], 42 | "kind": [ 43 | "Var", 44 | "1:5-1:8" 45 | ], 46 | "bindings": [ 47 | { 48 | "destructuring": { 49 | "location": "1:9-1:10", 50 | "destructuring": { 51 | "QualifiedIdentifier": { 52 | "location": "1:9-1:10", 53 | "attribute": false, 54 | "qualifier": null, 55 | "id": { 56 | "Id": [ 57 | "y", 58 | "1:9-1:10" 59 | ] 60 | } 61 | } 62 | }, 63 | "type_annotation": null 64 | }, 65 | "initializer": null 66 | } 67 | ] 68 | } 69 | }, 70 | { 71 | "VariableDefinition": { 72 | "location": "3:1-3:20", 73 | "asdoc": null, 74 | "attributes": [ 75 | { 76 | "Metadata": { 77 | "location": "3:2-3:4", 78 | "asdoc": null, 79 | "name": [ 80 | "N1", 81 | "3:2-3:4" 82 | ], 83 | "entries": null 84 | } 85 | }, 86 | { 87 | "Internal": "3:6-3:14" 88 | } 89 | ], 90 | "kind": [ 91 | "Var", 92 | "3:15-3:18" 93 | ], 94 | "bindings": [ 95 | { 96 | "destructuring": { 97 | "location": "3:19-3:20", 98 | "destructuring": { 99 | "QualifiedIdentifier": { 100 | "location": "3:19-3:20", 101 | "attribute": false, 102 | "qualifier": null, 103 | "id": { 104 | "Id": [ 105 | "z", 106 | "3:19-3:20" 107 | ] 108 | } 109 | } 110 | }, 111 | "type_annotation": null 112 | }, 113 | "initializer": null 114 | } 115 | ] 116 | } 117 | }, 118 | { 119 | "Block": { 120 | "location": "5:15-5:17", 121 | "metadata": [ 122 | { 123 | "Metadata": { 124 | "location": "5:2-5:13", 125 | "asdoc": null, 126 | "name": [ 127 | "N1", 128 | "5:2-5:4" 129 | ], 130 | "entries": [ 131 | { 132 | "location": "5:5-5:12", 133 | "key": [ 134 | "x", 135 | "5:5-5:6" 136 | ], 137 | "value": { 138 | "String": [ 139 | "y", 140 | "5:9-5:12" 141 | ] 142 | } 143 | } 144 | ] 145 | } 146 | } 147 | ], 148 | "directives": [] 149 | } 150 | }, 151 | { 152 | "ClassDefinition": { 153 | "location": "7:1-7:26", 154 | "asdoc": null, 155 | "attributes": [ 156 | { 157 | "Dynamic": "7:1-7:8" 158 | }, 159 | { 160 | "Final": "7:9-7:14" 161 | } 162 | ], 163 | "name": [ 164 | "C1", 165 | "7:21-7:23" 166 | ], 167 | "type_parameters": null, 168 | "extends_clause": null, 169 | "implements_clause": null, 170 | "block": { 171 | "location": "7:24-7:26", 172 | "metadata": null, 173 | "directives": [] 174 | } 175 | } 176 | }, 177 | { 178 | "ClassDefinition": { 179 | "location": "12:1-17:12", 180 | "asdoc": { 181 | "location": "9:1-11:4", 182 | "main_body": [ 183 | "Foo", 184 | "10:4-10:7" 185 | ], 186 | "tags": [] 187 | }, 188 | "attributes": [ 189 | { 190 | "Metadata": { 191 | "location": "12:2-12:4", 192 | "asdoc": null, 193 | "name": [ 194 | "N1", 195 | "12:2-12:4" 196 | ], 197 | "entries": null 198 | } 199 | }, 200 | { 201 | "Metadata": { 202 | "location": "16:2-16:40", 203 | "asdoc": { 204 | "location": "13:1-15:4", 205 | "main_body": null, 206 | "tags": [ 207 | [ 208 | { 209 | "EventType": { 210 | "Member": { 211 | "location": "14:15-14:27", 212 | "base": { 213 | "QualifiedIdentifier": { 214 | "location": "14:15-14:23", 215 | "attribute": false, 216 | "qualifier": null, 217 | "id": { 218 | "Id": [ 219 | "FooEvent", 220 | "14:15-14:23" 221 | ] 222 | } 223 | } 224 | }, 225 | "identifier": { 226 | "location": "14:24-14:27", 227 | "attribute": false, 228 | "qualifier": null, 229 | "id": { 230 | "Id": [ 231 | "FOO", 232 | "14:24-14:27" 233 | ] 234 | } 235 | } 236 | } 237 | } 238 | }, 239 | "14:4-14:27" 240 | ] 241 | ] 242 | }, 243 | "name": [ 244 | "Event", 245 | "16:2-16:7" 246 | ], 247 | "entries": [ 248 | { 249 | "location": "16:8-16:20", 250 | "key": [ 251 | "name", 252 | "16:8-16:12" 253 | ], 254 | "value": { 255 | "String": [ 256 | "foo", 257 | "16:15-16:20" 258 | ] 259 | } 260 | }, 261 | { 262 | "location": "16:22-16:39", 263 | "key": [ 264 | "type", 265 | "16:22-16:26" 266 | ], 267 | "value": { 268 | "String": [ 269 | "FooEvent", 270 | "16:29-16:39" 271 | ] 272 | } 273 | } 274 | ] 275 | } 276 | } 277 | ], 278 | "name": [ 279 | "C1", 280 | "17:7-17:9" 281 | ], 282 | "type_parameters": null, 283 | "extends_clause": null, 284 | "implements_clause": null, 285 | "block": { 286 | "location": "17:10-17:12", 287 | "metadata": null, 288 | "directives": [] 289 | } 290 | } 291 | } 292 | ] 293 | } --------------------------------------------------------------------------------