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/// Compile and execute via the bytecode VM.
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 surface any "VM unimplemented op" as a real error (in practice unreachable: the
230    // chunk was produced by `compile_program`, which only emits ops the VM implements).
231    if let Some(chunk) = interp.pec_precompiled_chunk.take() {
232        return Some(run_compiled_chunk(chunk, interp));
233    }
234
235    // `use strict 'vars'` is enforced at compile time by the compiler (see
236    // `Compiler::check_strict_scalar_access` and siblings). `strict refs` / `strict subs` are
237    // enforced by the tree helpers that the VM already delegates into (symbolic deref,
238    // `call_named_sub`, etc.), so they work transitively.
239    let comp = compiler::Compiler::new()
240        .with_source_file(interp.file.clone())
241        .with_strict_vars(interp.strict_vars);
242    let chunk = match comp.compile_program(program) {
243        Ok(chunk) => chunk,
244        Err(compiler::CompileError::Frozen { line, detail }) => {
245            return Some(Err(PerlError::runtime(detail, line)));
246        }
247        Err(compiler::CompileError::Unsupported(reason)) => {
248            return Some(Err(PerlError::runtime(
249                format!("VM compile error (unsupported): {}", reason),
250                0,
251            )));
252        }
253    };
254
255    if let Some(fp) = interp.pec_cache_fingerprint.take() {
256        let bundle = pec::PecBundle::new(interp.strict_vars, fp, program.clone(), chunk.clone());
257        let _ = pec::try_save(&bundle);
258    }
259    Some(run_compiled_chunk(chunk, interp))
260}
261
262/// Shared execution tail used by both the cache-hit and compile paths in
263/// [`try_vm_execute`]. Pulled out so the `.pec` fast path does not duplicate the
264/// flip-flop / BEGIN-END / struct-def wiring every VM run depends on.
265fn run_compiled_chunk(chunk: bytecode::Chunk, interp: &mut Interpreter) -> PerlResult<PerlValue> {
266    interp.clear_flip_flop_state();
267    interp.prepare_flip_flop_vm_slots(chunk.flip_flop_slots);
268    if interp.disasm_bytecode {
269        eprintln!("{}", chunk.disassemble());
270    }
271    interp.clear_begin_end_blocks_after_vm_compile();
272    for def in &chunk.struct_defs {
273        interp
274            .struct_defs
275            .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
276    }
277    for def in &chunk.enum_defs {
278        interp
279            .enum_defs
280            .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
281    }
282    // Load traits before classes so trait enforcement can reference them
283    for def in &chunk.trait_defs {
284        interp
285            .trait_defs
286            .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
287    }
288    for def in &chunk.class_defs {
289        let mut def = def.clone();
290        // Final class/method enforcement
291        for parent_name in &def.extends.clone() {
292            if let Some(parent_def) = interp.class_defs.get(parent_name) {
293                if parent_def.is_final {
294                    return Err(crate::error::PerlError::runtime(
295                        format!("cannot extend final class `{}`", parent_name),
296                        0,
297                    ));
298                }
299                for m in &def.methods {
300                    if let Some(parent_method) = parent_def.method(&m.name) {
301                        if parent_method.is_final {
302                            return Err(crate::error::PerlError::runtime(
303                                format!(
304                                    "cannot override final method `{}` from class `{}`",
305                                    m.name, parent_name
306                                ),
307                                0,
308                            ));
309                        }
310                    }
311                }
312            }
313        }
314        // Trait contract enforcement + default method inheritance
315        for trait_name in &def.implements.clone() {
316            if let Some(trait_def) = interp.trait_defs.get(trait_name) {
317                for required in trait_def.required_methods() {
318                    let has_method = def.methods.iter().any(|m| m.name == required.name);
319                    if !has_method {
320                        return Err(crate::error::PerlError::runtime(
321                            format!(
322                                "class `{}` implements trait `{}` but does not define required method `{}`",
323                                def.name, trait_name, required.name
324                            ),
325                            0,
326                        ));
327                    }
328                }
329                // Inherit default methods from trait (methods with bodies)
330                for tm in &trait_def.methods {
331                    if tm.body.is_some() && !def.methods.iter().any(|m| m.name == tm.name) {
332                        def.methods.push(tm.clone());
333                    }
334                }
335            }
336        }
337        // Abstract method enforcement: concrete subclasses must implement
338        // all abstract methods (body-less methods) from abstract parents
339        if !def.is_abstract {
340            for parent_name in &def.extends.clone() {
341                if let Some(parent_def) = interp.class_defs.get(parent_name) {
342                    if parent_def.is_abstract {
343                        for m in &parent_def.methods {
344                            if m.body.is_none() && !def.methods.iter().any(|dm| dm.name == m.name) {
345                                return Err(crate::error::PerlError::runtime(
346                                    format!(
347                                        "class `{}` must implement abstract method `{}` from `{}`",
348                                        def.name, m.name, parent_name
349                                    ),
350                                    0,
351                                ));
352                            }
353                        }
354                    }
355                }
356            }
357        }
358        // Initialize static fields
359        for sf in &def.static_fields {
360            let val = if let Some(ref expr) = sf.default {
361                match interp.eval_expr(expr) {
362                    Ok(v) => v,
363                    Err(crate::interpreter::FlowOrError::Error(e)) => return Err(e),
364                    Err(_) => crate::value::PerlValue::UNDEF,
365                }
366            } else {
367                crate::value::PerlValue::UNDEF
368            };
369            let key = format!("{}::{}", def.name, sf.name);
370            interp.scope.declare_scalar(&key, val);
371        }
372        // Register class methods into subs so method dispatch finds them.
373        for m in &def.methods {
374            if let Some(ref body) = m.body {
375                let fq = format!("{}::{}", def.name, m.name);
376                let sub = std::sync::Arc::new(crate::value::PerlSub {
377                    name: fq.clone(),
378                    params: m.params.clone(),
379                    body: body.clone(),
380                    closure_env: None,
381                    prototype: None,
382                    fib_like: None,
383                });
384                interp.subs.insert(fq, sub);
385            }
386        }
387        // Set @ClassName::ISA so MRO/isa resolution works.
388        if !def.extends.is_empty() {
389            let isa_key = format!("{}::ISA", def.name);
390            let parents: Vec<crate::value::PerlValue> = def
391                .extends
392                .iter()
393                .map(|p| crate::value::PerlValue::string(p.clone()))
394                .collect();
395            interp.scope.declare_array(&isa_key, parents);
396        }
397        interp
398            .class_defs
399            .insert(def.name.clone(), std::sync::Arc::new(def));
400    }
401    let vm_jit = interp.vm_jit_enabled && interp.profiler.is_none();
402    let mut vm = vm::VM::new(&chunk, interp);
403    vm.set_jit_enabled(vm_jit);
404    match vm.execute() {
405        Ok(val) => {
406            interp.drain_pending_destroys(0)?;
407            Ok(val)
408        }
409        // On cache-hit path, surface VM errors directly (we no longer hold the
410        // fresh Program the caller passed). For the cold-compile path, the compiler would
411        // have already returned `Unsupported` for anything the VM cannot run, so this
412        // branch is effectively unreachable there. Either way, surface as a runtime error.
413        Err(e)
414            if e.message.starts_with("VM: unimplemented op")
415                || e.message.starts_with("Unimplemented builtin") =>
416        {
417            Err(PerlError::runtime(e.message, 0))
418        }
419        Err(e) => Err(e),
420    }
421}
422
423/// Compile program and run only the prelude (BEGIN/CHECK/INIT phase blocks) via the VM.
424/// Stores the compiled chunk on `interp.line_mode_chunk` for per-line re-execution.
425pub fn compile_and_run_prelude(program: &ast::Program, interp: &mut Interpreter) -> PerlResult<()> {
426    interp.prepare_program_top_level(program)?;
427    let comp = compiler::Compiler::new()
428        .with_source_file(interp.file.clone())
429        .with_strict_vars(interp.strict_vars);
430    let mut chunk = match comp.compile_program(program) {
431        Ok(chunk) => chunk,
432        Err(compiler::CompileError::Frozen { line, detail }) => {
433            return Err(PerlError::runtime(detail, line));
434        }
435        Err(compiler::CompileError::Unsupported(reason)) => {
436            return Err(PerlError::runtime(
437                format!("VM compile error (unsupported): {}", reason),
438                0,
439            ));
440        }
441    };
442
443    interp.clear_flip_flop_state();
444    interp.prepare_flip_flop_vm_slots(chunk.flip_flop_slots);
445    if interp.disasm_bytecode {
446        eprintln!("{}", chunk.disassemble());
447    }
448    interp.clear_begin_end_blocks_after_vm_compile();
449    for def in &chunk.struct_defs {
450        interp
451            .struct_defs
452            .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
453    }
454    for def in &chunk.enum_defs {
455        interp
456            .enum_defs
457            .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
458    }
459    for def in &chunk.trait_defs {
460        interp
461            .trait_defs
462            .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
463    }
464    for def in &chunk.class_defs {
465        interp
466            .class_defs
467            .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
468    }
469    // Register class methods.
470    for def in &chunk.class_defs {
471        for m in &def.methods {
472            if let Some(ref body) = m.body {
473                let fq = format!("{}::{}", def.name, m.name);
474                let sub = std::sync::Arc::new(crate::value::PerlSub {
475                    name: fq.clone(),
476                    params: m.params.clone(),
477                    body: body.clone(),
478                    closure_env: None,
479                    prototype: None,
480                    fib_like: None,
481                });
482                interp.subs.insert(fq, sub);
483            }
484        }
485    }
486
487    let body_ip = chunk.body_start_ip;
488    if body_ip > 0 && body_ip < chunk.ops.len() {
489        // Run only the prelude: temporarily place Halt at body start.
490        let saved_op = chunk.ops[body_ip].clone();
491        chunk.ops[body_ip] = bytecode::Op::Halt;
492        let vm_jit = interp.vm_jit_enabled && interp.profiler.is_none();
493        let mut vm = vm::VM::new(&chunk, interp);
494        vm.set_jit_enabled(vm_jit);
495        let _ = vm.execute()?;
496        chunk.ops[body_ip] = saved_op;
497    }
498
499    interp.line_mode_chunk = Some(chunk);
500    Ok(())
501}
502
503/// Execute the body portion of a pre-compiled chunk for one input line.
504/// Sets `$_` to `line_str`, runs from `body_start_ip` to Halt, returns `$_` for `-p` output.
505pub fn run_line_body(
506    chunk: &bytecode::Chunk,
507    interp: &mut Interpreter,
508    line_str: &str,
509    is_last_input_line: bool,
510) -> PerlResult<Option<String>> {
511    interp.line_mode_eof_pending = is_last_input_line;
512    let result: PerlResult<Option<String>> = (|| {
513        interp.line_number += 1;
514        interp
515            .scope
516            .set_topic(value::PerlValue::string(line_str.to_string()));
517
518        if interp.auto_split {
519            let sep = interp.field_separator.as_deref().unwrap_or(" ");
520            let re = regex::Regex::new(sep).unwrap_or_else(|_| regex::Regex::new(" ").unwrap());
521            let fields: Vec<value::PerlValue> = re
522                .split(line_str)
523                .map(|s| value::PerlValue::string(s.to_string()))
524                .collect();
525            interp.scope.set_array("F", fields)?;
526        }
527
528        let vm_jit = interp.vm_jit_enabled && interp.profiler.is_none();
529        let mut vm = vm::VM::new(chunk, interp);
530        vm.set_jit_enabled(vm_jit);
531        vm.ip = chunk.body_start_ip;
532        let _ = vm.execute()?;
533
534        let mut out = interp.scope.get_scalar("_").to_string();
535        out.push_str(&interp.ors);
536        Ok(Some(out))
537    })();
538    interp.line_mode_eof_pending = false;
539    result
540}
541
542/// Parse + register top-level subs / `use` (same as the VM path), then compile to bytecode without running.
543/// Also runs static analysis to detect undefined variables and subroutines.
544pub fn lint_program(program: &ast::Program, interp: &mut Interpreter) -> PerlResult<()> {
545    interp.prepare_program_top_level(program)?;
546    static_analysis::analyze_program(program, &interp.file)?;
547    if interp.strict_refs || interp.strict_subs || interp.strict_vars {
548        return Ok(());
549    }
550    let comp = compiler::Compiler::new().with_source_file(interp.file.clone());
551    match comp.compile_program(program) {
552        Ok(_) => Ok(()),
553        Err(e) => Err(compile_error_to_perl(e)),
554    }
555}
556
557fn compile_error_to_perl(e: compiler::CompileError) -> PerlError {
558    match e {
559        compiler::CompileError::Unsupported(msg) => {
560            PerlError::runtime(format!("compile: {}", msg), 0)
561        }
562        compiler::CompileError::Frozen { line, detail } => PerlError::runtime(detail, line),
563    }
564}
565
566#[cfg(test)]
567mod tests {
568    use super::*;
569
570    #[test]
571    fn run_executes_last_expression_value() {
572        // Statement-only programs may yield 0 via the VM path; assert parse + run succeed.
573        let p = parse("2 + 2").expect("parse");
574        assert!(!p.statements.is_empty());
575        let _ = run("2 + 2").expect("run");
576    }
577
578    #[test]
579    fn run_propagates_parse_errors() {
580        assert!(run("sub f {").is_err());
581    }
582
583    #[test]
584    fn interpreter_scope_persists_global_scalar_across_execute_calls() {
585        let mut interp = Interpreter::new();
586        let assign = parse("$persist_test = 100").expect("parse assign");
587        interp.execute(&assign).expect("assign");
588        let read = parse("$persist_test").expect("parse read");
589        let v = interp.execute(&read).expect("read");
590        assert_eq!(v.to_int(), 100);
591    }
592
593    #[test]
594    fn parse_empty_program() {
595        let p = parse("").expect("empty input should parse");
596        assert!(p.statements.is_empty());
597    }
598
599    #[test]
600    fn parse_expression_statement() {
601        let p = parse("2 + 2").expect("parse");
602        assert!(!p.statements.is_empty());
603    }
604
605    #[test]
606    fn parse_semicolon_only_statements() {
607        parse(";;").expect("semicolons only");
608    }
609
610    #[test]
611    fn parse_if_with_block() {
612        parse("if (1) { 2 }").expect("if");
613    }
614
615    #[test]
616    fn parse_fails_on_invalid_syntax() {
617        assert!(parse("sub f {").is_err());
618    }
619
620    #[test]
621    fn parse_qw_word_list() {
622        parse("my @a = qw(x y z)").expect("qw list");
623    }
624
625    #[test]
626    fn parse_c_style_for_loop() {
627        parse("for (my $i = 0; $i < 3; $i = $i + 1) { 1; }").expect("c-style for");
628    }
629
630    #[test]
631    fn parse_package_statement() {
632        parse("package Foo::Bar; 1").expect("package");
633    }
634
635    #[test]
636    fn parse_unless_block() {
637        parse("unless (0) { 1; }").expect("unless");
638    }
639
640    #[test]
641    fn parse_if_elsif_else() {
642        parse("if (0) { 1; } elsif (1) { 2; } else { 3; }").expect("if elsif");
643    }
644
645    #[test]
646    fn parse_q_constructor() {
647        parse(r#"my $s = q{braces}"#).expect("q{}");
648        parse(r#"my $t = qq(double)"#).expect("qq()");
649    }
650
651    #[test]
652    fn parse_regex_literals() {
653        parse("m/foo/").expect("m//");
654        parse("s/foo/bar/g").expect("s///");
655    }
656
657    #[test]
658    fn parse_begin_and_end_blocks() {
659        parse("BEGIN { 1; }").expect("BEGIN");
660        parse("END { 1; }").expect("END");
661    }
662
663    #[test]
664    fn parse_transliterate_y() {
665        parse("$_ = 'a'; y/a/A/").expect("y//");
666    }
667
668    #[test]
669    fn parse_foreach_with_my_iterator() {
670        parse("foreach my $x (1, 2) { $x; }").expect("foreach my");
671    }
672
673    #[test]
674    fn parse_our_declaration() {
675        parse("our $g = 1").expect("our");
676    }
677
678    #[test]
679    fn parse_local_declaration() {
680        parse("local $x = 1").expect("local");
681    }
682
683    #[test]
684    fn parse_use_no_statements() {
685        parse("use strict").expect("use");
686        parse("no warnings").expect("no");
687    }
688
689    #[test]
690    fn parse_sub_with_prototype() {
691        parse("fn sum ($$) { return $_0 + $_1; }").expect("fn prototype");
692        parse("fn try (&;@) { my ( $try, @code_refs ) = @_; }").expect("prototype @ slurpy");
693    }
694
695    #[test]
696    fn parse_list_expression_in_parentheses() {
697        parse("my @a = (1, 2, 3)").expect("list");
698    }
699
700    #[test]
701    fn parse_require_expression() {
702        parse("require strict").expect("require");
703    }
704
705    #[test]
706    fn parse_do_string_eval_form() {
707        parse(r#"do "foo.pl""#).expect("do string");
708    }
709
710    #[test]
711    fn parse_package_qualified_name() {
712        parse("package Foo::Bar::Baz").expect("package ::");
713    }
714
715    #[test]
716    fn parse_my_multiple_declarations() {
717        parse("my ($a, $b, $c)").expect("my list");
718    }
719
720    #[test]
721    fn parse_eval_block_statement() {
722        parse("eval { 1; }").expect("eval block");
723    }
724
725    #[test]
726    fn parse_p_statement() {
727        parse("p 42").expect("p");
728    }
729
730    #[test]
731    fn parse_chop_scalar() {
732        parse("chop $s").expect("chop");
733    }
734
735    #[test]
736    fn vendor_perl_inc_path_points_at_vendor_perl() {
737        let p = vendor_perl_inc_path();
738        assert!(
739            p.ends_with("vendor/perl"),
740            "unexpected vendor path: {}",
741            p.display()
742        );
743    }
744
745    #[test]
746    fn format_program_roundtrips_simple_expression() {
747        let p = parse("$x + 1").expect("parse");
748        let out = format_program(&p);
749        assert!(!out.trim().is_empty());
750    }
751}
752
753#[cfg(test)]
754mod builtins_extended_tests;
755
756#[cfg(test)]
757mod lib_api_extended_tests;
758
759#[cfg(test)]
760mod parallel_api_tests;
761
762#[cfg(test)]
763mod parse_smoke_extended;
764
765#[cfg(test)]
766mod parse_smoke_batch2;
767
768#[cfg(test)]
769mod parse_smoke_batch3;
770
771#[cfg(test)]
772mod parse_smoke_batch4;
773
774#[cfg(test)]
775mod crate_api_tests;
776
777#[cfg(test)]
778mod parser_shape_tests;
779
780#[cfg(test)]
781mod interpreter_unit_tests;
782
783#[cfg(test)]
784mod run_semantics_tests;
785
786#[cfg(test)]
787mod run_semantics_more;
788
789#[cfg(test)]
790mod value_extra_tests;
791
792#[cfg(test)]
793mod lexer_extra_tests;
794
795#[cfg(test)]
796mod parser_extra_tests;
797
798#[cfg(test)]
799mod builtins_extra_tests;
800
801#[cfg(test)]
802mod thread_extra_tests;
803
804#[cfg(test)]
805mod error_extra_tests;
806
807#[cfg(test)]
808mod oo_extra_tests;
809
810#[cfg(test)]
811mod regex_extra_tests;
812
813#[cfg(test)]
814mod aot_extra_tests;