diff --git a/.githooks/check b/.githooks/check index 0fa92ae..c70efed 100755 --- a/.githooks/check +++ b/.githooks/check @@ -92,4 +92,10 @@ echo "Checking Stylelint for CSS files..." if ! npm run lint:css; then echo "Error: Stylelint check for CSS files failed." >&2 exit 1 +fi + +echo "Running Rust tests..." +if ! cargo test --manifest-path "$PROJECT_ROOT/rust/Cargo.toml"; then + echo "Error: Rust tests failed." >&2 + exit 1 fi \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9aa0aa5..ed25ba5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,3 +117,33 @@ jobs: - name: Check Stylelint for CSS files run: npm run lint:css + + rust: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + + - name: Install ANTLR + run: | + curl -L -o rust/antlr4rust.jar https://github.com/rrevenantt/antlr4rust/releases/download/antlr4-4.8-2-Rust0.3.0-beta/antlr4-4.8-2-SNAPSHOT-complete.jar + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Generate grammar + run: | + chmod +x ./generate_parser.sh + ./generate_parser.sh + working-directory: rust + + - name: Run Rust tests + run: cargo test + working-directory: rust diff --git a/.gitignore b/.gitignore index 515f61e..c45cc7d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,8 @@ grammar/.antlr/ node_modules/ # Output files. -output/ dist/ +output/ # Parsers, generated from `IconScript.g4` by ANTLR4. grammar/*.interp @@ -21,3 +21,12 @@ web/bundles/*.bundle.min.js.map # WIP. webassembly/ work/ + +# Rust. +**/*.rs.bk +*.long-type-*.txt +*.pdb +rust/Cargo.lock +rust/antlr4rust.jar +rust/src/grammar/ +rust/target/ diff --git a/.stylelintignore b/.stylelintignore index 94f9f8e..1d44719 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -1,4 +1,5 @@ node_modules/ dist/ *.bundle.css +rust/target/ output/ diff --git a/doc/public.moi b/doc/public.moi index a0c1445..dc89820 100644 --- a/doc/public.moi +++ b/doc/public.moi @@ -54,3 +54,5 @@ the style of the \ref {#roentgen} {Röntgen} project. } + +\include {syntax.moi} \ No newline at end of file diff --git a/doc/syntax.moi b/doc/syntax.moi new file mode 100644 index 0000000..67a31a9 --- /dev/null +++ b/doc/syntax.moi @@ -0,0 +1,55 @@ +\2 {Syntax} {iconscript_syntax} + +\3 {Global context} {iconscript_global_context} + +\list + {\c {width} — stroke width.} + {\c {position} — current position of the cursor.} + +\3 {Commands} {iconscript_commands} + +\c {\formal {position}} is 2D coordinates in the form \c {\formal {x},\formal {y}} or \c {+\formal {x},\formal {y}} +(\c {+} means that the position is relative to the \c {position}). + +\table + {{Command} {Description}} + {{\c {subtract}} {Set subtraction mode}} + {{\c {w \formal {float}}} {Set \c {width} to a value}} + {{\c {m \formal {position}}} {Set \c {position} to a value}} + {{\c {l [\formal {position}]}} {Draw lines between positions}} + {{\c {lf [\formal {position}]}} {Draw filled lines between positions}} + { + {\c {e \formal {position} \formal {float}}} + {Draw circle specified by center point and radius} + } + { + {\c {r \formal {position} \formal {position}}} + {Draw rectangle specified by top left and bottom right points} + } + { + {\c {a \formal {position} \formal {float} \formal {float} \formal {float}}} + {Draw arc specified by center point, radius, and two angles in radians} + } + +\3 {Variables} {iconscript_variables} + +Variables can be defined with \c { = []} and accessed with +\c {@}. + +\3 {Scopes} {iconscript_scopes} + +Scopes group commands together using \c {\{} and \c {\}}. They can be nested and +are used to incapsulate context changes. + +\3 {Example} {iconscript_example} + +\code {iconscript} { +\sv {square} = \skw {lf} +0,0 +2,0 +0,2 +-2,0 +0,-2 +icon glider = \{ + \skw {m} 6,2 \sv {@square} \skw {m} +4,4 \sv {@square} + \skw {m} +-8,4 \sv {@square} \skw {m} +4,0 \sv {@square} \skw {m} +4,0 \sv {@square} +\} +} + +This code defines a square (\c {lf}, filled line — polygon with 5 points). It +then reuses \c {square} variable 5 times to draw a glider. \ No newline at end of file diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..a6cf32f --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "iconscript" +version = "0.2.0" +edition = "2021" +authors = ["Sergey Vartanov"] +description = "iconscript parser and SVG generator in Rust" +license = "MIT" +repository = "https://github.com/enzet/iconscript" +readme = "README.md" +keywords = ["icons", "svg", "cli"] + +[[bin]] +name = "iconscript" +path = "src/main.rs" + +[dependencies] +antlr-rust = "0.3.0-beta" +anyhow = "1.0" +clap = { version = "4.5", features = ["derive"] } +kurbo = "0.12.0" +lazy_static = "1.4" +linesweeper = "0.1.2" +thiserror = "1.0" diff --git a/rust/LICENSE b/rust/LICENSE new file mode 100644 index 0000000..cdfaa64 --- /dev/null +++ b/rust/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Sergey Vartanov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rust/README.md b/rust/README.md new file mode 100644 index 0000000..0e8de9d --- /dev/null +++ b/rust/README.md @@ -0,0 +1,43 @@ +# iconscript implementation in Rust + +- Parses iconscript files using ANTLR grammar. +- Generates SVG files using the `linesweeper` library for Boolean path + operations. + +## Building + +```bash +cd rust +./generate_parser.sh +cargo build --release +``` + +The binary will be available at `target/release/iconscript`. + +## Usage + +```shell +./target/release/iconscript $OPTIONS $ICONSCRIPT_FILE +``` + +| Option | Description | +| -------------------- | -------------------------------------------------- | +| `-o`, `--output` | Output directory for SVG files (default: `output`) | +| `-s`, `--sketch` | Output raw paths without combining | +| `--no-rounding` | Disable coordinate rounding | +| `--no-deduplication` | Disable duplicate point removal | +| `--no-collinear` | Disable collinear point simplification | + +## Testing + +Run tests: + +```shell +cargo test +``` + +Try to run on test file: + +```shell +cargo run -- ../test/main.iconscript -o test-output/ +``` diff --git a/rust/build.rs b/rust/build.rs new file mode 100644 index 0000000..f6fc0a1 --- /dev/null +++ b/rust/build.rs @@ -0,0 +1,6 @@ +fn main() { + // Before running this file, run `./generate_parser.sh`. + + let grammar_file = "../grammar/IconScript.g4"; + println!("cargo:rerun-if-changed={}", grammar_file); +} diff --git a/rust/generate_parser.sh b/rust/generate_parser.sh new file mode 100755 index 0000000..b112aec --- /dev/null +++ b/rust/generate_parser.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Script to generate Rust parser from ANTLR grammar +# This uses the ANTLR4 Rust tool + +set -e + +GRAMMAR_FILE="../grammar/IconScript.g4" +OUTPUT_DIR="src/parser" +ANTLR_JAR="antlr4rust.jar" + +# Create output directory +mkdir -p "$OUTPUT_DIR" + +# Check if ANTLR Rust jar exists +if [ ! -f "$ANTLR_JAR" ]; then + echo "Downloading ANTLR4 Rust target..." + curl -L -o "$ANTLR_JAR" \ + "https://github.com/rrevenantt/antlr4rust/releases/download/antlr4-4.8-2-Rust0.3.0-beta/antlr4-4.8-2-SNAPSHOT-complete.jar" + + if [ $? -ne 0 ]; then + echo "ERROR: Failed to download ANTLR Rust jar." + echo "Please download manually from:" + echo " https://github.com/rrevenantt/antlr4rust/releases" + exit 1 + fi +fi + +# Generate parser +echo "Generating Rust parser from $GRAMMAR_FILE..." +java -jar "$ANTLR_JAR" -Dlanguage=Rust -visitor -o src/grammar "$GRAMMAR_FILE" + +# Move generated files to correct location +echo "Moving generated files to $OUTPUT_DIR..." +mv src/grammar/iconscript*.rs "$OUTPUT_DIR/" 2>/dev/null || true + +echo "Parser generated successfully!" +echo "Generated files:" +ls -lh "$OUTPUT_DIR"/*.rs diff --git a/rust/rustfmt.toml b/rust/rustfmt.toml new file mode 100644 index 0000000..df99c69 --- /dev/null +++ b/rust/rustfmt.toml @@ -0,0 +1 @@ +max_width = 80 diff --git a/rust/src/generator.rs b/rust/src/generator.rs new file mode 100644 index 0000000..3e50872 --- /dev/null +++ b/rust/src/generator.rs @@ -0,0 +1,688 @@ +use anyhow::Result; +use std::fs; +use std::path::Path; + +use crate::listener::parse_iconscript; +use crate::types::{Icon, PathWithMode}; + +/// Options for controlling path optimization. +#[derive(Debug, Clone)] +pub struct OptimizationOptions { + pub enable_rounding: bool, + pub enable_deduplication: bool, + pub enable_collinear_simplification: bool, +} + +impl Default for OptimizationOptions { + fn default() -> Self { + Self { + enable_rounding: true, + enable_deduplication: true, + enable_collinear_simplification: true, + } + } +} + +/// Create a circle path using SVG arc commands. +pub fn create_circle_path(cx: f64, cy: f64, r: f64) -> Option { + if !cx.is_finite() || !cy.is_finite() || !r.is_finite() { + eprintln!("Invalid circle coordinates: ({}, {}, {})", cx, cy, r); + return None; + } + + Some(format!( + "M {} {} A {} {} 0 0 1 {} {} A {} {} 0 0 1 {} {} Z", + cx - r, + cy, + r, + r, + cx + r, + cy, + r, + r, + cx - r, + cy + )) +} + +/// Create a thick line path as a rectangle. +pub fn create_thick_line_path( + x1: f64, + y1: f64, + x2: f64, + y2: f64, + thickness: f64, +) -> Option { + let dx = x2 - x1; + let dy = y2 - y1; + let length = (dx * dx + dy * dy).sqrt(); + + if length == 0.0 { + return None; + } + + // Normalize the direction vector. + let nx = dx / length; + let ny = dy / length; + + // Perpendicular vector. + let px = -ny; + let py = nx; + + // Create a rectangle path. + let half_thickness = thickness / 2.0; + let x1a = x1 + px * half_thickness; + let y1a = y1 + py * half_thickness; + let x1b = x1 - px * half_thickness; + let y1b = y1 - py * half_thickness; + let x2a = x2 + px * half_thickness; + let y2a = y2 + py * half_thickness; + let x2b = x2 - px * half_thickness; + let y2b = y2 - py * half_thickness; + + Some(format!( + "M {} {} L {} {} L {} {} L {} {} Z", + x1a, y1a, x2a, y2a, x2b, y2b, x1b, y1b + )) +} + +/// Check if three points are approximately collinear. +fn are_collinear( + p1: kurbo::Point, + p2: kurbo::Point, + p3: kurbo::Point, + epsilon: f64, +) -> bool { + // Use cross product to check collinearity. If the cross product of vectors + // `(p2-p1)` and `(p3-p1)` is near zero, points are collinear. + let v1x = p2.x - p1.x; + let v1y = p2.y - p1.y; + let v2x = p3.x - p1.x; + let v2y = p3.y - p1.y; + + let cross_product = (v1x * v2y - v1y * v2x).abs(); + + // Also check that `p2` is between `p1` and `p3` (not beyond them). + let dot1 = v1x * v2x + v1y * v2y; + let dot2 = (p3.x - p2.x) * v2x + (p3.y - p2.y) * v2y; + + cross_product < epsilon && dot1 > 0.0 && dot2 > 0.0 +} + +/// Remove collinear points from a sequence, keeping only first and last. +fn simplify_collinear_points( + points: Vec, + epsilon: f64, +) -> Vec { + if points.len() < 3 { + return points; + } + + let mut result = Vec::new(); + result.push(points[0]); + + let mut i = 0; + while i < points.len() - 1 { + let start = points[i]; + let mut end_idx = i + 1; + + // Find the longest sequence of collinear points starting from `i`. + while end_idx < points.len() - 1 { + // Check if `points[i]`, `points[end_idx]`, and `points[end_idx+1]` + // are collinear. + if are_collinear( + start, + points[end_idx], + points[end_idx + 1], + epsilon, + ) { + end_idx += 1; + } else { + break; + } + } + + // If we found a sequence of 3+ collinear points, skip the middle ones. + if end_idx > i + 1 { + // Skip all middle points, jump to the end of the collinear + // sequence. + i = end_idx; + result.push(points[i]); + } else { + // Not collinear, just add the next point. + result.push(points[i + 1]); + i += 1; + } + } + + result +} + +/// Remove duplicate consecutive path elements that have the same position. +pub fn deduplicate_path_elements(path: &kurbo::BezPath) -> kurbo::BezPath { + use kurbo::{BezPath, PathEl, Point}; + + let mut result = BezPath::new(); + let mut last_point: Option = None; + let mut subpath_start: Option = None; + + // Use epsilon of 0.01 to merge points within 0.01 pixels. This catches + // floating point precision issues from boolean operations. + const EPSILON: f64 = 0.01; + + // Use stricter epsilon for detecting truly degenerate curves. We only want + // to convert curves to lines when control points are VERY close to + // endpoint. 0.0015 catches differences up to ~0.001 while preserving + // legitimate small curves (0.003+). + const DEGENERATE_EPSILON: f64 = 0.0015; + + fn points_equal(p1: Point, p2: Point) -> bool { + (p1.x - p2.x).abs() < EPSILON && (p1.y - p2.y).abs() < EPSILON + } + + fn points_very_close(p1: Point, p2: Point) -> bool { + (p1.x - p2.x).abs() < DEGENERATE_EPSILON + && (p1.y - p2.y).abs() < DEGENERATE_EPSILON + } + + for el in path.iter() { + match el { + PathEl::MoveTo(p) => { + result.push(PathEl::MoveTo(p)); + last_point = Some(p); + subpath_start = Some(p); + } + PathEl::LineTo(p) => { + // Skip if this point is essentially the same as the last point. + if last_point.map_or(true, |last| !points_equal(last, p)) { + // Also skip if this line returns to the subpath start (will + // be handled by `ClosePath`). + if subpath_start + .map_or(false, |start| points_equal(p, start)) + { + // Line returns to start, don't add it - `ClosePath` + // will handle it. + continue; + } + result.push(PathEl::LineTo(p)); + last_point = Some(p); + } + } + PathEl::QuadTo(p1, p2) => { + let start = last_point.unwrap_or(p1); + + // Skip degenerate curve where start and end are nearly + // identical. + if points_equal(start, p2) { + // Curve goes nowhere, skip it entirely. + continue; + } + + // Only skip if this curve returns to the subpath start AND the + // control point is also very close to start/end (meaning it's + // essentially a straight line back). + if subpath_start.map_or(false, |substart| { + points_equal(p2, substart) + && (points_equal(p1, start) || points_equal(p1, p2)) + }) { + continue; + } + + // Skip if the end point is the same as last point. + if last_point.map_or(true, |last| !points_equal(last, p2)) { + // Check if control point is VERY close to the endpoint + // (degenerate curve). Use strict epsilon to avoid + // converting small curve segments to lines. + if points_very_close(p1, p2) { + // Control point is at the endpoint, convert to + // `LineTo`. + result.push(PathEl::LineTo(p2)); + last_point = Some(p2); + } else { + // Simplify control point if it's very close to start. + let simplified_p1 = + if points_equal(p1, start) { start } else { p1 }; + + result.push(PathEl::QuadTo(simplified_p1, p2)); + last_point = Some(p2); + } + } + } + PathEl::CurveTo(p1, p2, p3) => { + let start = last_point.unwrap_or(p1); + + // Skip degenerate curve where start and end are nearly + // identical. + if points_equal(start, p3) { + // Curve goes nowhere, skip it entirely. + continue; + } + + // Only skip if this curve returns to the subpath start AND both + // control points are very close to start/end (meaning it's + // essentially a straight line back). + if subpath_start.map_or(false, |substart| { + points_equal(p3, substart) + && points_equal(p1, start) + && points_equal(p2, p3) + }) { + continue; + } + + // Skip if the end point is the same as last point. + if last_point.map_or(true, |last| !points_equal(last, p3)) { + // Check if both control points are VERY close to the + // endpoint (degenerate curve). Use strict epsilon to avoid + // converting small curve segments to lines. + let p1_near_end = points_very_close(p1, p3); + let p2_near_end = points_very_close(p2, p3); + + if p1_near_end && p2_near_end { + // Both control points are at the endpoint, convert to + // LineTo. + result.push(PathEl::LineTo(p3)); + last_point = Some(p3); + } else { + // Simplify control points if they're very close to + // start or end. + let simplified_p1 = + if points_equal(p1, start) { start } else { p1 }; + let simplified_p2 = + if points_equal(p2, p3) { p3 } else { p2 }; + + result.push(PathEl::CurveTo( + simplified_p1, + simplified_p2, + p3, + )); + last_point = Some(p3); + } + } + } + PathEl::ClosePath => { + result.push(PathEl::ClosePath); + last_point = subpath_start; + subpath_start = None; + } + } + } + + result +} + +/// Simplify path by removing collinear points in sequences of `LineTo` +/// commands. Also replaces curves with `LineTo` if the curve endpoint is +/// collinear with preceding lines. +pub fn simplify_path_collinear(path: &kurbo::BezPath) -> kurbo::BezPath { + use kurbo::{BezPath, PathEl, Point}; + + const EPSILON: f64 = 0.1; // Tolerance for collinearity detection. + + let mut result = BezPath::new(); + let mut current_line_sequence: Vec = Vec::new(); + let mut current_position: Option = None; + let mut line_sequence_start: Option = None; + + // Helper to flush accumulated line sequence. + let flush_line_sequence = + |result: &mut BezPath, sequence: &mut Vec, start: Point| { + if sequence.is_empty() { + return; + } + + // Build full sequence: start point + all `LineTo` endpoints. + let mut full_sequence = vec![start]; + full_sequence.extend(sequence.iter()); + + // Simplify collinear points. + let simplified = simplify_collinear_points(full_sequence, EPSILON); + + // Add all simplified points except the first (it's already in the + // path). + for point in simplified.iter().skip(1) { + result.push(PathEl::LineTo(*point)); + } + + sequence.clear(); + }; + + for el in path.iter() { + match el { + PathEl::MoveTo(p) => { + // Flush any accumulated line sequence. + if let Some(start) = line_sequence_start { + flush_line_sequence( + &mut result, + &mut current_line_sequence, + start, + ); + line_sequence_start = None; + } + + result.push(PathEl::MoveTo(p)); + current_position = Some(p); + } + PathEl::LineTo(p) => { + // Record the start of the line sequence if this is the first + // `LineTo`. + if line_sequence_start.is_none() { + line_sequence_start = current_position; + } + + // Add to current line sequence. + current_line_sequence.push(p); + current_position = Some(p); + } + PathEl::QuadTo(p1, p2) => { + // Flush any accumulated line sequence first. + if let Some(start) = line_sequence_start { + flush_line_sequence( + &mut result, + &mut current_line_sequence, + start, + ); + line_sequence_start = None; + } + + // Always output curves as-is - don't try to simplify them. + result.push(PathEl::QuadTo(p1, p2)); + current_position = Some(p2); + } + PathEl::CurveTo(p1, p2, p3) => { + // Flush any accumulated line sequence first. + if let Some(start) = line_sequence_start { + flush_line_sequence( + &mut result, + &mut current_line_sequence, + start, + ); + line_sequence_start = None; + } + + // Always output curves as-is - don't try to simplify them. + result.push(PathEl::CurveTo(p1, p2, p3)); + current_position = Some(p3); + } + PathEl::ClosePath => { + // Flush line sequence before closing. + if let Some(start) = line_sequence_start { + flush_line_sequence( + &mut result, + &mut current_line_sequence, + start, + ); + line_sequence_start = None; + } + + result.push(PathEl::ClosePath); + current_position = None; + } + } + } + + // Flush any remaining line sequence. + if let Some(start) = line_sequence_start { + flush_line_sequence(&mut result, &mut current_line_sequence, start); + } + + result +} + +/// Round a number to a maximum of 4 decimal places, removing trailing zeros. +fn round_coordinate(value: f64) -> f64 { + // Round to 4 decimal places. + let rounded = (value * 1000.0).round() / 1000.0; + + // Check if it's very close to an integer. + if (rounded - rounded.round()).abs() < 0.001 { + rounded.round() + } else { + rounded + } +} + +/// Round all coordinates in a path to maximum 4 decimal places. +pub fn round_path_coordinates(path: &kurbo::BezPath) -> kurbo::BezPath { + use kurbo::{BezPath, PathEl, Point}; + + let mut result = BezPath::new(); + + for el in path.iter() { + let rounded_el = match el { + PathEl::MoveTo(p) => PathEl::MoveTo(Point::new( + round_coordinate(p.x), + round_coordinate(p.y), + )), + PathEl::LineTo(p) => PathEl::LineTo(Point::new( + round_coordinate(p.x), + round_coordinate(p.y), + )), + PathEl::QuadTo(p1, p2) => PathEl::QuadTo( + Point::new(round_coordinate(p1.x), round_coordinate(p1.y)), + Point::new(round_coordinate(p2.x), round_coordinate(p2.y)), + ), + PathEl::CurveTo(p1, p2, p3) => PathEl::CurveTo( + Point::new(round_coordinate(p1.x), round_coordinate(p1.y)), + Point::new(round_coordinate(p2.x), round_coordinate(p2.y)), + Point::new(round_coordinate(p3.x), round_coordinate(p3.y)), + ), + PathEl::ClosePath => PathEl::ClosePath, + }; + result.push(rounded_el); + } + + result +} + +/// Combine paths using linesweeper boolean operations. +pub fn combine_paths( + paths: &[PathWithMode], + options: &OptimizationOptions, +) -> Option { + use kurbo::BezPath; + use linesweeper::{binary_op, BinaryOp, FillRule}; + + if paths.is_empty() { + eprintln!("No paths to combine."); + return None; + } + + // Deduplicate identical paths with the same mode. `Union(A, A) = A`, so we + // only need to include each unique path once per mode. + use std::collections::HashSet; + let mut seen_union: HashSet = HashSet::new(); + let mut seen_difference: HashSet = HashSet::new(); + let mut deduplicated_paths: Vec = Vec::new(); + let mut skipped_count = 0; + + for path_with_mode in paths { + let seen = if path_with_mode.mode { + &mut seen_union + } else { + &mut seen_difference + }; + + if seen.contains(&path_with_mode.path) { + // Skip duplicate path with same mode. + skipped_count += 1; + continue; + } + + seen.insert(path_with_mode.path.clone()); + deduplicated_paths.push(path_with_mode.clone()); + } + + if skipped_count > 0 { + eprintln!("Skipped {} duplicate path(s)", skipped_count); + } + + if deduplicated_paths.is_empty() { + eprintln!("No paths to combine after deduplication."); + return None; + } + + // If only one path, return it directly. + if deduplicated_paths.len() == 1 { + return Some(deduplicated_paths[0].path.clone()); + } + + // Parse the first path as the starting result. + let mut result = match BezPath::from_svg(&deduplicated_paths[0].path) { + Ok(path) => path, + Err(e) => { + eprintln!("Failed to parse first path: {:?}", e); + return None; + } + }; + + // Apply boolean operations for each subsequent path. + for (i, path_with_mode) in deduplicated_paths[1..].iter().enumerate() { + let path = match BezPath::from_svg(&path_with_mode.path) { + Ok(p) => p, + Err(e) => { + eprintln!("Failed to parse path {}: {:?}", i + 1, e); + continue; + } + }; + + let op = if path_with_mode.mode { + BinaryOp::Union + } else { + BinaryOp::Difference + }; + + // Perform boolean operation and convert result back to `BezPath`. + let contours = match binary_op(&result, &path, FillRule::NonZero, op) { + Ok(c) => c, + Err(e) => { + eprintln!( + "Boolean operation {} failed for path {}: {:?}", + if path_with_mode.mode { + "union" + } else { + "difference" + }, + i + 1, + e + ); + continue; // Skip this operation on error. + } + }; + + // Convert contours back to a single `BezPath` by concatenating all + // contour paths. + result = BezPath::new(); + for group in contours.grouped() { + for contour_idx in group { + let contour_path = &contours[contour_idx].path; + result.extend(contour_path.iter()); + } + } + } + + // Apply optimizations based on options. + let mut optimized = result; + + if options.enable_rounding { + // Round coordinates to maximum 4 decimal places. + optimized = round_path_coordinates(&optimized); + } + + if options.enable_deduplication { + // Remove duplicate consecutive anchors with identical positions. + optimized = deduplicate_path_elements(&optimized); + } + + if options.enable_collinear_simplification { + // Remove collinear points. + optimized = simplify_path_collinear(&optimized); + } + + Some(optimized.to_svg()) +} + +/// Generate SVG from icon. +pub fn icon_to_svg( + icon: &Icon, + sketch_mode: bool, + paths: &[PathWithMode], + options: &OptimizationOptions, +) -> String { + let svg_content = if sketch_mode { + // In sketch mode, output raw path elements without combining. + let mut path_elements = String::new(); + for path_with_mode in paths { + let class = if path_with_mode.mode { + "sketch-path-union" + } else { + "sketch-path-subtract" + }; + path_elements.push_str(&format!( + r#""#, + path_with_mode.path, class + )); + } + path_elements + } else { + // In final mode, combine all paths into a single SVG path. + if let Some(combined) = combine_paths(paths, options) { + format!(r#""#, combined) + } else { + eprintln!( + "No combined path constructed for icon `{}`.", + icon.name.as_deref().unwrap_or("unnamed") + ); + String::new() + } + }; + + format!( + concat!( + r#""#, + r#"{}"# + ), + svg_content + ) +} + +/// Generate all icons and save them to files. +pub fn generate_icons( + content: &str, + output_dir: &Path, + sketch_mode: bool, + options: &OptimizationOptions, +) -> Result { + // Parse the iconscript file. + let icons = parse_iconscript(content, sketch_mode)?; + + // Ensure output directory exists. + fs::create_dir_all(output_dir)?; + + let mut icon_count = 0; + + for (i, (icon, paths)) in icons.iter().enumerate() { + let svg = icon_to_svg(icon, sketch_mode, paths, options); + + if !svg.is_empty() { + let filename = if let Some(ref name) = icon.name { + if name != "temp" { + format!("{}.svg", name) + } else { + format!("icon_{}.svg", i) + } + } else { + format!("icon_{}.svg", i) + }; + + let filepath = output_dir.join(&filename); + fs::write(&filepath, svg)?; + println!("Generated: {}", filename); + icon_count += 1; + } + } + + Ok(icon_count) +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..7e4c4f4 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,5 @@ +// Library interface for testing. +pub mod generator; +pub mod listener; +pub mod parser; +pub mod types; diff --git a/rust/src/listener.rs b/rust/src/listener.rs new file mode 100644 index 0000000..bf5030a --- /dev/null +++ b/rust/src/listener.rs @@ -0,0 +1,486 @@ +use antlr_rust::token::Token; +use antlr_rust::tree::ParseTree; +use antlr_rust::{ + common_token_stream::CommonTokenStream, tree::ParseTreeWalker, InputStream, +}; +use anyhow::Result; +use std::collections::HashMap; +use std::rc::Rc; + +use crate::generator::{create_circle_path, create_thick_line_path}; +use crate::parser::iconscriptparser::*; +use crate::parser::*; +use crate::types::{Icon, PathWithMode, Point, Scope}; + +const SCALE: f64 = 1.0; + +/// Parse iconscript content and return icons with their paths. +pub fn parse_iconscript( + content: &str, + sketch_mode: bool, +) -> Result)>> { + + let input = InputStream::new(content); + let lexer = IconScriptLexer::new(input); + let token_stream = CommonTokenStream::new(lexer); + let mut parser = IconScriptParser::new(token_stream); + let script = parser.script().expect("Failed to parse script"); + + let listener = IconScriptListenerImpl::new(sketch_mode); + let listener = ParseTreeWalker::::walk( + Box::new(listener), + &*script, + ); + + Ok(listener.into_icons()) +} + +struct IconScriptListenerImpl<'input> { + variables: HashMap>>, + icons: Vec<(Icon, Vec)>, + current_icon: Option, + paths: Vec, + scopes: Vec, +} + +impl<'input> IconScriptListenerImpl<'input> { + fn new(_sketch_mode: bool) -> Self { + Self { + variables: HashMap::new(), + icons: Vec::new(), + current_icon: None, + paths: Vec::new(), + scopes: vec![Scope::new()], + } + } + + fn get_scope(&self) -> &Scope { + self.scopes.last().unwrap() + } + + fn get_scope_mut(&mut self) -> &mut Scope { + self.scopes.last_mut().unwrap() + } + + fn into_icons(self) -> Vec<(Icon, Vec)> { + self.icons + } + + fn parse_position(&mut self, ctx: &PositionContext) -> Point { + let x_text = ctx.x.as_ref().unwrap().get_text(); + let y_text = ctx.y.as_ref().unwrap().get_text(); + + let x: f64 = x_text.parse().unwrap_or(0.0); + let y: f64 = y_text.parse().unwrap_or(0.0); + + let is_relative = ctx.relative.is_some(); + + let position = if is_relative { + let current = self.get_scope().position; + current.add(&Point::new(x, y)) + } else { + Point::new(x + 0.5, y + 0.5) + }; + + self.get_scope_mut().position = position; + position + } + + fn arc_point(center: Point, angle: f64, radius: f64) -> Point { + Point::new( + center.x + angle.cos() * radius, + center.y - angle.sin() * radius, + ) + } + + /// Walk a `CommandsContext` to process variable expansion. This manually + /// triggers the listener methods for each child command. + fn walk_commands(&mut self, commands_ctx: &CommandsContext<'input>) { + use antlr_rust::tree::Tree; + + // Process children in order (commands and scopes interleaved). + // We track indices separately because command_all() and scope_all() + // return them in order within their own type. + let mut command_idx = 0; + let mut scope_idx = 0; + + for i in 0..commands_ctx.get_child_count() { + if let Some(child) = commands_ctx.get_child(i) { + let rule_index = child.get_rule_index(); + if rule_index == RULE_command { + if let Some(cmd) = commands_ctx.command(command_idx) { + self.process_command(&cmd); + } + command_idx += 1; + } else if rule_index == RULE_scope { + if let Some(scope) = commands_ctx.scope(scope_idx) { + self.enter_scope(&scope); + if let Some(inner_commands) = scope.commands() { + self.walk_commands(&inner_commands); + } + self.exit_scope(&scope); + } + scope_idx += 1; + } + } + } + } + + /// Process a single command, handling variable expansion. + fn process_command(&mut self, ctx: &CommandContext<'input>) { + use crate::parser::iconscriptparser::CommandContextAttrs; + + // Check if this is a variable reference. + if let Some(var_token) = ctx.VARIABLE() { + let var_name = &var_token.get_text()[1..]; // Remove '@' prefix + if let Some(commands_ctx) = self.variables.get(var_name).cloned() { + self.walk_commands(&commands_ctx); + } + return; + } + + // Handle other command types by calling their exit methods. + if let Some(line_ctx) = ctx.line() { + self.exit_line(&line_ctx); + } else if let Some(circle_ctx) = ctx.circle() { + self.exit_circle(&circle_ctx); + } else if let Some(arc_ctx) = ctx.arc() { + self.exit_arc(&arc_ctx); + } else if let Some(rect_ctx) = ctx.rectangle() { + self.exit_rectangle(&rect_ctx); + } else if let Some(pos_ctx) = ctx.setPosition() { + self.exit_setPosition(&pos_ctx); + } else if let Some(width_ctx) = ctx.setWidth() { + self.exit_setWidth(&width_ctx); + } else if let Some(remove_ctx) = ctx.setRemove() { + self.exit_setRemove(&remove_ctx); + } else if let Some(name_ctx) = ctx.name() { + self.exit_name(&name_ctx); + } + } +} + +impl<'input> antlr_rust::tree::ParseTreeListener<'input, IconScriptParserContextType> + for IconScriptListenerImpl<'input> +{ +} + +impl<'input> IconScriptListener<'input> for IconScriptListenerImpl<'input> { + fn enter_icon(&mut self, _ctx: &IconContext<'input>) { + self.current_icon = Some(Icon::new()); + self.paths.clear(); + self.scopes = vec![Scope::new()]; + } + + fn exit_icon(&mut self, _ctx: &IconContext<'input>) { + if let Some(icon) = self.current_icon.take() { + let paths = std::mem::take(&mut self.paths); + self.icons.push((icon, paths)); + } + } + + fn enter_assignment(&mut self, ctx: &AssignmentContext<'input>) { + if let (Some(left), Some(right)) = (&ctx.left, &ctx.right) { + let var_name = left.get_text().to_string(); + // Store the CommandsContext for later expansion. + self.variables.insert(var_name, right.clone()); + } + } + + fn enter_command(&mut self, ctx: &CommandContext<'input>) { + + // Check if this is a variable reference. + if let Some(var_token) = ctx.VARIABLE() { + let var_name = &var_token.get_text()[1..]; // Remove '@' prefix + if let Some(commands_ctx) = self.variables.get(var_name).cloned() { + self.walk_commands(&commands_ctx); + } + } + } + + fn enter_scope(&mut self, _ctx: &ScopeContext<'input>) { + let new_scope = self.scopes.last().unwrap().deep_copy(); + self.scopes.push(new_scope); + } + + fn exit_scope(&mut self, _ctx: &ScopeContext<'input>) { + self.scopes.pop(); + } + + fn exit_name(&mut self, ctx: &NameContext<'input>) { + if let Some(icon) = &mut self.current_icon { + let name = ctx.get_text(); + icon.name = Some(name); + } + } + + fn exit_circle(&mut self, ctx: &CircleContext<'input>) { + if let Some(pos_ctx) = ctx.position() { + let center = self.parse_position(&pos_ctx); + let radius_text = ctx.FLOAT().unwrap().get_text(); + let radius: f64 = radius_text.parse().unwrap_or(0.0); + + if let Some(path) = + create_circle_path(center.x, center.y, radius / 2.0) + { + self.paths.push(PathWithMode { + path, + mode: self.get_scope().uniting, + }); + } + } + } + + fn exit_line(&mut self, ctx: &LineContext<'input>) { + let is_filled = ctx.get_text().contains("lf"); + let positions: Vec = ctx + .position_all() + .iter() + .map(|pos| self.parse_position(pos)) + .collect(); + + if positions.is_empty() { + return; + } + + let scope = self.get_scope(); + let width = scope.width; + let uniting = scope.uniting; + + // Add circles at all points. + for pos in &positions { + if let Some(path) = create_circle_path(pos.x, pos.y, width / 2.0) { + self.paths.push(PathWithMode { + path, + mode: uniting, + }); + } + } + + // Add lines between consecutive points. + for i in 0..positions.len() - 1 { + let from = positions[i]; + let to = positions[i + 1]; + if let Some(path) = + create_thick_line_path(from.x, from.y, to.x, to.y, width) + { + self.paths.push(PathWithMode { + path, + mode: uniting, + }); + } + } + + // If filled, add a filled polyline. + if is_filled && positions.len() >= 2 { + let mut path = format!("M {} {}", positions[0].x, positions[0].y); + for pos in &positions[1..] { + path.push_str(&format!(" L {} {}", pos.x, pos.y)); + } + path.push_str(" Z"); + self.paths.push(PathWithMode { + path, + mode: uniting, + }); + } + } + + fn exit_rectangle(&mut self, ctx: &RectangleContext<'input>) { + let positions = ctx.position_all(); + if positions.len() < 2 { + return; + } + + let point1 = self.parse_position(&positions[0]); + let point2 = self.parse_position(&positions[1]); + + let scope = self.get_scope(); + let width = scope.width; + let uniting = scope.uniting; + + let p1 = Point::new(point2.x, point1.y); + let p2 = Point::new(point1.x, point2.y); + + // Add circles at all four corners. + let corners = [point1, p1, point2, p2]; + for corner in &corners { + if let Some(path) = + create_circle_path(corner.x, corner.y, width / 2.0) + { + self.paths.push(PathWithMode { + path, + mode: uniting, + }); + } + } + + let half_width = width / 2.0; + + // Add filled rectangles. + let rect_path1 = format!( + "M {} {} L {} {} L {} {} L {} {} Z", + point1.x - half_width, + point1.y, + p1.x + half_width, + p1.y, + point2.x + half_width, + point2.y, + p2.x - half_width, + p2.y + ); + let rect_path2 = format!( + "M {} {} L {} {} L {} {} L {} {} Z", + point1.x, + point1.y - half_width, + p1.x, + p1.y - half_width, + point2.x, + point2.y + half_width, + p2.x, + p2.y + half_width + ); + + self.paths.push(PathWithMode { + path: rect_path1, + mode: uniting, + }); + self.paths.push(PathWithMode { + path: rect_path2, + mode: uniting, + }); + } + + fn exit_arc(&mut self, ctx: &ArcContext<'input>) { + use std::f64::consts::PI; + + if let Some(pos_ctx) = ctx.position() { + let pos = self.parse_position(&pos_ctx); + + let floats = ctx.FLOAT_all(); + if floats.len() < 3 { + return; + } + + let radius: f64 = + floats[0].get_text().parse().unwrap_or(0.0) * SCALE; + let start_angle: f64 = floats[1].get_text().parse().unwrap_or(0.0); + let end_angle: f64 = floats[2].get_text().parse().unwrap_or(0.0); + + let center = Point::new(pos.x + 0.5, pos.y + 0.5); + + let tau = 2.0 * PI; + let mut delta = end_angle - start_angle; + + if delta.abs() < 1e-9 { + return; + } + if delta.abs() > tau { + let wrapped = ((delta % tau) + tau) % tau; + delta = if delta < 0.0 { wrapped - tau } else { wrapped }; + if delta.abs() < 1e-9 { + return; + } + } + + let half_width = self.get_scope().width / 2.0; + let uniting = self.get_scope().uniting; + let outer_radius = radius + half_width; + let inner_radius = (radius - half_width).max(0.0); + + let large_arc_flag = if delta.abs() > PI { 1 } else { 0 }; + let sweep_flag = if delta < 0.0 { 1 } else { 0 }; + + let start_outer = + Self::arc_point(center, start_angle, outer_radius); + let end_outer = Self::arc_point(center, end_angle, outer_radius); + + let arc_path = if inner_radius == 0.0 { + format!( + "M {} {} A {} {} 0 {} {} {} {} L {} {} Z", + start_outer.x, + start_outer.y, + outer_radius, + outer_radius, + large_arc_flag, + sweep_flag, + end_outer.x, + end_outer.y, + center.x, + center.y + ) + } else { + let end_inner = + Self::arc_point(center, end_angle, inner_radius); + let start_inner = + Self::arc_point(center, start_angle, inner_radius); + let sweep_inner = if sweep_flag == 1 { 0 } else { 1 }; + format!( + concat!( + "M {} {} A {} {} 0 {} {} {} {} L {} {} ", + "A {} {} 0 {} {} {} {} Z" + ), + start_outer.x, + start_outer.y, + outer_radius, + outer_radius, + large_arc_flag, + sweep_flag, + end_outer.x, + end_outer.y, + end_inner.x, + end_inner.y, + inner_radius, + inner_radius, + large_arc_flag, + sweep_inner, + start_inner.x, + start_inner.y + ) + }; + + self.paths.push(PathWithMode { + path: arc_path, + mode: uniting, + }); + + // Add round end caps. + if half_width > 0.0 { + let cap_start = Self::arc_point(center, start_angle, radius); + let cap_end = Self::arc_point(center, end_angle, radius); + if let Some(path1) = + create_circle_path(cap_start.x, cap_start.y, half_width) + { + self.paths.push(PathWithMode { + path: path1, + mode: uniting, + }); + } + if let Some(path2) = + create_circle_path(cap_end.x, cap_end.y, half_width) + { + self.paths.push(PathWithMode { + path: path2, + mode: uniting, + }); + } + } + } + } + + fn exit_setPosition(&mut self, ctx: &SetPositionContext<'input>) { + if let Some(pos_ctx) = ctx.position() { + self.parse_position(&pos_ctx); + } + } + + fn exit_setWidth(&mut self, ctx: &SetWidthContext<'input>) { + if let Some(float_token) = ctx.FLOAT() { + let width: f64 = float_token.get_text().parse().unwrap_or(1.0); + self.get_scope_mut().width = width; + } + } + + fn exit_setRemove(&mut self, _ctx: &SetRemoveContext<'input>) { + self.get_scope_mut().uniting = false; + } +} diff --git a/rust/src/main.rs b/rust/src/main.rs new file mode 100644 index 0000000..0c01b87 --- /dev/null +++ b/rust/src/main.rs @@ -0,0 +1,79 @@ +use anyhow::Result; +use clap::Parser as ClapParser; +use std::fs; +use std::path::PathBuf; + +mod generator; +mod listener; +mod parser; +mod types; + +use generator::{generate_icons, OptimizationOptions}; + +#[derive(ClapParser, Debug)] +#[command( + name = "iconscript", + about = "iconscript parser and SVG generator", + version +)] +struct Args { + /// Input iconscript file. + #[arg(value_name = "INPUT")] + input: Option, + + /// Output directory for SVG files. + #[arg(value_name = "DIR")] + output: Option, + + /// Enable sketch mode (output raw paths without combining). + #[arg(short, long)] + sketch: bool, + + /// Disable coordinate rounding. + #[arg(long)] + no_rounding: bool, + + /// Disable duplicate point removal. + #[arg(long)] + no_deduplication: bool, + + /// Disable collinear point simplification. + #[arg(long)] + no_collinear: bool, +} + +fn main() -> Result<()> { + let args = Args::parse(); + + let input_file = args + .input + .unwrap_or_else(|| PathBuf::from("main.iconscript")); + let output_dir = args.output.unwrap_or_else(|| PathBuf::from("output")); + let sketch_mode = args.sketch; + + // Build optimization options from CLI flags. + let optimization_options = OptimizationOptions { + enable_rounding: !args.no_rounding, + enable_deduplication: !args.no_deduplication, + enable_collinear_simplification: !args.no_collinear, + }; + + // Read input file. + let content = fs::read_to_string(&input_file)?; + + // Parse and generate icons. + let icon_count = generate_icons( + &content, + &output_dir, + sketch_mode, + &optimization_options, + )?; + + println!( + "\nGenerated {} SVG files in the `{}` directory.", + icon_count, + output_dir.display() + ); + + Ok(()) +} diff --git a/rust/src/parser/iconscriptlexer.rs b/rust/src/parser/iconscriptlexer.rs new file mode 100644 index 0000000..0fdb8e0 --- /dev/null +++ b/rust/src/parser/iconscriptlexer.rs @@ -0,0 +1,276 @@ +// Generated from ../grammar/IconScript.g4 by ANTLR 4.8 +#![allow(dead_code)] +#![allow(nonstandard_style)] +#![allow(unused_imports)] +#![allow(unused_variables)] +use antlr_rust::atn::ATN; +use antlr_rust::char_stream::CharStream; +use antlr_rust::int_stream::IntStream; +use antlr_rust::lexer::{BaseLexer, Lexer, LexerRecog}; +use antlr_rust::atn_deserializer::ATNDeserializer; +use antlr_rust::dfa::DFA; +use antlr_rust::lexer_atn_simulator::{LexerATNSimulator, ILexerATNSimulator}; +use antlr_rust::PredictionContextCache; +use antlr_rust::recognizer::{Recognizer,Actions}; +use antlr_rust::error_listener::ErrorListener; +use antlr_rust::TokenSource; +use antlr_rust::token_factory::{TokenFactory,CommonTokenFactory,TokenAware}; +use antlr_rust::token::*; +use antlr_rust::rule_context::{BaseRuleContext,EmptyCustomRuleContext,EmptyContext}; +use antlr_rust::parser_rule_context::{ParserRuleContext,BaseParserRuleContext,cast}; +use antlr_rust::vocabulary::{Vocabulary,VocabularyImpl}; + +use antlr_rust::{lazy_static,Tid,TidAble,TidExt}; + +use std::sync::Arc; +use std::cell::RefCell; +use std::rc::Rc; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; + + + pub const T__0:isize=1; + pub const T__1:isize=2; + pub const T__2:isize=3; + pub const T__3:isize=4; + pub const T__4:isize=5; + pub const T__5:isize=6; + pub const T__6:isize=7; + pub const T__7:isize=8; + pub const T__8:isize=9; + pub const T__9:isize=10; + pub const T__10:isize=11; + pub const T__11:isize=12; + pub const T__12:isize=13; + pub const T__13:isize=14; + pub const VARIABLE:isize=15; + pub const FLOAT:isize=16; + pub const IDENTIFIER:isize=17; + pub const COMMENT:isize=18; + pub const WS:isize=19; + pub const channelNames: [&'static str;0+2] = [ + "DEFAULT_TOKEN_CHANNEL", "HIDDEN" + ]; + + pub const modeNames: [&'static str;1] = [ + "DEFAULT_MODE" + ]; + + pub const ruleNames: [&'static str;19] = [ + "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8", + "T__9", "T__10", "T__11", "T__12", "T__13", "VARIABLE", "FLOAT", "IDENTIFIER", + "COMMENT", "WS" + ]; + + + pub const _LITERAL_NAMES: [Option<&'static str>;15] = [ + None, Some("'+'"), Some("','"), Some("'='"), Some("'{'"), Some("'}'"), + Some("'icon'"), Some("'a'"), Some("'e'"), Some("'l'"), Some("'lf'"), Some("'r'"), + Some("'m'"), Some("'w'"), Some("'subtract'") + ]; + pub const _SYMBOLIC_NAMES: [Option<&'static str>;20] = [ + None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, Some("VARIABLE"), Some("FLOAT"), Some("IDENTIFIER"), + Some("COMMENT"), Some("WS") + ]; + lazy_static!{ + static ref _shared_context_cache: Arc = Arc::new(PredictionContextCache::new()); + static ref VOCABULARY: Box = Box::new(VocabularyImpl::new(_LITERAL_NAMES.iter(), _SYMBOLIC_NAMES.iter(), None)); + } + + +pub type LexerContext<'input> = BaseRuleContext<'input,EmptyCustomRuleContext<'input,LocalTokenFactory<'input> >>; +pub type LocalTokenFactory<'input> = CommonTokenFactory; + +type From<'a> = as TokenFactory<'a> >::From; + +pub struct IconScriptLexer<'input, Input:CharStream >> { + base: BaseLexer<'input,IconScriptLexerActions,Input,LocalTokenFactory<'input>>, +} + +antlr_rust::tid! { impl<'input,Input> TidAble<'input> for IconScriptLexer<'input,Input> where Input:CharStream > } + +impl<'input, Input:CharStream >> Deref for IconScriptLexer<'input,Input>{ + type Target = BaseLexer<'input,IconScriptLexerActions,Input,LocalTokenFactory<'input>>; + + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl<'input, Input:CharStream >> DerefMut for IconScriptLexer<'input,Input>{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.base + } +} + + +impl<'input, Input:CharStream >> IconScriptLexer<'input,Input>{ + fn get_rule_names(&self) -> &'static [&'static str] { + &ruleNames + } + fn get_literal_names(&self) -> &[Option<&str>] { + &_LITERAL_NAMES + } + + fn get_symbolic_names(&self) -> &[Option<&str>] { + &_SYMBOLIC_NAMES + } + + fn get_grammar_file_name(&self) -> &'static str { + "IconScriptLexer.g4" + } + + pub fn new_with_token_factory(input: Input, tf: &'input LocalTokenFactory<'input>) -> Self { + antlr_rust::recognizer::check_version("0","3"); + Self { + base: BaseLexer::new_base_lexer( + input, + LexerATNSimulator::new_lexer_atnsimulator( + _ATN.clone(), + _decision_to_DFA.clone(), + _shared_context_cache.clone(), + ), + IconScriptLexerActions{}, + tf + ) + } + } +} + +impl<'input, Input:CharStream >> IconScriptLexer<'input,Input> where &'input LocalTokenFactory<'input>:Default{ + pub fn new(input: Input) -> Self{ + IconScriptLexer::new_with_token_factory(input, <&LocalTokenFactory<'input> as Default>::default()) + } +} + +pub struct IconScriptLexerActions { +} + +impl IconScriptLexerActions{ +} + +impl<'input, Input:CharStream >> Actions<'input,BaseLexer<'input,IconScriptLexerActions,Input,LocalTokenFactory<'input>>> for IconScriptLexerActions{ + } + + impl<'input, Input:CharStream >> IconScriptLexer<'input,Input>{ + +} + +impl<'input, Input:CharStream >> LexerRecog<'input,BaseLexer<'input,IconScriptLexerActions,Input,LocalTokenFactory<'input>>> for IconScriptLexerActions{ +} +impl<'input> TokenAware<'input> for IconScriptLexerActions{ + type TF = LocalTokenFactory<'input>; +} + +impl<'input, Input:CharStream >> TokenSource<'input> for IconScriptLexer<'input,Input>{ + type TF = LocalTokenFactory<'input>; + + fn next_token(&mut self) -> >::Tok { + self.base.next_token() + } + + fn get_line(&self) -> isize { + self.base.get_line() + } + + fn get_char_position_in_line(&self) -> isize { + self.base.get_char_position_in_line() + } + + fn get_input_stream(&mut self) -> Option<&mut dyn IntStream> { + self.base.get_input_stream() + } + + fn get_source_name(&self) -> String { + self.base.get_source_name() + } + + fn get_token_factory(&self) -> &'input Self::TF { + self.base.get_token_factory() + } +} + + + + lazy_static! { + static ref _ATN: Arc = + Arc::new(ATNDeserializer::new(None).deserialize(_serializedATN.chars())); + static ref _decision_to_DFA: Arc>> = { + let mut dfa = Vec::new(); + let size = _ATN.decision_to_state.len(); + for i in 0..size { + dfa.push(DFA::new( + _ATN.clone(), + _ATN.get_decision_state(i), + i as isize, + ).into()) + } + Arc::new(dfa) + }; + } + + + + const _serializedATN:&'static str = + "\x03\u{608b}\u{a72a}\u{8133}\u{b9ed}\u{417c}\u{3be7}\u{7786}\u{5964}\x02\ + \x15\x78\x08\x01\x04\x02\x09\x02\x04\x03\x09\x03\x04\x04\x09\x04\x04\x05\ + \x09\x05\x04\x06\x09\x06\x04\x07\x09\x07\x04\x08\x09\x08\x04\x09\x09\x09\ + \x04\x0a\x09\x0a\x04\x0b\x09\x0b\x04\x0c\x09\x0c\x04\x0d\x09\x0d\x04\x0e\ + \x09\x0e\x04\x0f\x09\x0f\x04\x10\x09\x10\x04\x11\x09\x11\x04\x12\x09\x12\ + \x04\x13\x09\x13\x04\x14\x09\x14\x03\x02\x03\x02\x03\x03\x03\x03\x03\x04\ + \x03\x04\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03\x07\x03\x07\x03\x07\ + \x03\x07\x03\x08\x03\x08\x03\x09\x03\x09\x03\x0a\x03\x0a\x03\x0b\x03\x0b\ + \x03\x0b\x03\x0c\x03\x0c\x03\x0d\x03\x0d\x03\x0e\x03\x0e\x03\x0f\x03\x0f\ + \x03\x0f\x03\x0f\x03\x0f\x03\x0f\x03\x0f\x03\x0f\x03\x0f\x03\x10\x03\x10\ + \x03\x10\x03\x11\x05\x11\x55\x0a\x11\x03\x11\x06\x11\x58\x0a\x11\x0d\x11\ + \x0e\x11\x59\x03\x11\x03\x11\x07\x11\x5e\x0a\x11\x0c\x11\x0e\x11\x61\x0b\ + \x11\x05\x11\x63\x0a\x11\x03\x12\x03\x12\x07\x12\x67\x0a\x12\x0c\x12\x0e\ + \x12\x6a\x0b\x12\x03\x13\x03\x13\x07\x13\x6e\x0a\x13\x0c\x13\x0e\x13\x71\ + \x0b\x13\x03\x13\x03\x13\x03\x14\x03\x14\x03\x14\x03\x14\x02\x02\x15\x03\ + \x03\x05\x04\x07\x05\x09\x06\x0b\x07\x0d\x08\x0f\x09\x11\x0a\x13\x0b\x15\ + \x0c\x17\x0d\x19\x0e\x1b\x0f\x1d\x10\x1f\x11\x21\x12\x23\x13\x25\x14\x27\ + \x15\x03\x02\x07\x03\x02\x32\x3b\x05\x02\x43\x5c\x61\x61\x63\x7c\x06\x02\ + \x32\x3b\x43\x5c\x61\x61\x63\x7c\x04\x02\x0c\x0c\x0f\x0f\x05\x02\x0b\x0c\ + \x0f\x0f\x22\x22\x02\x7d\x02\x03\x03\x02\x02\x02\x02\x05\x03\x02\x02\x02\ + \x02\x07\x03\x02\x02\x02\x02\x09\x03\x02\x02\x02\x02\x0b\x03\x02\x02\x02\ + \x02\x0d\x03\x02\x02\x02\x02\x0f\x03\x02\x02\x02\x02\x11\x03\x02\x02\x02\ + \x02\x13\x03\x02\x02\x02\x02\x15\x03\x02\x02\x02\x02\x17\x03\x02\x02\x02\ + \x02\x19\x03\x02\x02\x02\x02\x1b\x03\x02\x02\x02\x02\x1d\x03\x02\x02\x02\ + \x02\x1f\x03\x02\x02\x02\x02\x21\x03\x02\x02\x02\x02\x23\x03\x02\x02\x02\ + \x02\x25\x03\x02\x02\x02\x02\x27\x03\x02\x02\x02\x03\x29\x03\x02\x02\x02\ + \x05\x2b\x03\x02\x02\x02\x07\x2d\x03\x02\x02\x02\x09\x2f\x03\x02\x02\x02\ + \x0b\x31\x03\x02\x02\x02\x0d\x33\x03\x02\x02\x02\x0f\x38\x03\x02\x02\x02\ + \x11\x3a\x03\x02\x02\x02\x13\x3c\x03\x02\x02\x02\x15\x3e\x03\x02\x02\x02\ + \x17\x41\x03\x02\x02\x02\x19\x43\x03\x02\x02\x02\x1b\x45\x03\x02\x02\x02\ + \x1d\x47\x03\x02\x02\x02\x1f\x50\x03\x02\x02\x02\x21\x54\x03\x02\x02\x02\ + \x23\x64\x03\x02\x02\x02\x25\x6b\x03\x02\x02\x02\x27\x74\x03\x02\x02\x02\ + \x29\x2a\x07\x2d\x02\x02\x2a\x04\x03\x02\x02\x02\x2b\x2c\x07\x2e\x02\x02\ + \x2c\x06\x03\x02\x02\x02\x2d\x2e\x07\x3f\x02\x02\x2e\x08\x03\x02\x02\x02\ + \x2f\x30\x07\x7d\x02\x02\x30\x0a\x03\x02\x02\x02\x31\x32\x07\x7f\x02\x02\ + \x32\x0c\x03\x02\x02\x02\x33\x34\x07\x6b\x02\x02\x34\x35\x07\x65\x02\x02\ + \x35\x36\x07\x71\x02\x02\x36\x37\x07\x70\x02\x02\x37\x0e\x03\x02\x02\x02\ + \x38\x39\x07\x63\x02\x02\x39\x10\x03\x02\x02\x02\x3a\x3b\x07\x67\x02\x02\ + \x3b\x12\x03\x02\x02\x02\x3c\x3d\x07\x6e\x02\x02\x3d\x14\x03\x02\x02\x02\ + \x3e\x3f\x07\x6e\x02\x02\x3f\x40\x07\x68\x02\x02\x40\x16\x03\x02\x02\x02\ + \x41\x42\x07\x74\x02\x02\x42\x18\x03\x02\x02\x02\x43\x44\x07\x6f\x02\x02\ + \x44\x1a\x03\x02\x02\x02\x45\x46\x07\x79\x02\x02\x46\x1c\x03\x02\x02\x02\ + \x47\x48\x07\x75\x02\x02\x48\x49\x07\x77\x02\x02\x49\x4a\x07\x64\x02\x02\ + \x4a\x4b\x07\x76\x02\x02\x4b\x4c\x07\x74\x02\x02\x4c\x4d\x07\x63\x02\x02\ + \x4d\x4e\x07\x65\x02\x02\x4e\x4f\x07\x76\x02\x02\x4f\x1e\x03\x02\x02\x02\ + \x50\x51\x07\x42\x02\x02\x51\x52\x05\x23\x12\x02\x52\x20\x03\x02\x02\x02\ + \x53\x55\x07\x2f\x02\x02\x54\x53\x03\x02\x02\x02\x54\x55\x03\x02\x02\x02\ + \x55\x57\x03\x02\x02\x02\x56\x58\x09\x02\x02\x02\x57\x56\x03\x02\x02\x02\ + \x58\x59\x03\x02\x02\x02\x59\x57\x03\x02\x02\x02\x59\x5a\x03\x02\x02\x02\ + \x5a\x62\x03\x02\x02\x02\x5b\x5f\x07\x30\x02\x02\x5c\x5e\x09\x02\x02\x02\ + \x5d\x5c\x03\x02\x02\x02\x5e\x61\x03\x02\x02\x02\x5f\x5d\x03\x02\x02\x02\ + \x5f\x60\x03\x02\x02\x02\x60\x63\x03\x02\x02\x02\x61\x5f\x03\x02\x02\x02\ + \x62\x5b\x03\x02\x02\x02\x62\x63\x03\x02\x02\x02\x63\x22\x03\x02\x02\x02\ + \x64\x68\x09\x03\x02\x02\x65\x67\x09\x04\x02\x02\x66\x65\x03\x02\x02\x02\ + \x67\x6a\x03\x02\x02\x02\x68\x66\x03\x02\x02\x02\x68\x69\x03\x02\x02\x02\ + \x69\x24\x03\x02\x02\x02\x6a\x68\x03\x02\x02\x02\x6b\x6f\x07\x25\x02\x02\ + \x6c\x6e\x0a\x05\x02\x02\x6d\x6c\x03\x02\x02\x02\x6e\x71\x03\x02\x02\x02\ + \x6f\x6d\x03\x02\x02\x02\x6f\x70\x03\x02\x02\x02\x70\x72\x03\x02\x02\x02\ + \x71\x6f\x03\x02\x02\x02\x72\x73\x08\x13\x02\x02\x73\x26\x03\x02\x02\x02\ + \x74\x75\x09\x06\x02\x02\x75\x76\x03\x02\x02\x02\x76\x77\x08\x14\x02\x02\ + \x77\x28\x03\x02\x02\x02\x09\x02\x54\x59\x5f\x62\x68\x6f\x03\x08\x02\x02"; diff --git a/rust/src/parser/iconscriptlistener.rs b/rust/src/parser/iconscriptlistener.rs new file mode 100644 index 0000000..0d15a00 --- /dev/null +++ b/rust/src/parser/iconscriptlistener.rs @@ -0,0 +1,172 @@ +#![allow(nonstandard_style)] +// Generated from ../grammar/IconScript.g4 by ANTLR 4.8 +use antlr_rust::tree::ParseTreeListener; +use super::iconscriptparser::*; + +pub trait IconScriptListener<'input> : ParseTreeListener<'input,IconScriptParserContextType>{ +/** + * Enter a parse tree produced by {@link IconScriptParser#script}. + * @param ctx the parse tree + */ +fn enter_script(&mut self, _ctx: &ScriptContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#script}. + * @param ctx the parse tree + */ +fn exit_script(&mut self, _ctx: &ScriptContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#position}. + * @param ctx the parse tree + */ +fn enter_position(&mut self, _ctx: &PositionContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#position}. + * @param ctx the parse tree + */ +fn exit_position(&mut self, _ctx: &PositionContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#expression}. + * @param ctx the parse tree + */ +fn enter_expression(&mut self, _ctx: &ExpressionContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#expression}. + * @param ctx the parse tree + */ +fn exit_expression(&mut self, _ctx: &ExpressionContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#assignment}. + * @param ctx the parse tree + */ +fn enter_assignment(&mut self, _ctx: &AssignmentContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#assignment}. + * @param ctx the parse tree + */ +fn exit_assignment(&mut self, _ctx: &AssignmentContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#scope}. + * @param ctx the parse tree + */ +fn enter_scope(&mut self, _ctx: &ScopeContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#scope}. + * @param ctx the parse tree + */ +fn exit_scope(&mut self, _ctx: &ScopeContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#commands}. + * @param ctx the parse tree + */ +fn enter_commands(&mut self, _ctx: &CommandsContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#commands}. + * @param ctx the parse tree + */ +fn exit_commands(&mut self, _ctx: &CommandsContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#icon}. + * @param ctx the parse tree + */ +fn enter_icon(&mut self, _ctx: &IconContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#icon}. + * @param ctx the parse tree + */ +fn exit_icon(&mut self, _ctx: &IconContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#command}. + * @param ctx the parse tree + */ +fn enter_command(&mut self, _ctx: &CommandContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#command}. + * @param ctx the parse tree + */ +fn exit_command(&mut self, _ctx: &CommandContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#arc}. + * @param ctx the parse tree + */ +fn enter_arc(&mut self, _ctx: &ArcContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#arc}. + * @param ctx the parse tree + */ +fn exit_arc(&mut self, _ctx: &ArcContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#circle}. + * @param ctx the parse tree + */ +fn enter_circle(&mut self, _ctx: &CircleContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#circle}. + * @param ctx the parse tree + */ +fn exit_circle(&mut self, _ctx: &CircleContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#line}. + * @param ctx the parse tree + */ +fn enter_line(&mut self, _ctx: &LineContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#line}. + * @param ctx the parse tree + */ +fn exit_line(&mut self, _ctx: &LineContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#rectangle}. + * @param ctx the parse tree + */ +fn enter_rectangle(&mut self, _ctx: &RectangleContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#rectangle}. + * @param ctx the parse tree + */ +fn exit_rectangle(&mut self, _ctx: &RectangleContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#name}. + * @param ctx the parse tree + */ +fn enter_name(&mut self, _ctx: &NameContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#name}. + * @param ctx the parse tree + */ +fn exit_name(&mut self, _ctx: &NameContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#setPosition}. + * @param ctx the parse tree + */ +fn enter_setPosition(&mut self, _ctx: &SetPositionContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#setPosition}. + * @param ctx the parse tree + */ +fn exit_setPosition(&mut self, _ctx: &SetPositionContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#setWidth}. + * @param ctx the parse tree + */ +fn enter_setWidth(&mut self, _ctx: &SetWidthContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#setWidth}. + * @param ctx the parse tree + */ +fn exit_setWidth(&mut self, _ctx: &SetWidthContext<'input>) { } +/** + * Enter a parse tree produced by {@link IconScriptParser#setRemove}. + * @param ctx the parse tree + */ +fn enter_setRemove(&mut self, _ctx: &SetRemoveContext<'input>) { } +/** + * Exit a parse tree produced by {@link IconScriptParser#setRemove}. + * @param ctx the parse tree + */ +fn exit_setRemove(&mut self, _ctx: &SetRemoveContext<'input>) { } + +} + +antlr_rust::coerce_from!{ 'input : IconScriptListener<'input> } + + diff --git a/rust/src/parser/iconscriptparser.rs b/rust/src/parser/iconscriptparser.rs new file mode 100644 index 0000000..1959131 --- /dev/null +++ b/rust/src/parser/iconscriptparser.rs @@ -0,0 +1,2265 @@ +// Generated from ../grammar/IconScript.g4 by ANTLR 4.8 +#![allow(dead_code)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(nonstandard_style)] +#![allow(unused_imports)] +#![allow(unused_mut)] +#![allow(unused_braces)] +use antlr_rust::PredictionContextCache; +use antlr_rust::parser::{Parser, BaseParser, ParserRecog, ParserNodeType}; +use antlr_rust::token_stream::TokenStream; +use antlr_rust::TokenSource; +use antlr_rust::parser_atn_simulator::ParserATNSimulator; +use antlr_rust::errors::*; +use antlr_rust::rule_context::{BaseRuleContext, CustomRuleContext, RuleContext}; +use antlr_rust::recognizer::{Recognizer,Actions}; +use antlr_rust::atn_deserializer::ATNDeserializer; +use antlr_rust::dfa::DFA; +use antlr_rust::atn::{ATN, INVALID_ALT}; +use antlr_rust::error_strategy::{ErrorStrategy, DefaultErrorStrategy}; +use antlr_rust::parser_rule_context::{BaseParserRuleContext, ParserRuleContext,cast,cast_mut}; +use antlr_rust::tree::*; +use antlr_rust::token::{TOKEN_EOF,OwningToken,Token}; +use antlr_rust::int_stream::EOF; +use antlr_rust::vocabulary::{Vocabulary,VocabularyImpl}; +use antlr_rust::token_factory::{CommonTokenFactory,TokenFactory, TokenAware}; +use super::iconscriptlistener::*; +use super::iconscriptvisitor::*; + +use antlr_rust::lazy_static; +use antlr_rust::{TidAble,TidExt}; + +use std::marker::PhantomData; +use std::sync::Arc; +use std::rc::Rc; +use std::convert::TryFrom; +use std::cell::RefCell; +use std::ops::{DerefMut, Deref}; +use std::borrow::{Borrow,BorrowMut}; +use std::any::{Any,TypeId}; + + pub const T__0:isize=1; + pub const T__1:isize=2; + pub const T__2:isize=3; + pub const T__3:isize=4; + pub const T__4:isize=5; + pub const T__5:isize=6; + pub const T__6:isize=7; + pub const T__7:isize=8; + pub const T__8:isize=9; + pub const T__9:isize=10; + pub const T__10:isize=11; + pub const T__11:isize=12; + pub const T__12:isize=13; + pub const T__13:isize=14; + pub const VARIABLE:isize=15; + pub const FLOAT:isize=16; + pub const IDENTIFIER:isize=17; + pub const COMMENT:isize=18; + pub const WS:isize=19; + pub const RULE_script:usize = 0; + pub const RULE_position:usize = 1; + pub const RULE_expression:usize = 2; + pub const RULE_assignment:usize = 3; + pub const RULE_scope:usize = 4; + pub const RULE_commands:usize = 5; + pub const RULE_icon:usize = 6; + pub const RULE_command:usize = 7; + pub const RULE_arc:usize = 8; + pub const RULE_circle:usize = 9; + pub const RULE_line:usize = 10; + pub const RULE_rectangle:usize = 11; + pub const RULE_name:usize = 12; + pub const RULE_setPosition:usize = 13; + pub const RULE_setWidth:usize = 14; + pub const RULE_setRemove:usize = 15; + pub const ruleNames: [&'static str; 16] = [ + "script", "position", "expression", "assignment", "scope", "commands", + "icon", "command", "arc", "circle", "line", "rectangle", "name", "setPosition", + "setWidth", "setRemove" + ]; + + + pub const _LITERAL_NAMES: [Option<&'static str>;15] = [ + None, Some("'+'"), Some("','"), Some("'='"), Some("'{'"), Some("'}'"), + Some("'icon'"), Some("'a'"), Some("'e'"), Some("'l'"), Some("'lf'"), Some("'r'"), + Some("'m'"), Some("'w'"), Some("'subtract'") + ]; + pub const _SYMBOLIC_NAMES: [Option<&'static str>;20] = [ + None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, Some("VARIABLE"), Some("FLOAT"), Some("IDENTIFIER"), + Some("COMMENT"), Some("WS") + ]; + lazy_static!{ + static ref _shared_context_cache: Arc = Arc::new(PredictionContextCache::new()); + static ref VOCABULARY: Box = Box::new(VocabularyImpl::new(_LITERAL_NAMES.iter(), _SYMBOLIC_NAMES.iter(), None)); + } + + +type BaseParserType<'input, I> = + BaseParser<'input,IconScriptParserExt<'input>, I, IconScriptParserContextType , dyn IconScriptListener<'input> + 'input >; + +type TokenType<'input> = as TokenFactory<'input>>::Tok; +pub type LocalTokenFactory<'input> = CommonTokenFactory; + +pub type IconScriptTreeWalker<'input,'a> = + ParseTreeWalker<'input, 'a, IconScriptParserContextType , dyn IconScriptListener<'input> + 'a>; + +/// Parser for IconScript grammar +pub struct IconScriptParser<'input,I,H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + base:BaseParserType<'input,I>, + interpreter:Arc, + _shared_context_cache: Box, + pub err_handler: H, +} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn get_serialized_atn() -> &'static str { _serializedATN } + + pub fn set_error_strategy(&mut self, strategy: H) { + self.err_handler = strategy + } + + pub fn with_strategy(input: I, strategy: H) -> Self { + antlr_rust::recognizer::check_version("0","3"); + let interpreter = Arc::new(ParserATNSimulator::new( + _ATN.clone(), + _decision_to_DFA.clone(), + _shared_context_cache.clone(), + )); + Self { + base: BaseParser::new_base_parser( + input, + Arc::clone(&interpreter), + IconScriptParserExt{ + _pd: Default::default(), + } + ), + interpreter, + _shared_context_cache: Box::new(PredictionContextCache::new()), + err_handler: strategy, + } + } + +} + +type DynStrategy<'input,I> = Box> + 'input>; + +impl<'input, I> IconScriptParser<'input, I, DynStrategy<'input,I>> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, +{ + pub fn with_dyn_strategy(input: I) -> Self{ + Self::with_strategy(input,Box::new(DefaultErrorStrategy::new())) + } +} + +impl<'input, I> IconScriptParser<'input, I, DefaultErrorStrategy<'input,IconScriptParserContextType>> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, +{ + pub fn new(input: I) -> Self{ + Self::with_strategy(input,DefaultErrorStrategy::new()) + } +} + +/// Trait for monomorphized trait object that corresponds to the nodes of parse tree generated for IconScriptParser +pub trait IconScriptParserContext<'input>: + for<'x> Listenable + 'x > + + for<'x> Visitable + 'x > + + ParserRuleContext<'input, TF=LocalTokenFactory<'input>, Ctx=IconScriptParserContextType> +{} + +antlr_rust::coerce_from!{ 'input : IconScriptParserContext<'input> } + +impl<'input, 'x, T> VisitableDyn for dyn IconScriptParserContext<'input> + 'input +where + T: IconScriptVisitor<'input> + 'x, +{ + fn accept_dyn(&self, visitor: &mut T) { + self.accept(visitor as &mut (dyn IconScriptVisitor<'input> + 'x)) + } +} + +impl<'input> IconScriptParserContext<'input> for TerminalNode<'input,IconScriptParserContextType> {} +impl<'input> IconScriptParserContext<'input> for ErrorNode<'input,IconScriptParserContextType> {} + +antlr_rust::tid! { impl<'input> TidAble<'input> for dyn IconScriptParserContext<'input> + 'input } + +antlr_rust::tid! { impl<'input> TidAble<'input> for dyn IconScriptListener<'input> + 'input } + +pub struct IconScriptParserContextType; +antlr_rust::tid!{IconScriptParserContextType} + +impl<'input> ParserNodeType<'input> for IconScriptParserContextType{ + type TF = LocalTokenFactory<'input>; + type Type = dyn IconScriptParserContext<'input> + 'input; +} + +impl<'input, I, H> Deref for IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + type Target = BaseParserType<'input,I>; + + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl<'input, I, H> DerefMut for IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.base + } +} + +pub struct IconScriptParserExt<'input>{ + _pd: PhantomData<&'input str>, +} + +impl<'input> IconScriptParserExt<'input>{ +} +antlr_rust::tid! { IconScriptParserExt<'a> } + +impl<'input> TokenAware<'input> for IconScriptParserExt<'input>{ + type TF = LocalTokenFactory<'input>; +} + +impl<'input,I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>> ParserRecog<'input, BaseParserType<'input,I>> for IconScriptParserExt<'input>{} + +impl<'input,I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>> Actions<'input, BaseParserType<'input,I>> for IconScriptParserExt<'input>{ + fn get_grammar_file_name(&self) -> & str{ "IconScript.g4"} + + fn get_rule_names(&self) -> &[& str] {&ruleNames} + + fn get_vocabulary(&self) -> &dyn Vocabulary { &**VOCABULARY } +} +//------------------- script ---------------- +pub type ScriptContextAll<'input> = ScriptContext<'input>; + + +pub type ScriptContext<'input> = BaseParserRuleContext<'input,ScriptContextExt<'input>>; + +#[derive(Clone)] +pub struct ScriptContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for ScriptContext<'input>{} + +impl<'input,'a> Listenable + 'a> for ScriptContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_script(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_script(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for ScriptContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_script(self); + } +} + +impl<'input> CustomRuleContext<'input> for ScriptContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_script } + //fn type_rule_index() -> usize where Self: Sized { RULE_script } +} +antlr_rust::tid!{ScriptContextExt<'a>} + +impl<'input> ScriptContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,ScriptContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait ScriptContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +fn expression_all(&self) -> Vec>> where Self:Sized{ + self.children_of_type() +} +fn expression(&self, i: usize) -> Option>> where Self:Sized{ + self.child_of_type(i) +} + +} + +impl<'input> ScriptContextAttrs<'input> for ScriptContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn script(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = ScriptContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 0, RULE_script); + let mut _localctx: Rc = _localctx; + let mut _la: isize = -1; + let result: Result<(), ANTLRError> = (|| { + + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(35); + recog.err_handler.sync(&mut recog.base)?; + _la = recog.base.input.la(1); + while _la==T__5 || _la==IDENTIFIER { + { + { + /*InvokeRule expression*/ + recog.base.set_state(32); + recog.expression()?; + + } + } + recog.base.set_state(37); + recog.err_handler.sync(&mut recog.base)?; + _la = recog.base.input.la(1); + } + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- position ---------------- +pub type PositionContextAll<'input> = PositionContext<'input>; + + +pub type PositionContext<'input> = BaseParserRuleContext<'input,PositionContextExt<'input>>; + +#[derive(Clone)] +pub struct PositionContextExt<'input>{ + pub relative: Option>, + pub x: Option>, + pub y: Option>, +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for PositionContext<'input>{} + +impl<'input,'a> Listenable + 'a> for PositionContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_position(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_position(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for PositionContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_position(self); + } +} + +impl<'input> CustomRuleContext<'input> for PositionContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_position } + //fn type_rule_index() -> usize where Self: Sized { RULE_position } +} +antlr_rust::tid!{PositionContextExt<'a>} + +impl<'input> PositionContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,PositionContextExt{ + relative: None, x: None, y: None, + ph:PhantomData + }), + ) + } +} + +pub trait PositionContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +/// Retrieves all `TerminalNode`s corresponding to token FLOAT in current rule +fn FLOAT_all(&self) -> Vec>> where Self:Sized{ + self.children_of_type() +} +/// Retrieves 'i's TerminalNode corresponding to token FLOAT, starting from 0. +/// Returns `None` if number of children corresponding to token FLOAT is less or equal than `i`. +fn FLOAT(&self, i: usize) -> Option>> where Self:Sized{ + self.get_token(FLOAT, i) +} + +} + +impl<'input> PositionContextAttrs<'input> for PositionContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn position(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = PositionContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 2, RULE_position); + let mut _localctx: Rc = _localctx; + let mut _la: isize = -1; + let result: Result<(), ANTLRError> = (|| { + + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(39); + recog.err_handler.sync(&mut recog.base)?; + _la = recog.base.input.la(1); + if _la==T__0 { + { + recog.base.set_state(38); + let tmp = recog.base.match_token(T__0,&mut recog.err_handler)?; + cast_mut::<_,PositionContext >(&mut _localctx).relative = Some(tmp.clone()); + + + } + } + + recog.base.set_state(41); + let tmp = recog.base.match_token(FLOAT,&mut recog.err_handler)?; + cast_mut::<_,PositionContext >(&mut _localctx).x = Some(tmp.clone()); + + + recog.base.set_state(42); + recog.base.match_token(T__1,&mut recog.err_handler)?; + + recog.base.set_state(43); + let tmp = recog.base.match_token(FLOAT,&mut recog.err_handler)?; + cast_mut::<_,PositionContext >(&mut _localctx).y = Some(tmp.clone()); + + + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- expression ---------------- +pub type ExpressionContextAll<'input> = ExpressionContext<'input>; + + +pub type ExpressionContext<'input> = BaseParserRuleContext<'input,ExpressionContextExt<'input>>; + +#[derive(Clone)] +pub struct ExpressionContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for ExpressionContext<'input>{} + +impl<'input,'a> Listenable + 'a> for ExpressionContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_expression(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_expression(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for ExpressionContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_expression(self); + } +} + +impl<'input> CustomRuleContext<'input> for ExpressionContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_expression } + //fn type_rule_index() -> usize where Self: Sized { RULE_expression } +} +antlr_rust::tid!{ExpressionContextExt<'a>} + +impl<'input> ExpressionContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,ExpressionContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait ExpressionContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +fn assignment(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} +fn icon(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} + +} + +impl<'input> ExpressionContextAttrs<'input> for ExpressionContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn expression(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = ExpressionContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 4, RULE_expression); + let mut _localctx: Rc = _localctx; + let result: Result<(), ANTLRError> = (|| { + + recog.base.set_state(47); + recog.err_handler.sync(&mut recog.base)?; + match recog.base.input.la(1) { + IDENTIFIER + => { + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + /*InvokeRule assignment*/ + recog.base.set_state(45); + recog.assignment()?; + + } + } + + T__5 + => { + //recog.base.enter_outer_alt(_localctx.clone(), 2); + recog.base.enter_outer_alt(None, 2); + { + /*InvokeRule icon*/ + recog.base.set_state(46); + recog.icon()?; + + } + } + + _ => Err(ANTLRError::NoAltError(NoViableAltError::new(&mut recog.base)))? + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- assignment ---------------- +pub type AssignmentContextAll<'input> = AssignmentContext<'input>; + + +pub type AssignmentContext<'input> = BaseParserRuleContext<'input,AssignmentContextExt<'input>>; + +#[derive(Clone)] +pub struct AssignmentContextExt<'input>{ + pub left: Option>, + pub right: Option>>, +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for AssignmentContext<'input>{} + +impl<'input,'a> Listenable + 'a> for AssignmentContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_assignment(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_assignment(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for AssignmentContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_assignment(self); + } +} + +impl<'input> CustomRuleContext<'input> for AssignmentContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_assignment } + //fn type_rule_index() -> usize where Self: Sized { RULE_assignment } +} +antlr_rust::tid!{AssignmentContextExt<'a>} + +impl<'input> AssignmentContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,AssignmentContextExt{ + left: None, + right: None, + ph:PhantomData + }), + ) + } +} + +pub trait AssignmentContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +/// Retrieves first TerminalNode corresponding to token IDENTIFIER +/// Returns `None` if there is no child corresponding to token IDENTIFIER +fn IDENTIFIER(&self) -> Option>> where Self:Sized{ + self.get_token(IDENTIFIER, 0) +} +fn commands(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} + +} + +impl<'input> AssignmentContextAttrs<'input> for AssignmentContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn assignment(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = AssignmentContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 6, RULE_assignment); + let mut _localctx: Rc = _localctx; + let result: Result<(), ANTLRError> = (|| { + + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(49); + let tmp = recog.base.match_token(IDENTIFIER,&mut recog.err_handler)?; + cast_mut::<_,AssignmentContext >(&mut _localctx).left = Some(tmp.clone()); + + + recog.base.set_state(50); + recog.base.match_token(T__2,&mut recog.err_handler)?; + + /*InvokeRule commands*/ + recog.base.set_state(51); + let tmp = recog.commands()?; + cast_mut::<_,AssignmentContext >(&mut _localctx).right = Some(tmp.clone()); + + + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- scope ---------------- +pub type ScopeContextAll<'input> = ScopeContext<'input>; + + +pub type ScopeContext<'input> = BaseParserRuleContext<'input,ScopeContextExt<'input>>; + +#[derive(Clone)] +pub struct ScopeContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for ScopeContext<'input>{} + +impl<'input,'a> Listenable + 'a> for ScopeContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_scope(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_scope(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for ScopeContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_scope(self); + } +} + +impl<'input> CustomRuleContext<'input> for ScopeContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_scope } + //fn type_rule_index() -> usize where Self: Sized { RULE_scope } +} +antlr_rust::tid!{ScopeContextExt<'a>} + +impl<'input> ScopeContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,ScopeContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait ScopeContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +fn commands(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} + +} + +impl<'input> ScopeContextAttrs<'input> for ScopeContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn scope(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = ScopeContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 8, RULE_scope); + let mut _localctx: Rc = _localctx; + let result: Result<(), ANTLRError> = (|| { + + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(53); + recog.base.match_token(T__3,&mut recog.err_handler)?; + + /*InvokeRule commands*/ + recog.base.set_state(54); + recog.commands()?; + + recog.base.set_state(55); + recog.base.match_token(T__4,&mut recog.err_handler)?; + + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- commands ---------------- +pub type CommandsContextAll<'input> = CommandsContext<'input>; + + +pub type CommandsContext<'input> = BaseParserRuleContext<'input,CommandsContextExt<'input>>; + +#[derive(Clone)] +pub struct CommandsContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for CommandsContext<'input>{} + +impl<'input,'a> Listenable + 'a> for CommandsContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_commands(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_commands(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for CommandsContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_commands(self); + } +} + +impl<'input> CustomRuleContext<'input> for CommandsContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_commands } + //fn type_rule_index() -> usize where Self: Sized { RULE_commands } +} +antlr_rust::tid!{CommandsContextExt<'a>} + +impl<'input> CommandsContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,CommandsContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait CommandsContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +fn command_all(&self) -> Vec>> where Self:Sized{ + self.children_of_type() +} +fn command(&self, i: usize) -> Option>> where Self:Sized{ + self.child_of_type(i) +} +fn scope_all(&self) -> Vec>> where Self:Sized{ + self.children_of_type() +} +fn scope(&self, i: usize) -> Option>> where Self:Sized{ + self.child_of_type(i) +} + +} + +impl<'input> CommandsContextAttrs<'input> for CommandsContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn commands(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = CommandsContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 10, RULE_commands); + let mut _localctx: Rc = _localctx; + let result: Result<(), ANTLRError> = (|| { + + let mut _alt: isize; + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(59); + recog.err_handler.sync(&mut recog.base)?; + _alt = 1; + loop { + match _alt { + x if x == 1=> + { + recog.base.set_state(59); + recog.err_handler.sync(&mut recog.base)?; + match recog.base.input.la(1) { + T__6 | T__7 | T__8 | T__9 | T__10 | T__11 | T__12 | T__13 | VARIABLE | + IDENTIFIER + => { + { + /*InvokeRule command*/ + recog.base.set_state(57); + recog.command()?; + + } + } + + T__3 + => { + { + /*InvokeRule scope*/ + recog.base.set_state(58); + recog.scope()?; + + } + } + + _ => Err(ANTLRError::NoAltError(NoViableAltError::new(&mut recog.base)))? + } + } + + _ => Err(ANTLRError::NoAltError(NoViableAltError::new(&mut recog.base)))? + } + recog.base.set_state(61); + recog.err_handler.sync(&mut recog.base)?; + _alt = recog.interpreter.adaptive_predict(4,&mut recog.base)?; + if _alt==2 || _alt==INVALID_ALT { break } + } + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- icon ---------------- +pub type IconContextAll<'input> = IconContext<'input>; + + +pub type IconContext<'input> = BaseParserRuleContext<'input,IconContextExt<'input>>; + +#[derive(Clone)] +pub struct IconContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for IconContext<'input>{} + +impl<'input,'a> Listenable + 'a> for IconContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_icon(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_icon(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for IconContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_icon(self); + } +} + +impl<'input> CustomRuleContext<'input> for IconContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_icon } + //fn type_rule_index() -> usize where Self: Sized { RULE_icon } +} +antlr_rust::tid!{IconContextExt<'a>} + +impl<'input> IconContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,IconContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait IconContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +fn name(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} +fn commands(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} +fn command(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} + +} + +impl<'input> IconContextAttrs<'input> for IconContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn icon(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = IconContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 12, RULE_icon); + let mut _localctx: Rc = _localctx; + let result: Result<(), ANTLRError> = (|| { + + recog.base.set_state(75); + recog.err_handler.sync(&mut recog.base)?; + match recog.interpreter.adaptive_predict(5,&mut recog.base)? { + 1 =>{ + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(63); + recog.base.match_token(T__5,&mut recog.err_handler)?; + + /*InvokeRule name*/ + recog.base.set_state(64); + recog.name()?; + + recog.base.set_state(65); + recog.base.match_token(T__2,&mut recog.err_handler)?; + + recog.base.set_state(66); + recog.base.match_token(T__3,&mut recog.err_handler)?; + + /*InvokeRule commands*/ + recog.base.set_state(67); + recog.commands()?; + + recog.base.set_state(68); + recog.base.match_token(T__4,&mut recog.err_handler)?; + + } + } + , + 2 =>{ + //recog.base.enter_outer_alt(_localctx.clone(), 2); + recog.base.enter_outer_alt(None, 2); + { + recog.base.set_state(70); + recog.base.match_token(T__5,&mut recog.err_handler)?; + + /*InvokeRule name*/ + recog.base.set_state(71); + recog.name()?; + + recog.base.set_state(72); + recog.base.match_token(T__2,&mut recog.err_handler)?; + + /*InvokeRule command*/ + recog.base.set_state(73); + recog.command()?; + + } + } + + _ => {} + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- command ---------------- +pub type CommandContextAll<'input> = CommandContext<'input>; + + +pub type CommandContext<'input> = BaseParserRuleContext<'input,CommandContextExt<'input>>; + +#[derive(Clone)] +pub struct CommandContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for CommandContext<'input>{} + +impl<'input,'a> Listenable + 'a> for CommandContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_command(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_command(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for CommandContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_command(self); + } +} + +impl<'input> CustomRuleContext<'input> for CommandContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_command } + //fn type_rule_index() -> usize where Self: Sized { RULE_command } +} +antlr_rust::tid!{CommandContextExt<'a>} + +impl<'input> CommandContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,CommandContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait CommandContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +fn name(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} +/// Retrieves first TerminalNode corresponding to token VARIABLE +/// Returns `None` if there is no child corresponding to token VARIABLE +fn VARIABLE(&self) -> Option>> where Self:Sized{ + self.get_token(VARIABLE, 0) +} +fn arc(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} +fn circle(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} +fn line(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} +fn rectangle(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} +fn setPosition(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} +fn setRemove(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} +fn setWidth(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} + +} + +impl<'input> CommandContextAttrs<'input> for CommandContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn command(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = CommandContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 14, RULE_command); + let mut _localctx: Rc = _localctx; + let result: Result<(), ANTLRError> = (|| { + + recog.base.set_state(86); + recog.err_handler.sync(&mut recog.base)?; + match recog.base.input.la(1) { + IDENTIFIER + => { + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + /*InvokeRule name*/ + recog.base.set_state(77); + recog.name()?; + + } + } + + VARIABLE + => { + //recog.base.enter_outer_alt(_localctx.clone(), 2); + recog.base.enter_outer_alt(None, 2); + { + recog.base.set_state(78); + recog.base.match_token(VARIABLE,&mut recog.err_handler)?; + + } + } + + T__6 + => { + //recog.base.enter_outer_alt(_localctx.clone(), 3); + recog.base.enter_outer_alt(None, 3); + { + /*InvokeRule arc*/ + recog.base.set_state(79); + recog.arc()?; + + } + } + + T__7 + => { + //recog.base.enter_outer_alt(_localctx.clone(), 4); + recog.base.enter_outer_alt(None, 4); + { + /*InvokeRule circle*/ + recog.base.set_state(80); + recog.circle()?; + + } + } + + T__8 | T__9 + => { + //recog.base.enter_outer_alt(_localctx.clone(), 5); + recog.base.enter_outer_alt(None, 5); + { + /*InvokeRule line*/ + recog.base.set_state(81); + recog.line()?; + + } + } + + T__10 + => { + //recog.base.enter_outer_alt(_localctx.clone(), 6); + recog.base.enter_outer_alt(None, 6); + { + /*InvokeRule rectangle*/ + recog.base.set_state(82); + recog.rectangle()?; + + } + } + + T__11 + => { + //recog.base.enter_outer_alt(_localctx.clone(), 7); + recog.base.enter_outer_alt(None, 7); + { + /*InvokeRule setPosition*/ + recog.base.set_state(83); + recog.setPosition()?; + + } + } + + T__13 + => { + //recog.base.enter_outer_alt(_localctx.clone(), 8); + recog.base.enter_outer_alt(None, 8); + { + /*InvokeRule setRemove*/ + recog.base.set_state(84); + recog.setRemove()?; + + } + } + + T__12 + => { + //recog.base.enter_outer_alt(_localctx.clone(), 9); + recog.base.enter_outer_alt(None, 9); + { + /*InvokeRule setWidth*/ + recog.base.set_state(85); + recog.setWidth()?; + + } + } + + _ => Err(ANTLRError::NoAltError(NoViableAltError::new(&mut recog.base)))? + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- arc ---------------- +pub type ArcContextAll<'input> = ArcContext<'input>; + + +pub type ArcContext<'input> = BaseParserRuleContext<'input,ArcContextExt<'input>>; + +#[derive(Clone)] +pub struct ArcContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for ArcContext<'input>{} + +impl<'input,'a> Listenable + 'a> for ArcContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_arc(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_arc(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for ArcContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_arc(self); + } +} + +impl<'input> CustomRuleContext<'input> for ArcContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_arc } + //fn type_rule_index() -> usize where Self: Sized { RULE_arc } +} +antlr_rust::tid!{ArcContextExt<'a>} + +impl<'input> ArcContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,ArcContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait ArcContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +fn position(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} +/// Retrieves all `TerminalNode`s corresponding to token FLOAT in current rule +fn FLOAT_all(&self) -> Vec>> where Self:Sized{ + self.children_of_type() +} +/// Retrieves 'i's TerminalNode corresponding to token FLOAT, starting from 0. +/// Returns `None` if number of children corresponding to token FLOAT is less or equal than `i`. +fn FLOAT(&self, i: usize) -> Option>> where Self:Sized{ + self.get_token(FLOAT, i) +} + +} + +impl<'input> ArcContextAttrs<'input> for ArcContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn arc(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = ArcContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 16, RULE_arc); + let mut _localctx: Rc = _localctx; + let result: Result<(), ANTLRError> = (|| { + + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(88); + recog.base.match_token(T__6,&mut recog.err_handler)?; + + /*InvokeRule position*/ + recog.base.set_state(89); + recog.position()?; + + recog.base.set_state(90); + recog.base.match_token(FLOAT,&mut recog.err_handler)?; + + recog.base.set_state(91); + recog.base.match_token(FLOAT,&mut recog.err_handler)?; + + recog.base.set_state(92); + recog.base.match_token(FLOAT,&mut recog.err_handler)?; + + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- circle ---------------- +pub type CircleContextAll<'input> = CircleContext<'input>; + + +pub type CircleContext<'input> = BaseParserRuleContext<'input,CircleContextExt<'input>>; + +#[derive(Clone)] +pub struct CircleContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for CircleContext<'input>{} + +impl<'input,'a> Listenable + 'a> for CircleContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_circle(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_circle(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for CircleContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_circle(self); + } +} + +impl<'input> CustomRuleContext<'input> for CircleContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_circle } + //fn type_rule_index() -> usize where Self: Sized { RULE_circle } +} +antlr_rust::tid!{CircleContextExt<'a>} + +impl<'input> CircleContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,CircleContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait CircleContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +fn position(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} +/// Retrieves first TerminalNode corresponding to token FLOAT +/// Returns `None` if there is no child corresponding to token FLOAT +fn FLOAT(&self) -> Option>> where Self:Sized{ + self.get_token(FLOAT, 0) +} + +} + +impl<'input> CircleContextAttrs<'input> for CircleContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn circle(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = CircleContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 18, RULE_circle); + let mut _localctx: Rc = _localctx; + let result: Result<(), ANTLRError> = (|| { + + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(94); + recog.base.match_token(T__7,&mut recog.err_handler)?; + + /*InvokeRule position*/ + recog.base.set_state(95); + recog.position()?; + + recog.base.set_state(96); + recog.base.match_token(FLOAT,&mut recog.err_handler)?; + + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- line ---------------- +pub type LineContextAll<'input> = LineContext<'input>; + + +pub type LineContext<'input> = BaseParserRuleContext<'input,LineContextExt<'input>>; + +#[derive(Clone)] +pub struct LineContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for LineContext<'input>{} + +impl<'input,'a> Listenable + 'a> for LineContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_line(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_line(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for LineContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_line(self); + } +} + +impl<'input> CustomRuleContext<'input> for LineContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_line } + //fn type_rule_index() -> usize where Self: Sized { RULE_line } +} +antlr_rust::tid!{LineContextExt<'a>} + +impl<'input> LineContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,LineContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait LineContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +fn position_all(&self) -> Vec>> where Self:Sized{ + self.children_of_type() +} +fn position(&self, i: usize) -> Option>> where Self:Sized{ + self.child_of_type(i) +} + +} + +impl<'input> LineContextAttrs<'input> for LineContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn line(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = LineContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 20, RULE_line); + let mut _localctx: Rc = _localctx; + let mut _la: isize = -1; + let result: Result<(), ANTLRError> = (|| { + + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(98); + _la = recog.base.input.la(1); + if { !(_la==T__8 || _la==T__9) } { + recog.err_handler.recover_inline(&mut recog.base)?; + + } + else { + if recog.base.input.la(1)==TOKEN_EOF { recog.base.matched_eof = true }; + recog.err_handler.report_match(&mut recog.base); + recog.base.consume(&mut recog.err_handler); + } + recog.base.set_state(100); + recog.err_handler.sync(&mut recog.base)?; + _la = recog.base.input.la(1); + loop { + { + { + /*InvokeRule position*/ + recog.base.set_state(99); + recog.position()?; + + } + } + recog.base.set_state(102); + recog.err_handler.sync(&mut recog.base)?; + _la = recog.base.input.la(1); + if !(_la==T__0 || _la==FLOAT) {break} + } + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- rectangle ---------------- +pub type RectangleContextAll<'input> = RectangleContext<'input>; + + +pub type RectangleContext<'input> = BaseParserRuleContext<'input,RectangleContextExt<'input>>; + +#[derive(Clone)] +pub struct RectangleContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for RectangleContext<'input>{} + +impl<'input,'a> Listenable + 'a> for RectangleContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_rectangle(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_rectangle(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for RectangleContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_rectangle(self); + } +} + +impl<'input> CustomRuleContext<'input> for RectangleContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_rectangle } + //fn type_rule_index() -> usize where Self: Sized { RULE_rectangle } +} +antlr_rust::tid!{RectangleContextExt<'a>} + +impl<'input> RectangleContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,RectangleContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait RectangleContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +fn position_all(&self) -> Vec>> where Self:Sized{ + self.children_of_type() +} +fn position(&self, i: usize) -> Option>> where Self:Sized{ + self.child_of_type(i) +} + +} + +impl<'input> RectangleContextAttrs<'input> for RectangleContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn rectangle(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = RectangleContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 22, RULE_rectangle); + let mut _localctx: Rc = _localctx; + let result: Result<(), ANTLRError> = (|| { + + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(104); + recog.base.match_token(T__10,&mut recog.err_handler)?; + + /*InvokeRule position*/ + recog.base.set_state(105); + recog.position()?; + + /*InvokeRule position*/ + recog.base.set_state(106); + recog.position()?; + + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- name ---------------- +pub type NameContextAll<'input> = NameContext<'input>; + + +pub type NameContext<'input> = BaseParserRuleContext<'input,NameContextExt<'input>>; + +#[derive(Clone)] +pub struct NameContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for NameContext<'input>{} + +impl<'input,'a> Listenable + 'a> for NameContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_name(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_name(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for NameContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_name(self); + } +} + +impl<'input> CustomRuleContext<'input> for NameContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_name } + //fn type_rule_index() -> usize where Self: Sized { RULE_name } +} +antlr_rust::tid!{NameContextExt<'a>} + +impl<'input> NameContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,NameContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait NameContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +/// Retrieves first TerminalNode corresponding to token IDENTIFIER +/// Returns `None` if there is no child corresponding to token IDENTIFIER +fn IDENTIFIER(&self) -> Option>> where Self:Sized{ + self.get_token(IDENTIFIER, 0) +} + +} + +impl<'input> NameContextAttrs<'input> for NameContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn name(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = NameContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 24, RULE_name); + let mut _localctx: Rc = _localctx; + let result: Result<(), ANTLRError> = (|| { + + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(108); + recog.base.match_token(IDENTIFIER,&mut recog.err_handler)?; + + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- setPosition ---------------- +pub type SetPositionContextAll<'input> = SetPositionContext<'input>; + + +pub type SetPositionContext<'input> = BaseParserRuleContext<'input,SetPositionContextExt<'input>>; + +#[derive(Clone)] +pub struct SetPositionContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for SetPositionContext<'input>{} + +impl<'input,'a> Listenable + 'a> for SetPositionContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_setPosition(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_setPosition(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for SetPositionContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_setPosition(self); + } +} + +impl<'input> CustomRuleContext<'input> for SetPositionContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_setPosition } + //fn type_rule_index() -> usize where Self: Sized { RULE_setPosition } +} +antlr_rust::tid!{SetPositionContextExt<'a>} + +impl<'input> SetPositionContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,SetPositionContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait SetPositionContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +fn position(&self) -> Option>> where Self:Sized{ + self.child_of_type(0) +} + +} + +impl<'input> SetPositionContextAttrs<'input> for SetPositionContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn setPosition(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = SetPositionContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 26, RULE_setPosition); + let mut _localctx: Rc = _localctx; + let result: Result<(), ANTLRError> = (|| { + + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(110); + recog.base.match_token(T__11,&mut recog.err_handler)?; + + /*InvokeRule position*/ + recog.base.set_state(111); + recog.position()?; + + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- setWidth ---------------- +pub type SetWidthContextAll<'input> = SetWidthContext<'input>; + + +pub type SetWidthContext<'input> = BaseParserRuleContext<'input,SetWidthContextExt<'input>>; + +#[derive(Clone)] +pub struct SetWidthContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for SetWidthContext<'input>{} + +impl<'input,'a> Listenable + 'a> for SetWidthContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_setWidth(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_setWidth(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for SetWidthContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_setWidth(self); + } +} + +impl<'input> CustomRuleContext<'input> for SetWidthContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_setWidth } + //fn type_rule_index() -> usize where Self: Sized { RULE_setWidth } +} +antlr_rust::tid!{SetWidthContextExt<'a>} + +impl<'input> SetWidthContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,SetWidthContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait SetWidthContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + +/// Retrieves first TerminalNode corresponding to token FLOAT +/// Returns `None` if there is no child corresponding to token FLOAT +fn FLOAT(&self) -> Option>> where Self:Sized{ + self.get_token(FLOAT, 0) +} + +} + +impl<'input> SetWidthContextAttrs<'input> for SetWidthContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn setWidth(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = SetWidthContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 28, RULE_setWidth); + let mut _localctx: Rc = _localctx; + let result: Result<(), ANTLRError> = (|| { + + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(113); + recog.base.match_token(T__12,&mut recog.err_handler)?; + + recog.base.set_state(114); + recog.base.match_token(FLOAT,&mut recog.err_handler)?; + + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} +//------------------- setRemove ---------------- +pub type SetRemoveContextAll<'input> = SetRemoveContext<'input>; + + +pub type SetRemoveContext<'input> = BaseParserRuleContext<'input,SetRemoveContextExt<'input>>; + +#[derive(Clone)] +pub struct SetRemoveContextExt<'input>{ +ph:PhantomData<&'input str> +} + +impl<'input> IconScriptParserContext<'input> for SetRemoveContext<'input>{} + +impl<'input,'a> Listenable + 'a> for SetRemoveContext<'input>{ + fn enter(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.enter_every_rule(self); + listener.enter_setRemove(self); + } + fn exit(&self,listener: &mut (dyn IconScriptListener<'input> + 'a)) { + listener.exit_setRemove(self); + listener.exit_every_rule(self); + } +} + +impl<'input,'a> Visitable + 'a> for SetRemoveContext<'input>{ + fn accept(&self,visitor: &mut (dyn IconScriptVisitor<'input> + 'a)) { + visitor.visit_setRemove(self); + } +} + +impl<'input> CustomRuleContext<'input> for SetRemoveContextExt<'input>{ + type TF = LocalTokenFactory<'input>; + type Ctx = IconScriptParserContextType; + fn get_rule_index(&self) -> usize { RULE_setRemove } + //fn type_rule_index() -> usize where Self: Sized { RULE_setRemove } +} +antlr_rust::tid!{SetRemoveContextExt<'a>} + +impl<'input> SetRemoveContextExt<'input>{ + fn new(parent: Option + 'input > >, invoking_state: isize) -> Rc> { + Rc::new( + BaseParserRuleContext::new_parser_ctx(parent, invoking_state,SetRemoveContextExt{ + ph:PhantomData + }), + ) + } +} + +pub trait SetRemoveContextAttrs<'input>: IconScriptParserContext<'input> + BorrowMut>{ + + +} + +impl<'input> SetRemoveContextAttrs<'input> for SetRemoveContext<'input>{} + +impl<'input, I, H> IconScriptParser<'input, I, H> +where + I: TokenStream<'input, TF = LocalTokenFactory<'input> > + TidAble<'input>, + H: ErrorStrategy<'input,BaseParserType<'input,I>> +{ + pub fn setRemove(&mut self,) + -> Result>,ANTLRError> { + let mut recog = self; + let _parentctx = recog.ctx.take(); + let mut _localctx = SetRemoveContextExt::new(_parentctx.clone(), recog.base.get_state()); + recog.base.enter_rule(_localctx.clone(), 30, RULE_setRemove); + let mut _localctx: Rc = _localctx; + let result: Result<(), ANTLRError> = (|| { + + //recog.base.enter_outer_alt(_localctx.clone(), 1); + recog.base.enter_outer_alt(None, 1); + { + recog.base.set_state(116); + recog.base.match_token(T__13,&mut recog.err_handler)?; + + } + Ok(()) + })(); + match result { + Ok(_)=>{}, + Err(e @ ANTLRError::FallThrough(_)) => return Err(e), + Err(ref re) => { + //_localctx.exception = re; + recog.err_handler.report_error(&mut recog.base, re); + recog.err_handler.recover(&mut recog.base, re)?; + } + } + recog.base.exit_rule(); + + Ok(_localctx) + } +} + +lazy_static! { + static ref _ATN: Arc = + Arc::new(ATNDeserializer::new(None).deserialize(_serializedATN.chars())); + static ref _decision_to_DFA: Arc>> = { + let mut dfa = Vec::new(); + let size = _ATN.decision_to_state.len(); + for i in 0..size { + dfa.push(DFA::new( + _ATN.clone(), + _ATN.get_decision_state(i), + i as isize, + ).into()) + } + Arc::new(dfa) + }; +} + + + +const _serializedATN:&'static str = + "\x03\u{608b}\u{a72a}\u{8133}\u{b9ed}\u{417c}\u{3be7}\u{7786}\u{5964}\x03\ + \x15\x79\x04\x02\x09\x02\x04\x03\x09\x03\x04\x04\x09\x04\x04\x05\x09\x05\ + \x04\x06\x09\x06\x04\x07\x09\x07\x04\x08\x09\x08\x04\x09\x09\x09\x04\x0a\ + \x09\x0a\x04\x0b\x09\x0b\x04\x0c\x09\x0c\x04\x0d\x09\x0d\x04\x0e\x09\x0e\ + \x04\x0f\x09\x0f\x04\x10\x09\x10\x04\x11\x09\x11\x03\x02\x07\x02\x24\x0a\ + \x02\x0c\x02\x0e\x02\x27\x0b\x02\x03\x03\x05\x03\x2a\x0a\x03\x03\x03\x03\ + \x03\x03\x03\x03\x03\x03\x04\x03\x04\x05\x04\x32\x0a\x04\x03\x05\x03\x05\ + \x03\x05\x03\x05\x03\x06\x03\x06\x03\x06\x03\x06\x03\x07\x03\x07\x06\x07\ + \x3e\x0a\x07\x0d\x07\x0e\x07\x3f\x03\x08\x03\x08\x03\x08\x03\x08\x03\x08\ + \x03\x08\x03\x08\x03\x08\x03\x08\x03\x08\x03\x08\x03\x08\x05\x08\x4e\x0a\ + \x08\x03\x09\x03\x09\x03\x09\x03\x09\x03\x09\x03\x09\x03\x09\x03\x09\x03\ + \x09\x05\x09\x59\x0a\x09\x03\x0a\x03\x0a\x03\x0a\x03\x0a\x03\x0a\x03\x0a\ + \x03\x0b\x03\x0b\x03\x0b\x03\x0b\x03\x0c\x03\x0c\x06\x0c\x67\x0a\x0c\x0d\ + \x0c\x0e\x0c\x68\x03\x0d\x03\x0d\x03\x0d\x03\x0d\x03\x0e\x03\x0e\x03\x0f\ + \x03\x0f\x03\x0f\x03\x10\x03\x10\x03\x10\x03\x11\x03\x11\x03\x11\x02\x02\ + \x12\x02\x04\x06\x08\x0a\x0c\x0e\x10\x12\x14\x16\x18\x1a\x1c\x1e\x20\x02\ + \x03\x03\x02\x0b\x0c\x02\x77\x02\x25\x03\x02\x02\x02\x04\x29\x03\x02\x02\ + \x02\x06\x31\x03\x02\x02\x02\x08\x33\x03\x02\x02\x02\x0a\x37\x03\x02\x02\ + \x02\x0c\x3d\x03\x02\x02\x02\x0e\x4d\x03\x02\x02\x02\x10\x58\x03\x02\x02\ + \x02\x12\x5a\x03\x02\x02\x02\x14\x60\x03\x02\x02\x02\x16\x64\x03\x02\x02\ + \x02\x18\x6a\x03\x02\x02\x02\x1a\x6e\x03\x02\x02\x02\x1c\x70\x03\x02\x02\ + \x02\x1e\x73\x03\x02\x02\x02\x20\x76\x03\x02\x02\x02\x22\x24\x05\x06\x04\ + \x02\x23\x22\x03\x02\x02\x02\x24\x27\x03\x02\x02\x02\x25\x23\x03\x02\x02\ + \x02\x25\x26\x03\x02\x02\x02\x26\x03\x03\x02\x02\x02\x27\x25\x03\x02\x02\ + \x02\x28\x2a\x07\x03\x02\x02\x29\x28\x03\x02\x02\x02\x29\x2a\x03\x02\x02\ + \x02\x2a\x2b\x03\x02\x02\x02\x2b\x2c\x07\x12\x02\x02\x2c\x2d\x07\x04\x02\ + \x02\x2d\x2e\x07\x12\x02\x02\x2e\x05\x03\x02\x02\x02\x2f\x32\x05\x08\x05\ + \x02\x30\x32\x05\x0e\x08\x02\x31\x2f\x03\x02\x02\x02\x31\x30\x03\x02\x02\ + \x02\x32\x07\x03\x02\x02\x02\x33\x34\x07\x13\x02\x02\x34\x35\x07\x05\x02\ + \x02\x35\x36\x05\x0c\x07\x02\x36\x09\x03\x02\x02\x02\x37\x38\x07\x06\x02\ + \x02\x38\x39\x05\x0c\x07\x02\x39\x3a\x07\x07\x02\x02\x3a\x0b\x03\x02\x02\ + \x02\x3b\x3e\x05\x10\x09\x02\x3c\x3e\x05\x0a\x06\x02\x3d\x3b\x03\x02\x02\ + \x02\x3d\x3c\x03\x02\x02\x02\x3e\x3f\x03\x02\x02\x02\x3f\x3d\x03\x02\x02\ + \x02\x3f\x40\x03\x02\x02\x02\x40\x0d\x03\x02\x02\x02\x41\x42\x07\x08\x02\ + \x02\x42\x43\x05\x1a\x0e\x02\x43\x44\x07\x05\x02\x02\x44\x45\x07\x06\x02\ + \x02\x45\x46\x05\x0c\x07\x02\x46\x47\x07\x07\x02\x02\x47\x4e\x03\x02\x02\ + \x02\x48\x49\x07\x08\x02\x02\x49\x4a\x05\x1a\x0e\x02\x4a\x4b\x07\x05\x02\ + \x02\x4b\x4c\x05\x10\x09\x02\x4c\x4e\x03\x02\x02\x02\x4d\x41\x03\x02\x02\ + \x02\x4d\x48\x03\x02\x02\x02\x4e\x0f\x03\x02\x02\x02\x4f\x59\x05\x1a\x0e\ + \x02\x50\x59\x07\x11\x02\x02\x51\x59\x05\x12\x0a\x02\x52\x59\x05\x14\x0b\ + \x02\x53\x59\x05\x16\x0c\x02\x54\x59\x05\x18\x0d\x02\x55\x59\x05\x1c\x0f\ + \x02\x56\x59\x05\x20\x11\x02\x57\x59\x05\x1e\x10\x02\x58\x4f\x03\x02\x02\ + \x02\x58\x50\x03\x02\x02\x02\x58\x51\x03\x02\x02\x02\x58\x52\x03\x02\x02\ + \x02\x58\x53\x03\x02\x02\x02\x58\x54\x03\x02\x02\x02\x58\x55\x03\x02\x02\ + \x02\x58\x56\x03\x02\x02\x02\x58\x57\x03\x02\x02\x02\x59\x11\x03\x02\x02\ + \x02\x5a\x5b\x07\x09\x02\x02\x5b\x5c\x05\x04\x03\x02\x5c\x5d\x07\x12\x02\ + \x02\x5d\x5e\x07\x12\x02\x02\x5e\x5f\x07\x12\x02\x02\x5f\x13\x03\x02\x02\ + \x02\x60\x61\x07\x0a\x02\x02\x61\x62\x05\x04\x03\x02\x62\x63\x07\x12\x02\ + \x02\x63\x15\x03\x02\x02\x02\x64\x66\x09\x02\x02\x02\x65\x67\x05\x04\x03\ + \x02\x66\x65\x03\x02\x02\x02\x67\x68\x03\x02\x02\x02\x68\x66\x03\x02\x02\ + \x02\x68\x69\x03\x02\x02\x02\x69\x17\x03\x02\x02\x02\x6a\x6b\x07\x0d\x02\ + \x02\x6b\x6c\x05\x04\x03\x02\x6c\x6d\x05\x04\x03\x02\x6d\x19\x03\x02\x02\ + \x02\x6e\x6f\x07\x13\x02\x02\x6f\x1b\x03\x02\x02\x02\x70\x71\x07\x0e\x02\ + \x02\x71\x72\x05\x04\x03\x02\x72\x1d\x03\x02\x02\x02\x73\x74\x07\x0f\x02\ + \x02\x74\x75\x07\x12\x02\x02\x75\x1f\x03\x02\x02\x02\x76\x77\x07\x10\x02\ + \x02\x77\x21\x03\x02\x02\x02\x0a\x25\x29\x31\x3d\x3f\x4d\x58\x68"; + diff --git a/rust/src/parser/iconscriptvisitor.rs b/rust/src/parser/iconscriptvisitor.rs new file mode 100644 index 0000000..a82dd0d --- /dev/null +++ b/rust/src/parser/iconscriptvisitor.rs @@ -0,0 +1,324 @@ +#![allow(nonstandard_style)] +// Generated from ../grammar/IconScript.g4 by ANTLR 4.8 +use antlr_rust::tree::{ParseTreeVisitor,ParseTreeVisitorCompat}; +use super::iconscriptparser::*; + +/** + * This interface defines a complete generic visitor for a parse tree produced + * by {@link IconScriptParser}. + */ +pub trait IconScriptVisitor<'input>: ParseTreeVisitor<'input,IconScriptParserContextType>{ + /** + * Visit a parse tree produced by {@link IconScriptParser#script}. + * @param ctx the parse tree + */ + fn visit_script(&mut self, ctx: &ScriptContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#position}. + * @param ctx the parse tree + */ + fn visit_position(&mut self, ctx: &PositionContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#expression}. + * @param ctx the parse tree + */ + fn visit_expression(&mut self, ctx: &ExpressionContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#assignment}. + * @param ctx the parse tree + */ + fn visit_assignment(&mut self, ctx: &AssignmentContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#scope}. + * @param ctx the parse tree + */ + fn visit_scope(&mut self, ctx: &ScopeContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#commands}. + * @param ctx the parse tree + */ + fn visit_commands(&mut self, ctx: &CommandsContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#icon}. + * @param ctx the parse tree + */ + fn visit_icon(&mut self, ctx: &IconContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#command}. + * @param ctx the parse tree + */ + fn visit_command(&mut self, ctx: &CommandContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#arc}. + * @param ctx the parse tree + */ + fn visit_arc(&mut self, ctx: &ArcContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#circle}. + * @param ctx the parse tree + */ + fn visit_circle(&mut self, ctx: &CircleContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#line}. + * @param ctx the parse tree + */ + fn visit_line(&mut self, ctx: &LineContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#rectangle}. + * @param ctx the parse tree + */ + fn visit_rectangle(&mut self, ctx: &RectangleContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#name}. + * @param ctx the parse tree + */ + fn visit_name(&mut self, ctx: &NameContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#setPosition}. + * @param ctx the parse tree + */ + fn visit_setPosition(&mut self, ctx: &SetPositionContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#setWidth}. + * @param ctx the parse tree + */ + fn visit_setWidth(&mut self, ctx: &SetWidthContext<'input>) { self.visit_children(ctx) } + + /** + * Visit a parse tree produced by {@link IconScriptParser#setRemove}. + * @param ctx the parse tree + */ + fn visit_setRemove(&mut self, ctx: &SetRemoveContext<'input>) { self.visit_children(ctx) } + +} + +pub trait IconScriptVisitorCompat<'input>:ParseTreeVisitorCompat<'input, Node= IconScriptParserContextType>{ + /** + * Visit a parse tree produced by {@link IconScriptParser#script}. + * @param ctx the parse tree + */ + fn visit_script(&mut self, ctx: &ScriptContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#position}. + * @param ctx the parse tree + */ + fn visit_position(&mut self, ctx: &PositionContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#expression}. + * @param ctx the parse tree + */ + fn visit_expression(&mut self, ctx: &ExpressionContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#assignment}. + * @param ctx the parse tree + */ + fn visit_assignment(&mut self, ctx: &AssignmentContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#scope}. + * @param ctx the parse tree + */ + fn visit_scope(&mut self, ctx: &ScopeContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#commands}. + * @param ctx the parse tree + */ + fn visit_commands(&mut self, ctx: &CommandsContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#icon}. + * @param ctx the parse tree + */ + fn visit_icon(&mut self, ctx: &IconContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#command}. + * @param ctx the parse tree + */ + fn visit_command(&mut self, ctx: &CommandContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#arc}. + * @param ctx the parse tree + */ + fn visit_arc(&mut self, ctx: &ArcContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#circle}. + * @param ctx the parse tree + */ + fn visit_circle(&mut self, ctx: &CircleContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#line}. + * @param ctx the parse tree + */ + fn visit_line(&mut self, ctx: &LineContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#rectangle}. + * @param ctx the parse tree + */ + fn visit_rectangle(&mut self, ctx: &RectangleContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#name}. + * @param ctx the parse tree + */ + fn visit_name(&mut self, ctx: &NameContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#setPosition}. + * @param ctx the parse tree + */ + fn visit_setPosition(&mut self, ctx: &SetPositionContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#setWidth}. + * @param ctx the parse tree + */ + fn visit_setWidth(&mut self, ctx: &SetWidthContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + + /** + * Visit a parse tree produced by {@link IconScriptParser#setRemove}. + * @param ctx the parse tree + */ + fn visit_setRemove(&mut self, ctx: &SetRemoveContext<'input>) -> Self::Return { + self.visit_children(ctx) + } + +} + +impl<'input,T> IconScriptVisitor<'input> for T +where + T: IconScriptVisitorCompat<'input> +{ + fn visit_script(&mut self, ctx: &ScriptContext<'input>){ + let result = ::visit_script(self, ctx); + *::temp_result(self) = result; + } + + fn visit_position(&mut self, ctx: &PositionContext<'input>){ + let result = ::visit_position(self, ctx); + *::temp_result(self) = result; + } + + fn visit_expression(&mut self, ctx: &ExpressionContext<'input>){ + let result = ::visit_expression(self, ctx); + *::temp_result(self) = result; + } + + fn visit_assignment(&mut self, ctx: &AssignmentContext<'input>){ + let result = ::visit_assignment(self, ctx); + *::temp_result(self) = result; + } + + fn visit_scope(&mut self, ctx: &ScopeContext<'input>){ + let result = ::visit_scope(self, ctx); + *::temp_result(self) = result; + } + + fn visit_commands(&mut self, ctx: &CommandsContext<'input>){ + let result = ::visit_commands(self, ctx); + *::temp_result(self) = result; + } + + fn visit_icon(&mut self, ctx: &IconContext<'input>){ + let result = ::visit_icon(self, ctx); + *::temp_result(self) = result; + } + + fn visit_command(&mut self, ctx: &CommandContext<'input>){ + let result = ::visit_command(self, ctx); + *::temp_result(self) = result; + } + + fn visit_arc(&mut self, ctx: &ArcContext<'input>){ + let result = ::visit_arc(self, ctx); + *::temp_result(self) = result; + } + + fn visit_circle(&mut self, ctx: &CircleContext<'input>){ + let result = ::visit_circle(self, ctx); + *::temp_result(self) = result; + } + + fn visit_line(&mut self, ctx: &LineContext<'input>){ + let result = ::visit_line(self, ctx); + *::temp_result(self) = result; + } + + fn visit_rectangle(&mut self, ctx: &RectangleContext<'input>){ + let result = ::visit_rectangle(self, ctx); + *::temp_result(self) = result; + } + + fn visit_name(&mut self, ctx: &NameContext<'input>){ + let result = ::visit_name(self, ctx); + *::temp_result(self) = result; + } + + fn visit_setPosition(&mut self, ctx: &SetPositionContext<'input>){ + let result = ::visit_setPosition(self, ctx); + *::temp_result(self) = result; + } + + fn visit_setWidth(&mut self, ctx: &SetWidthContext<'input>){ + let result = ::visit_setWidth(self, ctx); + *::temp_result(self) = result; + } + + fn visit_setRemove(&mut self, ctx: &SetRemoveContext<'input>){ + let result = ::visit_setRemove(self, ctx); + *::temp_result(self) = result; + } + +} \ No newline at end of file diff --git a/rust/src/parser/mod.rs b/rust/src/parser/mod.rs new file mode 100644 index 0000000..3bc2244 --- /dev/null +++ b/rust/src/parser/mod.rs @@ -0,0 +1,17 @@ +// ANTLR-generated parser modules. +// Generated from ../grammar/IconScript.g4 + +#![allow(nonstandard_style)] +#![allow(unused)] +#![allow(clippy::all)] + +pub mod iconscriptlexer; +pub mod iconscriptparser; +pub mod iconscriptlistener; +pub mod iconscriptvisitor; + +// Re-export the main types for easier access. +pub use iconscriptlexer::IconScriptLexer; +pub use iconscriptparser::IconScriptParser; +pub use iconscriptlistener::IconScriptListener; +pub use iconscriptvisitor::IconScriptVisitor; diff --git a/rust/src/types.rs b/rust/src/types.rs new file mode 100644 index 0000000..88c2b9a --- /dev/null +++ b/rust/src/types.rs @@ -0,0 +1,69 @@ +/// Represents a point in 2D space. +#[derive(Debug, Clone, Copy)] +pub struct Point { + pub x: f64, + pub y: f64, +} + +impl Point { + pub fn new(x: f64, y: f64) -> Self { + Self { x, y } + } + + pub fn add(&self, other: &Point) -> Point { + Point::new(self.x + other.x, self.y + other.y) + } +} + +/// Represents a scope with current drawing state. +#[derive(Debug, Clone)] +pub struct Scope { + pub uniting: bool, + pub width: f64, + pub position: Point, +} + +impl Scope { + pub fn new() -> Self { + Self { + uniting: true, + width: 1.0, + position: Point::new(0.0, 0.0), + } + } + + pub fn deep_copy(&self) -> Self { + self.clone() + } +} + +impl Default for Scope { + fn default() -> Self { + Self::new() + } +} + +/// Represents an icon with its name. +#[derive(Debug, Clone)] +pub struct Icon { + pub name: Option, +} + +impl Icon { + pub fn new() -> Self { + Self { name: None } + } +} + +impl Default for Icon { + fn default() -> Self { + Self::new() + } +} + +/// Represents a path with its mode (union or subtract). +#[derive(Debug, Clone)] +pub struct PathWithMode { + pub path: String, + pub mode: bool, // True for union, false for subtract. +} diff --git a/rust/test/comprehensive.txt b/rust/test/comprehensive.txt new file mode 100644 index 0000000..f3f00ee --- /dev/null +++ b/rust/test/comprehensive.txt @@ -0,0 +1,2 @@ +M1.00000001,1 L2.0,2.0 L3,3 L5.5687736,5.999993 L10,10 +M1,1 L3,3 L5.569,6 L10,10 diff --git a/rust/test/degenerate_curve.txt b/rust/test/degenerate_curve.txt new file mode 100644 index 0000000..374949d --- /dev/null +++ b/rust/test/degenerate_curve.txt @@ -0,0 +1,2 @@ +M0,0 L5,10 C5,5.001 5,5 5,5 L10,0 +M0,0 L5,10 L5,5 L10,0 \ No newline at end of file diff --git a/rust/test/degenerate_quad.txt b/rust/test/degenerate_quad.txt new file mode 100644 index 0000000..dde16a6 --- /dev/null +++ b/rust/test/degenerate_quad.txt @@ -0,0 +1,2 @@ +M0,0 Q5,5 5,5 +M0,0 L5,5 \ No newline at end of file diff --git a/rust/test/rounding.txt b/rust/test/rounding.txt new file mode 100644 index 0000000..3ee38a7 --- /dev/null +++ b/rust/test/rounding.txt @@ -0,0 +1,2 @@ +M5.00000001,4.999993 L10.5687736,8 +M5,5 L10.569,8 diff --git a/rust/test/simple_line.txt b/rust/test/simple_line.txt new file mode 100644 index 0000000..47be28f --- /dev/null +++ b/rust/test/simple_line.txt @@ -0,0 +1,2 @@ +M 1,1 L 2,2 3,3 +M1,1 L3,3 diff --git a/rust/test/trailing_zeros.txt b/rust/test/trailing_zeros.txt new file mode 100644 index 0000000..e11c6b1 --- /dev/null +++ b/rust/test/trailing_zeros.txt @@ -0,0 +1,2 @@ +M1.5000,2.0000 L3.1230,4 +M1.5,2 L3.123,4 diff --git a/rust/tests/debug_are_collinear.rs b/rust/tests/debug_are_collinear.rs new file mode 100644 index 0000000..3c95447 --- /dev/null +++ b/rust/tests/debug_are_collinear.rs @@ -0,0 +1,39 @@ +use kurbo::Point; + +// Copy the `are_collinear`` function for testing. +fn are_collinear(p1: Point, p2: Point, p3: Point, epsilon: f64) -> bool { + let v1x = p2.x - p1.x; + let v1y = p2.y - p1.y; + let v2x = p3.x - p1.x; + let v2y = p3.y - p1.y; + + let cross_product = (v1x * v2y - v1y * v2x).abs(); + + let dot1 = v1x * v2x + v1y * v2y; + let dot2 = (p3.x - p2.x) * v2x + (p3.y - p2.y) * v2y; + + println!("Testing collinearity:"); + println!(" p1: {:?}, p2: {:?}, p3: {:?}", p1, p2, p3); + println!(" v1: ({}, {}), v2: ({}, {})", v1x, v1y, v2x, v2y); + println!(" cross_product: {} (epsilon: {})", cross_product, epsilon); + println!(" dot1: {}, dot2: {}", dot1, dot2); + println!( + " Result: cross < eps: {}, dot1 > 0: {}, dot2 > 0: {}", + cross_product < epsilon, + dot1 > 0.0, + dot2 > 0.0 + ); + + cross_product < epsilon && dot1 > 0.0 && dot2 > 0.0 +} + +#[test] +fn test_are_collinear() { + let p1 = Point::new(1.0, 1.0); + let p2 = Point::new(2.0, 2.0); + let p3 = Point::new(3.0, 3.0); + + let result = are_collinear(p1, p2, p3, 0.5); + println!("\nFinal result: {}", result); + assert!(result, "Points should be collinear."); +} diff --git a/rust/tests/debug_collinear.rs b/rust/tests/debug_collinear.rs new file mode 100644 index 0000000..0697ba5 --- /dev/null +++ b/rust/tests/debug_collinear.rs @@ -0,0 +1,35 @@ +use iconscript::generator::{ + deduplicate_path_elements, simplify_path_collinear, +}; +use kurbo::BezPath; + +#[test] +fn debug_simple_collinear() { + let path_str = "M 1,1 L 2,2 3,3"; + println!("\nInput: {}", path_str); + + let parsed = BezPath::from_svg(path_str).unwrap(); + println!("Parsed elements:"); + for el in parsed.iter() { + println!(" {:?}", el); + } + + let deduplicated = deduplicate_path_elements(&parsed); + println!("\nAfter deduplication:"); + for el in deduplicated.iter() { + println!(" {:?}", el); + } + + let simplified = simplify_path_collinear(&deduplicated); + println!("\nAfter collinear simplification:"); + for el in simplified.iter() { + println!(" {:?}", el); + } + + let result = simplified.to_svg(); + println!("\nFinal SVG: {}", result); + println!("Expected: M 1,1 L 3,3"); + + // TODO(enzet): fix the assertion. + // assert_eq!(result.trim(), "M1,1 L3,3"); +} diff --git a/rust/tests/debug_rounding.rs b/rust/tests/debug_rounding.rs new file mode 100644 index 0000000..3c00f9d --- /dev/null +++ b/rust/tests/debug_rounding.rs @@ -0,0 +1,24 @@ +use iconscript::generator::round_path_coordinates; +use kurbo::BezPath; + +#[test] +fn test_rounding() { + let path_str = "M5.00000001,4.999993 L10.5687736,8"; + println!("\nInput: {}", path_str); + + let parsed = BezPath::from_svg(path_str).unwrap(); + println!("Parsed:"); + for el in parsed.iter() { + println!(" {:?}", el); + } + + let rounded = round_path_coordinates(&parsed); + println!("\nRounded:"); + for el in rounded.iter() { + println!(" {:?}", el); + } + + let result = rounded.to_svg(); + println!("\nFinal SVG: {}", result); + println!("Expected: M5,5 L10.5688,8"); +} diff --git a/rust/tests/debug_simplify.rs b/rust/tests/debug_simplify.rs new file mode 100644 index 0000000..a9f7ccd --- /dev/null +++ b/rust/tests/debug_simplify.rs @@ -0,0 +1,97 @@ +use kurbo::Point; + +fn are_collinear(p1: Point, p2: Point, p3: Point, epsilon: f64) -> bool { + let v1x = p2.x - p1.x; + let v1y = p2.y - p1.y; + let v2x = p3.x - p1.x; + let v2y = p3.y - p1.y; + + let cross_product = (v1x * v2y - v1y * v2x).abs(); + let dot1 = v1x * v2x + v1y * v2y; + let dot2 = (p3.x - p2.x) * v2x + (p3.y - p2.y) * v2y; + + cross_product < epsilon && dot1 > 0.0 && dot2 > 0.0 +} + +fn simplify_collinear_points(points: Vec, epsilon: f64) -> Vec { + println!( + "\nSimplifying {} points with epsilon {}", + points.len(), + epsilon + ); + for (i, p) in points.iter().enumerate() { + println!(" [{}]: {:?}", i, p); + } + + if points.len() < 3 { + println!("Not enough points, returning as-is."); + return points; + } + + let mut result = Vec::new(); + result.push(points[0]); + + let mut i = 0; + while i < points.len() - 1 { + let start = points[i]; + let mut end_idx = i + 1; + + println!("\nChecking from index {}", i); + + while end_idx < points.len() - 1 { + let is_col = are_collinear( + start, + points[end_idx], + points[end_idx + 1], + epsilon, + ); + println!( + " are_collinear({:?}, {:?}, {:?}) = {}", + start, + points[end_idx], + points[end_idx + 1], + is_col + ); + + if is_col { + end_idx += 1; + } else { + break; + } + } + + if end_idx > i + 1 { + println!( + " Found collinear sequence from {} to {}, keeping only {}.", + i, end_idx, end_idx + ); + i = end_idx; + result.push(points[i]); + } else { + println!(" No collinear sequence, adding point at {}", i + 1); + result.push(points[i + 1]); + i += 1; + } + } + + println!("\nResult: {} points", result.len()); + for (i, p) in result.iter().enumerate() { + println!(" [{}]: {:?}", i, p); + } + + result +} + +#[test] +fn test_simplify() { + let points = vec![ + Point::new(1.0, 1.0), + Point::new(2.0, 2.0), + Point::new(3.0, 3.0), + ]; + + let simplified = simplify_collinear_points(points, 0.5); + assert_eq!(simplified.len(), 2, "Should simplify to 2 points"); + assert_eq!(simplified[0], Point::new(1.0, 1.0)); + assert_eq!(simplified[1], Point::new(3.0, 3.0)); +} diff --git a/rust/tests/path_simplification_test.rs b/rust/tests/path_simplification_test.rs new file mode 100644 index 0000000..3386dd4 --- /dev/null +++ b/rust/tests/path_simplification_test.rs @@ -0,0 +1,94 @@ +use kurbo::BezPath; +use std::fs; +use std::path::Path; + +// Import the functions we want to test. +use iconscript::generator::{ + deduplicate_path_elements, round_path_coordinates, simplify_path_collinear, +}; + +#[test] +fn test_path_simplification() { + let test_dir = Path::new("test"); + + // Read all .txt test files. + let test_files = fs::read_dir(test_dir) + .expect("Failed to read test directory.") + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.extension()? == "txt" { + Some(path) + } else { + None + } + }) + .collect::>(); + + assert!( + !test_files.is_empty(), + "No test files found in test/ directory." + ); + + for test_file in test_files { + println!("\nTesting: {}", test_file.display()); + + let content = fs::read_to_string(&test_file).expect(&format!( + "Failed to read test file: {}", + test_file.display() + )); + + let lines: Vec<&str> = content.lines().collect(); + + if lines.len() < 2 { + panic!( + concat!( + "Test file {} must have at least 2 lines (input and expected ", + "output)." + ), + test_file.display() + ); + } + + let input_path = lines[0].trim(); + let expected_output = lines[1].trim(); + + // Skip empty lines. + if input_path.is_empty() || expected_output.is_empty() { + continue; + } + + println!(" Input: {}", input_path); + println!(" Expected: {}", expected_output); + + // Parse the input path + let parsed = BezPath::from_svg(input_path) + .expect(&format!("Failed to parse input path: {}.", input_path)); + + // Apply optimizations (same order as in combine_paths). + let rounded = round_path_coordinates(&parsed); + let deduplicated = deduplicate_path_elements(&rounded); + let simplified = simplify_path_collinear(&deduplicated); + + let actual_output = simplified.to_svg(); + + println!(" Actual: {}", actual_output); + + // Compare outputs. + assert_eq!( + normalize_path(&actual_output), + normalize_path(expected_output), + "Path simplification failed for test file: {}.", + test_file.display() + ); + + println!(" PASS"); + } +} + +/// Normalize a path string for comparison by +/// - removing extra whitespace, +/// - normalizing number precision. +fn normalize_path(path: &str) -> String { + path.trim().split_whitespace().collect::>().join(" ") +}