Skip to main content

stryke/
lib.rs

1//! Crate root — see [`README.md`](https://github.com/MenkeTechnologies/stryke) for overview.
2// `cargo doc` with `RUSTDOCFLAGS=-D warnings` (CI) flags intra-doc links to private items and
3// a few shorthand links (`MethodCall`, `Op::…`) that do not resolve as paths. Suppress until
4// docs are normalized to `crate::…` paths and public-only links.
5#![allow(rustdoc::private_intra_doc_links)]
6#![allow(rustdoc::broken_intra_doc_links)]
7#![allow(clippy::needless_range_loop)]
8
9pub mod aot;
10pub mod ast;
11pub mod builtins;
12pub mod bytecode;
13pub mod capture;
14pub mod cluster;
15pub mod compiler;
16pub mod convert;
17mod crypt_util;
18pub mod data_section;
19pub mod debugger;
20pub mod deconvert;
21pub mod deparse;
22pub mod english;
23pub mod error;
24mod fib_like_tail;
25pub mod fmt;
26pub mod format;
27pub mod interpreter;
28mod jit;
29mod jwt;
30pub mod lexer;
31pub mod list_util;
32pub mod lsp;
33mod map_grep_fast;
34mod map_stream;
35pub mod mro;
36mod nanbox;
37mod native_codec;
38pub mod native_data;
39pub mod pack;
40pub mod par_lines;
41mod par_list;
42pub mod par_pipeline;
43pub mod par_walk;
44pub mod parallel_trace;
45pub mod parser;
46pub mod pcache;
47pub mod pchannel;
48pub mod pec;
49mod pending_destroy;
50pub mod perl_decode;
51pub mod perl_fs;
52pub mod perl_inc;
53mod perl_regex;
54pub mod perl_signal;
55mod pmap_progress;
56pub mod ppool;
57pub mod profiler;
58pub mod pwatch;
59pub mod remote_wire;
60pub mod rust_ffi;
61pub mod rust_sugar;
62pub mod scope;
63mod sort_fast;
64pub mod special_vars;
65pub mod static_analysis;
66pub mod token;
67pub mod value;
68pub mod vm;
69
70// Re-export shell components from the zsh crate
71pub use zsh::exec as shell_exec;
72pub use zsh::fds as shell_fds;
73pub use zsh::history as shell_history;
74pub use zsh::jobs as shell_jobs;
75pub use zsh::lexer as zsh_lex;
76pub use zsh::parser as shell_parse;
77pub use zsh::parser as zsh_parse;
78pub use zsh::signals as shell_signal;
79pub use zsh::tokens as zsh_tokens;
80pub use zsh::zle as shell_zle;
81pub use zsh::zwc as shell_zwc;
82
83pub use interpreter::{
84    perl_bracket_version, FEAT_SAY, FEAT_STATE, FEAT_SWITCH, FEAT_UNICODE_STRINGS,
85};
86
87use error::{PerlError, PerlResult};
88use interpreter::Interpreter;
89
90// ── Perl 5 strict-compat mode (`--compat`) ──────────────────────────────────
91
92use std::sync::atomic::{AtomicBool, Ordering};
93
94/// When `true`, all stryke extensions are disabled and only stock Perl 5
95/// syntax / builtins are accepted.  Set once from the CLI driver and read by
96/// the parser, compiler, and interpreter.
97static COMPAT_MODE: AtomicBool = AtomicBool::new(false);
98
99/// Enable Perl 5 strict-compatibility mode (disables all stryke extensions).
100pub fn set_compat_mode(on: bool) {
101    COMPAT_MODE.store(on, Ordering::Relaxed);
102}
103
104/// Returns `true` when `--compat` is active.
105#[inline]
106pub fn compat_mode() -> bool {
107    COMPAT_MODE.load(Ordering::Relaxed)
108}
109use value::PerlValue;
110
111/// Parse a string of Perl code and return the AST.
112/// Pretty-print a parsed program as Perl-like source (`stryke --fmt`).
113pub fn format_program(p: &ast::Program) -> String {
114    fmt::format_program(p)
115}
116
117/// Convert a parsed program to stryke syntax with `|>` pipes and no semicolons.
118pub fn convert_to_stryke(p: &ast::Program) -> String {
119    convert::convert_program(p)
120}
121
122/// Convert a parsed program to stryke syntax with custom options.
123pub fn convert_to_stryke_with_options(p: &ast::Program, opts: &convert::ConvertOptions) -> String {
124    convert::convert_program_with_options(p, opts)
125}
126
127/// Deconvert a parsed stryke program back to standard Perl .pl syntax.
128pub fn deconvert_to_perl(p: &ast::Program) -> String {
129    deconvert::deconvert_program(p)
130}
131
132/// Deconvert a parsed stryke program back to standard Perl .pl syntax with options.
133pub fn deconvert_to_perl_with_options(
134    p: &ast::Program,
135    opts: &deconvert::DeconvertOptions,
136) -> String {
137    deconvert::deconvert_program_with_options(p, opts)
138}
139
140pub fn parse(code: &str) -> PerlResult<ast::Program> {
141    parse_with_file(code, "-e")
142}
143
144/// Parse with a **source path** for lexer/parser diagnostics (`… at FILE line N`), e.g. a script
145/// path or a required `.pm` absolute path. Use [`parse`] for snippets where `-e` is appropriate.
146pub fn parse_with_file(code: &str, file: &str) -> PerlResult<ast::Program> {
147    // `rust { ... }` FFI blocks are desugared at source level into BEGIN-wrapped builtin
148    // calls — the parity roadmap forbids new `StmtKind` variants for new behavior, so this
149    // pre-pass is the right shape. No-op for programs that don't mention `rust`.
150    let desugared = if compat_mode() {
151        code.to_string()
152    } else {
153        rust_sugar::desugar_rust_blocks(code)
154    };
155    let mut lexer = lexer::Lexer::new_with_file(&desugared, file);
156    let tokens = lexer.tokenize()?;
157    let mut parser = parser::Parser::new_with_file(tokens, file);
158    parser.parse_program()
159}
160
161/// Parse and execute a string of Perl code within an existing interpreter.
162/// Tries bytecode VM first, falls back to tree-walker on unsupported features.
163/// Uses [`Interpreter::file`] for both parse diagnostics and `__FILE__` during this execution.
164pub fn parse_and_run_string(code: &str, interp: &mut Interpreter) -> PerlResult<PerlValue> {
165    let file = interp.file.clone();
166    parse_and_run_string_in_file(code, interp, &file)
167}
168
169/// Like [`parse_and_run_string`], but parse errors and `__FILE__` for this run use `file` (e.g. a
170/// required module path). Restores [`Interpreter::file`] after execution.
171pub fn parse_and_run_string_in_file(
172    code: &str,
173    interp: &mut Interpreter,
174    file: &str,
175) -> PerlResult<PerlValue> {
176    let program = parse_with_file(code, file)?;
177    let saved = interp.file.clone();
178    interp.file = file.to_string();
179    let r = interp.execute(&program);
180    interp.file = saved;
181    let v = r?;
182    interp.drain_pending_destroys(0)?;
183    Ok(v)
184}
185
186/// Crate-root `vendor/perl` (e.g. `List/Util.pm`). The `stryke` / `stryke` driver prepends this to
187/// `@INC` when the directory exists so in-tree pure-Perl modules shadow XS-only core stubs.
188pub fn vendor_perl_inc_path() -> std::path::PathBuf {
189    std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("vendor/perl")
190}
191
192/// Language server over stdio (`stryke --lsp`). Returns a process exit code.
193pub fn run_lsp_stdio() -> i32 {
194    match lsp::run_stdio() {
195        Ok(()) => 0,
196        Err(e) => {
197            eprintln!("stryke --lsp: {e}");
198            1
199        }
200    }
201}
202
203/// Parse and execute a string of Perl code with a fresh interpreter.
204pub fn run(code: &str) -> PerlResult<PerlValue> {
205    let program = parse(code)?;
206    let mut interp = Interpreter::new();
207    let v = interp.execute(&program)?;
208    interp.run_global_teardown()?;
209    Ok(v)
210}
211
212/// Try to compile and run via bytecode VM. Returns None if compilation fails.
213///
214/// **`.pec` bytecode cache integration.** When `interp.pec_precompiled_chunk` is populated
215/// (set by the `stryke` driver from a [`crate::pec::try_load`] hit), this function skips
216/// `compile_program` entirely and runs the preloaded chunk. On cache miss the compiler
217/// runs normally and, if `interp.pec_cache_fingerprint` is set, the fresh chunk + program
218/// are persisted as a `.pec` bundle so the next warm start can skip both parse and compile.
219pub fn try_vm_execute(
220    program: &ast::Program,
221    interp: &mut Interpreter,
222) -> Option<PerlResult<PerlValue>> {
223    if let Err(e) = interp.prepare_program_top_level(program) {
224        return Some(Err(e));
225    }
226
227    // Fast path: chunk loaded from a `.pec` cache hit. Consume the slot with `.take()` so a
228    // subsequent re-entry (e.g. nested `do FILE`) does not reuse a stale chunk. On cache hit
229    // we cannot fall back to the tree walker mid-run — surface any "VM unimplemented op" as
230    // a real error (in practice unreachable: the chunk was produced by `compile_program`,
231    // which only emits ops the VM implements).
232    if let Some(chunk) = interp.pec_precompiled_chunk.take() {
233        return Some(run_compiled_chunk(chunk, interp));
234    }
235
236    // `use strict 'vars'` is enforced at compile time by the compiler (see
237    // `Compiler::check_strict_scalar_access` and siblings). `strict refs` / `strict subs` are
238    // enforced by the tree helpers that the VM already delegates into (symbolic deref,
239    // `call_named_sub`, etc.), so they work transitively.
240    let comp = compiler::Compiler::new()
241        .with_source_file(interp.file.clone())
242        .with_strict_vars(interp.strict_vars);
243    match comp.compile_program(program) {
244        Ok(chunk) => {
245            // Persist after a cache miss so the next warm start can skip both parse and
246            // compile. Save failures are swallowed: a broken cache is an optimization loss,
247            // not a runtime error.
248            if let Some(fp) = interp.pec_cache_fingerprint.take() {
249                let bundle =
250                    pec::PecBundle::new(interp.strict_vars, fp, program.clone(), chunk.clone());
251                let _ = pec::try_save(&bundle);
252            }
253            match run_compiled_chunk(chunk, interp) {
254                Ok(result) => Some(Ok(result)),
255                Err(e) => {
256                    let msg = e.message.as_str();
257                    if msg.starts_with("VM: unimplemented op")
258                        || msg.starts_with("Unimplemented builtin")
259                    {
260                        None
261                    } else {
262                        Some(Err(e))
263                    }
264                }
265            }
266        }
267        // `CompileError::Frozen` is a hard compile-time error (strict pragma violations, frozen
268        // lvalue writes, unknown goto labels). Promote it to a user-visible runtime error so
269        // the VM path matches `perl` — without this promotion the fallback would run the tree
270        // interpreter, which sometimes silently accepts the same construct (e.g. strict_vars
271        // isn't enforced on scalar assignment in the tree path).
272        Err(compiler::CompileError::Frozen { line, detail }) => {
273            Some(Err(PerlError::runtime(detail, line)))
274        }
275        // `Unsupported` just means "this VM compiler doesn't handle this construct yet" — fall
276        // back to the tree interpreter.
277        Err(compiler::CompileError::Unsupported(_)) => None,
278    }
279}
280
281/// Shared execution tail used by both the cache-hit and compile paths in
282/// [`try_vm_execute`]. Pulled out so the `.pec` fast path does not duplicate the
283/// flip-flop / BEGIN-END / struct-def wiring every VM run depends on.
284fn run_compiled_chunk(chunk: bytecode::Chunk, interp: &mut Interpreter) -> PerlResult<PerlValue> {
285    interp.clear_flip_flop_state();
286    interp.prepare_flip_flop_vm_slots(chunk.flip_flop_slots);
287    if interp.disasm_bytecode {
288        eprintln!("{}", chunk.disassemble());
289    }
290    interp.clear_begin_end_blocks_after_vm_compile();
291    for def in &chunk.struct_defs {
292        interp
293            .struct_defs
294            .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
295    }
296    for def in &chunk.enum_defs {
297        interp
298            .enum_defs
299            .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
300    }
301    // Load traits before classes so trait enforcement can reference them
302    for def in &chunk.trait_defs {
303        interp
304            .trait_defs
305            .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
306    }
307    for def in &chunk.class_defs {
308        let mut def = def.clone();
309        // Final class/method enforcement
310        for parent_name in &def.extends.clone() {
311            if let Some(parent_def) = interp.class_defs.get(parent_name) {
312                if parent_def.is_final {
313                    return Err(crate::error::PerlError::runtime(
314                        format!("cannot extend final class `{}`", parent_name),
315                        0,
316                    ));
317                }
318                for m in &def.methods {
319                    if let Some(parent_method) = parent_def.method(&m.name) {
320                        if parent_method.is_final {
321                            return Err(crate::error::PerlError::runtime(
322                                format!(
323                                    "cannot override final method `{}` from class `{}`",
324                                    m.name, parent_name
325                                ),
326                                0,
327                            ));
328                        }
329                    }
330                }
331            }
332        }
333        // Trait contract enforcement + default method inheritance
334        for trait_name in &def.implements.clone() {
335            if let Some(trait_def) = interp.trait_defs.get(trait_name) {
336                for required in trait_def.required_methods() {
337                    let has_method = def.methods.iter().any(|m| m.name == required.name);
338                    if !has_method {
339                        return Err(crate::error::PerlError::runtime(
340                            format!(
341                                "class `{}` implements trait `{}` but does not define required method `{}`",
342                                def.name, trait_name, required.name
343                            ),
344                            0,
345                        ));
346                    }
347                }
348                // Inherit default methods from trait (methods with bodies)
349                for tm in &trait_def.methods {
350                    if tm.body.is_some() && !def.methods.iter().any(|m| m.name == tm.name) {
351                        def.methods.push(tm.clone());
352                    }
353                }
354            }
355        }
356        // Abstract method enforcement: concrete subclasses must implement
357        // all abstract methods (body-less methods) from abstract parents
358        if !def.is_abstract {
359            for parent_name in &def.extends.clone() {
360                if let Some(parent_def) = interp.class_defs.get(parent_name) {
361                    if parent_def.is_abstract {
362                        for m in &parent_def.methods {
363                            if m.body.is_none() && !def.methods.iter().any(|dm| dm.name == m.name) {
364                                return Err(crate::error::PerlError::runtime(
365                                    format!(
366                                        "class `{}` must implement abstract method `{}` from `{}`",
367                                        def.name, m.name, parent_name
368                                    ),
369                                    0,
370                                ));
371                            }
372                        }
373                    }
374                }
375            }
376        }
377        // Initialize static fields
378        for sf in &def.static_fields {
379            let val = if let Some(ref expr) = sf.default {
380                match interp.eval_expr(expr) {
381                    Ok(v) => v,
382                    Err(crate::interpreter::FlowOrError::Error(e)) => return Err(e),
383                    Err(_) => crate::value::PerlValue::UNDEF,
384                }
385            } else {
386                crate::value::PerlValue::UNDEF
387            };
388            let key = format!("{}::{}", def.name, sf.name);
389            interp.scope.declare_scalar(&key, val);
390        }
391        // Register class methods into subs so method dispatch finds them.
392        for m in &def.methods {
393            if let Some(ref body) = m.body {
394                let fq = format!("{}::{}", def.name, m.name);
395                let sub = std::sync::Arc::new(crate::value::PerlSub {
396                    name: fq.clone(),
397                    params: m.params.clone(),
398                    body: body.clone(),
399                    closure_env: None,
400                    prototype: None,
401                    fib_like: None,
402                });
403                interp.subs.insert(fq, sub);
404            }
405        }
406        // Set @ClassName::ISA so MRO/isa resolution works.
407        if !def.extends.is_empty() {
408            let isa_key = format!("{}::ISA", def.name);
409            let parents: Vec<crate::value::PerlValue> = def
410                .extends
411                .iter()
412                .map(|p| crate::value::PerlValue::string(p.clone()))
413                .collect();
414            interp.scope.declare_array(&isa_key, parents);
415        }
416        interp
417            .class_defs
418            .insert(def.name.clone(), std::sync::Arc::new(def));
419    }
420    let vm_jit = interp.vm_jit_enabled && interp.profiler.is_none();
421    let mut vm = vm::VM::new(&chunk, interp);
422    vm.set_jit_enabled(vm_jit);
423    match vm.execute() {
424        Ok(val) => {
425            interp.drain_pending_destroys(0)?;
426            Ok(val)
427        }
428        // On cache-hit path we cannot fall back to the tree walker (we no longer hold the
429        // fresh Program the caller passed). For the cold-compile path, the compiler would
430        // have already returned `Unsupported` for anything the VM cannot run, so this
431        // branch is effectively unreachable there. Either way, surface as a runtime error.
432        Err(e)
433            if e.message.starts_with("VM: unimplemented op")
434                || e.message.starts_with("Unimplemented builtin") =>
435        {
436            Err(PerlError::runtime(e.message, 0))
437        }
438        Err(e) => Err(e),
439    }
440}
441
442/// Parse + register top-level subs / `use` (same as the VM path), then compile to bytecode without running.
443/// Also runs static analysis to detect undefined variables and subroutines.
444pub fn lint_program(program: &ast::Program, interp: &mut Interpreter) -> PerlResult<()> {
445    interp.prepare_program_top_level(program)?;
446    static_analysis::analyze_program(program, &interp.file)?;
447    if interp.strict_refs || interp.strict_subs || interp.strict_vars {
448        return Ok(());
449    }
450    let comp = compiler::Compiler::new().with_source_file(interp.file.clone());
451    match comp.compile_program(program) {
452        Ok(_) => Ok(()),
453        Err(e) => Err(compile_error_to_perl(e)),
454    }
455}
456
457fn compile_error_to_perl(e: compiler::CompileError) -> PerlError {
458    match e {
459        compiler::CompileError::Unsupported(msg) => {
460            PerlError::runtime(format!("compile: {}", msg), 0)
461        }
462        compiler::CompileError::Frozen { line, detail } => PerlError::runtime(detail, line),
463    }
464}
465
466#[cfg(test)]
467mod tests {
468    use super::*;
469
470    #[test]
471    fn run_executes_last_expression_value() {
472        // Statement-only programs may yield 0 via the VM path; assert parse + run succeed.
473        let p = parse("2 + 2").expect("parse");
474        assert!(!p.statements.is_empty());
475        let _ = run("2 + 2").expect("run");
476    }
477
478    #[test]
479    fn run_propagates_parse_errors() {
480        assert!(run("sub f {").is_err());
481    }
482
483    #[test]
484    fn interpreter_scope_persists_global_scalar_across_execute_tree_calls() {
485        let mut interp = Interpreter::new();
486        let assign = parse("$persist_test = 100").expect("parse assign");
487        interp.execute_tree(&assign).expect("assign");
488        let read = parse("$persist_test").expect("parse read");
489        let v = interp.execute_tree(&read).expect("read");
490        assert_eq!(v.to_int(), 100);
491    }
492
493    #[test]
494    fn parse_empty_program() {
495        let p = parse("").expect("empty input should parse");
496        assert!(p.statements.is_empty());
497    }
498
499    #[test]
500    fn parse_expression_statement() {
501        let p = parse("2 + 2").expect("parse");
502        assert!(!p.statements.is_empty());
503    }
504
505    #[test]
506    fn parse_semicolon_only_statements() {
507        parse(";;").expect("semicolons only");
508    }
509
510    #[test]
511    fn parse_if_with_block() {
512        parse("if (1) { 2 }").expect("if");
513    }
514
515    #[test]
516    fn parse_fails_on_invalid_syntax() {
517        assert!(parse("sub f {").is_err());
518    }
519
520    #[test]
521    fn parse_qw_word_list() {
522        parse("my @a = qw(x y z)").expect("qw list");
523    }
524
525    #[test]
526    fn parse_c_style_for_loop() {
527        parse("for (my $i = 0; $i < 3; $i = $i + 1) { 1; }").expect("c-style for");
528    }
529
530    #[test]
531    fn parse_package_statement() {
532        parse("package Foo::Bar; 1").expect("package");
533    }
534
535    #[test]
536    fn parse_unless_block() {
537        parse("unless (0) { 1; }").expect("unless");
538    }
539
540    #[test]
541    fn parse_if_elsif_else() {
542        parse("if (0) { 1; } elsif (1) { 2; } else { 3; }").expect("if elsif");
543    }
544
545    #[test]
546    fn parse_q_constructor() {
547        parse(r#"my $s = q{braces}"#).expect("q{}");
548        parse(r#"my $t = qq(double)"#).expect("qq()");
549    }
550
551    #[test]
552    fn parse_regex_literals() {
553        parse("m/foo/").expect("m//");
554        parse("s/foo/bar/g").expect("s///");
555    }
556
557    #[test]
558    fn parse_begin_and_end_blocks() {
559        parse("BEGIN { 1; }").expect("BEGIN");
560        parse("END { 1; }").expect("END");
561    }
562
563    #[test]
564    fn parse_transliterate_y() {
565        parse("$_ = 'a'; y/a/A/").expect("y//");
566    }
567
568    #[test]
569    fn parse_foreach_with_my_iterator() {
570        parse("foreach my $x (1, 2) { $x; }").expect("foreach my");
571    }
572
573    #[test]
574    fn parse_our_declaration() {
575        parse("our $g = 1").expect("our");
576    }
577
578    #[test]
579    fn parse_local_declaration() {
580        parse("local $x = 1").expect("local");
581    }
582
583    #[test]
584    fn parse_use_no_statements() {
585        parse("use strict").expect("use");
586        parse("no warnings").expect("no");
587    }
588
589    #[test]
590    fn parse_sub_with_prototype() {
591        parse("fn sum ($$) { return $_0 + $_1; }").expect("fn prototype");
592        parse("fn try (&;@) { my ( $try, @code_refs ) = @_; }").expect("prototype @ slurpy");
593    }
594
595    #[test]
596    fn parse_list_expression_in_parentheses() {
597        parse("my @a = (1, 2, 3)").expect("list");
598    }
599
600    #[test]
601    fn parse_require_expression() {
602        parse("require strict").expect("require");
603    }
604
605    #[test]
606    fn parse_do_string_eval_form() {
607        parse(r#"do "foo.pl""#).expect("do string");
608    }
609
610    #[test]
611    fn parse_package_qualified_name() {
612        parse("package Foo::Bar::Baz").expect("package ::");
613    }
614
615    #[test]
616    fn parse_my_multiple_declarations() {
617        parse("my ($a, $b, $c)").expect("my list");
618    }
619
620    #[test]
621    fn parse_eval_block_statement() {
622        parse("eval { 1; }").expect("eval block");
623    }
624
625    #[test]
626    fn parse_p_statement() {
627        parse("p 42").expect("p");
628    }
629
630    #[test]
631    fn parse_chop_scalar() {
632        parse("chop $s").expect("chop");
633    }
634
635    #[test]
636    fn vendor_perl_inc_path_points_at_vendor_perl() {
637        let p = vendor_perl_inc_path();
638        assert!(
639            p.ends_with("vendor/perl"),
640            "unexpected vendor path: {}",
641            p.display()
642        );
643    }
644
645    #[test]
646    fn format_program_roundtrips_simple_expression() {
647        let p = parse("$x + 1").expect("parse");
648        let out = format_program(&p);
649        assert!(!out.trim().is_empty());
650    }
651}
652
653#[cfg(test)]
654mod builtins_extended_tests;
655
656#[cfg(test)]
657mod lib_api_extended_tests;
658
659#[cfg(test)]
660mod parallel_api_tests;
661
662#[cfg(test)]
663mod parse_smoke_extended;
664
665#[cfg(test)]
666mod parse_smoke_batch2;
667
668#[cfg(test)]
669mod parse_smoke_batch3;
670
671#[cfg(test)]
672mod parse_smoke_batch4;
673
674#[cfg(test)]
675mod crate_api_tests;
676
677#[cfg(test)]
678mod parser_shape_tests;
679
680#[cfg(test)]
681mod interpreter_unit_tests;
682
683#[cfg(test)]
684mod run_semantics_tests;
685
686#[cfg(test)]
687mod run_semantics_more;
688
689#[cfg(test)]
690mod value_extra_tests;
691
692#[cfg(test)]
693mod lexer_extra_tests;
694
695#[cfg(test)]
696mod parser_extra_tests;
697
698#[cfg(test)]
699mod builtins_extra_tests;
700
701#[cfg(test)]
702mod thread_extra_tests;
703
704#[cfg(test)]
705mod error_extra_tests;
706
707#[cfg(test)]
708mod oo_extra_tests;
709
710#[cfg(test)]
711mod regex_extra_tests;
712
713#[cfg(test)]
714mod aot_extra_tests;