mago-type-syntax 1.25.2

Provides core utilities useful for building lexers and parsers within Mago.
Documentation
use std::hint::black_box;

use bumpalo::Bump;
use criterion::Criterion;
use criterion::Throughput;
use criterion::criterion_group;
use criterion::criterion_main;
use mago_database::file::FileId;
use mago_span::Position;
use mago_span::Span;
use mago_syntax_core::input::Input;
use mago_type_syntax::lexer::TypeLexer;
use mago_type_syntax::parse_str;

const SMALL_TYPES: &str = include_str!("fixtures/small.txt");
const MEDIUM_TYPES: &str = include_str!("fixtures/medium.txt");
const LARGE_TYPES: &str = include_str!("fixtures/large.txt");

fn benchmark_type_lexer(c: &mut Criterion) {
    let mut group = c.benchmark_group("type-lexer");
    let file_id = FileId::new("bench.php");

    group.throughput(Throughput::Bytes(LARGE_TYPES.len() as u64));
    group.bench_function("large", |b| {
        b.iter(|| {
            let input = Input::new(file_id, black_box(LARGE_TYPES.as_bytes()));
            let mut lexer = TypeLexer::new(input);
            let mut count = 0usize;
            while let Some(result) = lexer.advance() {
                if result.is_ok() {
                    count += 1;
                }
            }
            black_box(count)
        })
    });

    group.finish();
}

fn benchmark_type_parser(c: &mut Criterion) {
    let mut group = c.benchmark_group("type-parser");
    let file_id = FileId::new("bench.php");

    for (name, content) in [("small", SMALL_TYPES), ("medium", MEDIUM_TYPES), ("large", LARGE_TYPES)] {
        group.throughput(Throughput::Bytes(content.len() as u64));
        group.bench_function(name, |b| {
            b.iter(|| {
                let arena = Bump::new();
                let mut success_count = 0usize;
                for line in content.lines() {
                    let line = line.trim();
                    if line.is_empty() || line.starts_with("//") {
                        continue;
                    }
                    let span = Span::new(file_id, Position::new(0), Position::new(line.len() as u32));
                    if parse_str(&arena, span, black_box(line)).is_ok() {
                        success_count += 1;
                    }
                }
                black_box(success_count)
            })
        });
    }

    group.finish();
}

fn benchmark_single_complex_type(c: &mut Criterion) {
    let mut group = c.benchmark_group("type-complex");
    let file_id = FileId::new("bench.php");

    let complex_type = r#"array{users: list<object{id: positive-int, name: non-empty-string, email?: string, roles: list<string>}>, pagination: object{page: int, per_page: int, total: int}, filters?: array<string, mixed>}"#;

    group.throughput(Throughput::Bytes(complex_type.len() as u64));
    group.bench_function("nested_array_shape", |b| {
        b.iter(|| {
            let arena = Bump::new();
            let span = Span::new(file_id, Position::new(0), Position::new(complex_type.len() as u32));
            let ok = parse_str(&arena, span, black_box(complex_type)).is_ok();
            black_box(ok)
        })
    });

    let closure_type = r#"Closure(array{id: int, name: string}, list<string>, ?object{active: bool}): array{success: bool, errors?: list<string>}"#;

    group.throughput(Throughput::Bytes(closure_type.len() as u64));
    group.bench_function("complex_closure", |b| {
        b.iter(|| {
            let arena = Bump::new();
            let span = Span::new(file_id, Position::new(0), Position::new(closure_type.len() as u32));
            let ok = parse_str(&arena, span, black_box(closure_type)).is_ok();
            black_box(ok)
        })
    });

    group.finish();
}

criterion_group!(benches, benchmark_type_lexer, benchmark_type_parser, benchmark_single_complex_type);
criterion_main!(benches);