1#![allow(rustdoc::private_intra_doc_links)]
6#![allow(rustdoc::broken_intra_doc_links)]
7#![allow(clippy::needless_range_loop)]
8
9pub mod agent;
10pub mod aot;
11pub mod ast;
12pub mod builtins;
13pub mod bytecode;
14pub mod capture;
15pub mod cluster;
16pub mod compiler;
17pub mod controller;
18pub mod convert;
19mod crypt_util;
20pub mod data_section;
21pub mod debugger;
22pub mod deconvert;
23pub mod deparse;
24pub mod english;
25pub mod error;
26mod fib_like_tail;
27pub mod fmt;
28pub mod format;
29pub mod interpreter;
30mod jit;
31mod jwt;
32pub mod lexer;
33pub mod list_builtins;
34pub mod lsp;
35mod map_grep_fast;
36mod map_stream;
37pub mod mro;
38mod nanbox;
39mod native_codec;
40pub mod native_data;
41pub mod pack;
42pub mod par_lines;
43mod par_list;
44pub mod par_pipeline;
45pub mod par_walk;
46pub mod parallel_trace;
47pub mod parser;
48pub mod pcache;
49pub mod pchannel;
50mod pending_destroy;
51pub mod perl_decode;
52pub mod perl_fs;
53pub mod perl_inc;
54mod perl_regex;
55pub mod perl_signal;
56pub mod pkg;
57mod pmap_progress;
58pub mod ppool;
59pub mod profiler;
60pub mod pwatch;
61pub mod remote_wire;
62pub mod rust_ffi;
63pub mod rust_sugar;
64pub mod scope;
65pub mod script_cache;
66mod sort_fast;
67pub mod special_vars;
68pub mod static_analysis;
69pub mod token;
70pub mod value;
71pub mod vm;
72
73pub use zsh::exec as shell_exec;
75pub use zsh::fds as shell_fds;
76pub use zsh::history as shell_history;
77pub use zsh::jobs as shell_jobs;
78pub use zsh::lexer as zsh_lex;
79pub use zsh::parser as shell_parse;
80pub use zsh::parser as zsh_parse;
81pub use zsh::signals as shell_signal;
82pub use zsh::tokens as zsh_tokens;
83pub use zsh::zle as shell_zle;
84pub use zsh::zwc as shell_zwc;
85
86pub use interpreter::{
87 perl_bracket_version, FEAT_SAY, FEAT_STATE, FEAT_SWITCH, FEAT_UNICODE_STRINGS,
88};
89
90use error::{PerlError, PerlResult};
91use interpreter::Interpreter;
92
93use std::sync::atomic::{AtomicBool, Ordering};
96
97static COMPAT_MODE: AtomicBool = AtomicBool::new(false);
101
102static NO_INTEROP_MODE: AtomicBool = AtomicBool::new(false);
106
107pub fn set_compat_mode(on: bool) {
109 COMPAT_MODE.store(on, Ordering::Relaxed);
110}
111
112#[inline]
114pub fn compat_mode() -> bool {
115 COMPAT_MODE.load(Ordering::Relaxed)
116}
117
118pub fn set_no_interop_mode(on: bool) {
120 NO_INTEROP_MODE.store(on, Ordering::Relaxed);
121}
122
123#[inline]
125pub fn no_interop_mode() -> bool {
126 NO_INTEROP_MODE.load(Ordering::Relaxed)
127}
128use value::PerlValue;
129
130pub fn format_program(p: &ast::Program) -> String {
133 fmt::format_program(p)
134}
135
136pub fn convert_to_stryke(p: &ast::Program) -> String {
138 convert::convert_program(p)
139}
140
141pub fn convert_to_stryke_with_options(p: &ast::Program, opts: &convert::ConvertOptions) -> String {
143 convert::convert_program_with_options(p, opts)
144}
145
146pub fn deconvert_to_perl(p: &ast::Program) -> String {
148 deconvert::deconvert_program(p)
149}
150
151pub fn deconvert_to_perl_with_options(
153 p: &ast::Program,
154 opts: &deconvert::DeconvertOptions,
155) -> String {
156 deconvert::deconvert_program_with_options(p, opts)
157}
158
159pub fn parse(code: &str) -> PerlResult<ast::Program> {
160 parse_with_file(code, "-e")
161}
162
163pub fn parse_with_file(code: &str, file: &str) -> PerlResult<ast::Program> {
166 parse_with_file_inner(code, file, false)
167}
168
169pub fn parse_module_with_file(code: &str, file: &str) -> PerlResult<ast::Program> {
172 parse_with_file_inner(code, file, true)
173}
174
175fn parse_with_file_inner(code: &str, file: &str, is_module: bool) -> PerlResult<ast::Program> {
176 let desugared = if compat_mode() {
180 code.to_string()
181 } else {
182 rust_sugar::desugar_rust_blocks(code)
183 };
184 let mut lexer = lexer::Lexer::new_with_file(&desugared, file);
185 let tokens = lexer.tokenize()?;
186 let mut parser = parser::Parser::new_with_file(tokens, file);
187 parser.parsing_module = is_module;
188 parser.parse_program()
189}
190
191pub fn parse_and_run_string(code: &str, interp: &mut Interpreter) -> PerlResult<PerlValue> {
195 let file = interp.file.clone();
196 parse_and_run_string_in_file(code, interp, &file)
197}
198
199pub fn parse_and_run_string_in_file(
202 code: &str,
203 interp: &mut Interpreter,
204 file: &str,
205) -> PerlResult<PerlValue> {
206 parse_and_run_string_in_file_inner(code, interp, file, false)
207}
208
209pub fn parse_and_run_module_in_file(
212 code: &str,
213 interp: &mut Interpreter,
214 file: &str,
215) -> PerlResult<PerlValue> {
216 parse_and_run_string_in_file_inner(code, interp, file, true)
217}
218
219fn parse_and_run_string_in_file_inner(
220 code: &str,
221 interp: &mut Interpreter,
222 file: &str,
223 is_module: bool,
224) -> PerlResult<PerlValue> {
225 let program = if is_module {
226 parse_module_with_file(code, file)?
227 } else {
228 parse_with_file(code, file)?
229 };
230 let saved = interp.file.clone();
231 interp.file = file.to_string();
232 let r = interp.execute(&program);
233 interp.file = saved;
234 let v = r?;
235 interp.drain_pending_destroys(0)?;
236 Ok(v)
237}
238
239pub fn vendor_perl_inc_path() -> std::path::PathBuf {
242 std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("vendor/perl")
243}
244
245pub fn run_lsp_stdio() -> i32 {
247 match lsp::run_stdio() {
248 Ok(()) => 0,
249 Err(e) => {
250 eprintln!("stryke --lsp: {e}");
251 1
252 }
253 }
254}
255
256pub fn run(code: &str) -> PerlResult<PerlValue> {
258 let program = parse(code)?;
259 let mut interp = Interpreter::new();
260 let v = interp.execute(&program)?;
261 interp.run_global_teardown()?;
262 Ok(v)
263}
264
265pub fn try_vm_execute(
273 program: &ast::Program,
274 interp: &mut Interpreter,
275) -> Option<PerlResult<PerlValue>> {
276 if let Err(e) = interp.prepare_program_top_level(program) {
277 return Some(Err(e));
278 }
279
280 if let Some(chunk) = interp.cached_chunk.take() {
283 return Some(run_compiled_chunk(chunk, interp));
284 }
285
286 let comp = compiler::Compiler::new()
291 .with_source_file(interp.file.clone())
292 .with_strict_vars(interp.strict_vars);
293 let chunk = match comp.compile_program(program) {
294 Ok(chunk) => chunk,
295 Err(compiler::CompileError::Frozen { line, detail }) => {
296 return Some(Err(PerlError::runtime(detail, line)));
297 }
298 Err(compiler::CompileError::Unsupported(reason)) => {
299 return Some(Err(PerlError::runtime(
300 format!("VM compile error (unsupported): {}", reason),
301 0,
302 )));
303 }
304 };
305
306 if let Some(path) = interp.sqlite_cache_script_path.take() {
308 let _ = script_cache::try_save(&path, program, &chunk);
309 }
310 Some(run_compiled_chunk(chunk, interp))
311}
312
313fn run_compiled_chunk(chunk: bytecode::Chunk, interp: &mut Interpreter) -> PerlResult<PerlValue> {
317 interp.clear_flip_flop_state();
318 interp.prepare_flip_flop_vm_slots(chunk.flip_flop_slots);
319 if interp.disasm_bytecode {
320 eprintln!("{}", chunk.disassemble());
321 }
322 interp.clear_begin_end_blocks_after_vm_compile();
323 for def in &chunk.struct_defs {
324 interp
325 .struct_defs
326 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
327 }
328 for def in &chunk.enum_defs {
329 interp
330 .enum_defs
331 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
332 }
333 for def in &chunk.trait_defs {
335 interp
336 .trait_defs
337 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
338 }
339 for def in &chunk.class_defs {
340 let mut def = def.clone();
341 for parent_name in &def.extends.clone() {
343 if let Some(parent_def) = interp.class_defs.get(parent_name) {
344 if parent_def.is_final {
345 return Err(crate::error::PerlError::runtime(
346 format!("cannot extend final class `{}`", parent_name),
347 0,
348 ));
349 }
350 for m in &def.methods {
351 if let Some(parent_method) = parent_def.method(&m.name) {
352 if parent_method.is_final {
353 return Err(crate::error::PerlError::runtime(
354 format!(
355 "cannot override final method `{}` from class `{}`",
356 m.name, parent_name
357 ),
358 0,
359 ));
360 }
361 }
362 }
363 }
364 }
365 for trait_name in &def.implements.clone() {
367 if let Some(trait_def) = interp.trait_defs.get(trait_name) {
368 for required in trait_def.required_methods() {
369 let has_method = def.methods.iter().any(|m| m.name == required.name);
370 if !has_method {
371 return Err(crate::error::PerlError::runtime(
372 format!(
373 "class `{}` implements trait `{}` but does not define required method `{}`",
374 def.name, trait_name, required.name
375 ),
376 0,
377 ));
378 }
379 }
380 for tm in &trait_def.methods {
382 if tm.body.is_some() && !def.methods.iter().any(|m| m.name == tm.name) {
383 def.methods.push(tm.clone());
384 }
385 }
386 }
387 }
388 if !def.is_abstract {
391 for parent_name in &def.extends.clone() {
392 if let Some(parent_def) = interp.class_defs.get(parent_name) {
393 if parent_def.is_abstract {
394 for m in &parent_def.methods {
395 if m.body.is_none() && !def.methods.iter().any(|dm| dm.name == m.name) {
396 return Err(crate::error::PerlError::runtime(
397 format!(
398 "class `{}` must implement abstract method `{}` from `{}`",
399 def.name, m.name, parent_name
400 ),
401 0,
402 ));
403 }
404 }
405 }
406 }
407 }
408 }
409 for sf in &def.static_fields {
411 let val = if let Some(ref expr) = sf.default {
412 match interp.eval_expr(expr) {
413 Ok(v) => v,
414 Err(crate::interpreter::FlowOrError::Error(e)) => return Err(e),
415 Err(_) => crate::value::PerlValue::UNDEF,
416 }
417 } else {
418 crate::value::PerlValue::UNDEF
419 };
420 let key = format!("{}::{}", def.name, sf.name);
421 interp.scope.declare_scalar(&key, val);
422 }
423 for m in &def.methods {
425 if let Some(ref body) = m.body {
426 let fq = format!("{}::{}", def.name, m.name);
427 let sub = std::sync::Arc::new(crate::value::PerlSub {
428 name: fq.clone(),
429 params: m.params.clone(),
430 body: body.clone(),
431 closure_env: None,
432 prototype: None,
433 fib_like: None,
434 });
435 interp.subs.insert(fq, sub);
436 }
437 }
438 if !def.extends.is_empty() {
440 let isa_key = format!("{}::ISA", def.name);
441 let parents: Vec<crate::value::PerlValue> = def
442 .extends
443 .iter()
444 .map(|p| crate::value::PerlValue::string(p.clone()))
445 .collect();
446 interp.scope.declare_array(&isa_key, parents);
447 }
448 interp
449 .class_defs
450 .insert(def.name.clone(), std::sync::Arc::new(def));
451 }
452 let vm_jit = interp.vm_jit_enabled && interp.profiler.is_none();
453 let mut vm = vm::VM::new(&chunk, interp);
454 vm.set_jit_enabled(vm_jit);
455 match vm.execute() {
456 Ok(val) => {
457 interp.drain_pending_destroys(0)?;
458 Ok(val)
459 }
460 Err(e)
465 if e.message.starts_with("VM: unimplemented op")
466 || e.message.starts_with("Unimplemented builtin") =>
467 {
468 Err(PerlError::runtime(e.message, 0))
469 }
470 Err(e) => Err(e),
471 }
472}
473
474pub fn compile_and_run_prelude(program: &ast::Program, interp: &mut Interpreter) -> PerlResult<()> {
477 interp.prepare_program_top_level(program)?;
478 let comp = compiler::Compiler::new()
479 .with_source_file(interp.file.clone())
480 .with_strict_vars(interp.strict_vars);
481 let mut chunk = match comp.compile_program(program) {
482 Ok(chunk) => chunk,
483 Err(compiler::CompileError::Frozen { line, detail }) => {
484 return Err(PerlError::runtime(detail, line));
485 }
486 Err(compiler::CompileError::Unsupported(reason)) => {
487 return Err(PerlError::runtime(
488 format!("VM compile error (unsupported): {}", reason),
489 0,
490 ));
491 }
492 };
493
494 interp.clear_flip_flop_state();
495 interp.prepare_flip_flop_vm_slots(chunk.flip_flop_slots);
496 if interp.disasm_bytecode {
497 eprintln!("{}", chunk.disassemble());
498 }
499 interp.clear_begin_end_blocks_after_vm_compile();
500 for def in &chunk.struct_defs {
501 interp
502 .struct_defs
503 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
504 }
505 for def in &chunk.enum_defs {
506 interp
507 .enum_defs
508 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
509 }
510 for def in &chunk.trait_defs {
511 interp
512 .trait_defs
513 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
514 }
515 for def in &chunk.class_defs {
516 interp
517 .class_defs
518 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
519 }
520 for def in &chunk.class_defs {
522 for m in &def.methods {
523 if let Some(ref body) = m.body {
524 let fq = format!("{}::{}", def.name, m.name);
525 let sub = std::sync::Arc::new(crate::value::PerlSub {
526 name: fq.clone(),
527 params: m.params.clone(),
528 body: body.clone(),
529 closure_env: None,
530 prototype: None,
531 fib_like: None,
532 });
533 interp.subs.insert(fq, sub);
534 }
535 }
536 }
537
538 let body_ip = chunk.body_start_ip;
539 if body_ip > 0 && body_ip < chunk.ops.len() {
540 let saved_op = chunk.ops[body_ip].clone();
542 chunk.ops[body_ip] = bytecode::Op::Halt;
543 let vm_jit = interp.vm_jit_enabled && interp.profiler.is_none();
544 let mut vm = vm::VM::new(&chunk, interp);
545 vm.set_jit_enabled(vm_jit);
546 let _ = vm.execute()?;
547 chunk.ops[body_ip] = saved_op;
548 }
549
550 interp.line_mode_chunk = Some(chunk);
551 Ok(())
552}
553
554pub fn run_line_body(
557 chunk: &bytecode::Chunk,
558 interp: &mut Interpreter,
559 line_str: &str,
560 is_last_input_line: bool,
561) -> PerlResult<Option<String>> {
562 interp.line_mode_eof_pending = is_last_input_line;
563 let result: PerlResult<Option<String>> = (|| {
564 interp.line_number += 1;
565 interp
566 .scope
567 .set_topic(value::PerlValue::string(line_str.to_string()));
568
569 if interp.auto_split {
570 let sep = interp.field_separator.as_deref().unwrap_or(" ");
571 let re = regex::Regex::new(sep).unwrap_or_else(|_| regex::Regex::new(" ").unwrap());
572 let fields: Vec<value::PerlValue> = re
573 .split(line_str)
574 .map(|s| value::PerlValue::string(s.to_string()))
575 .collect();
576 interp.scope.set_array("F", fields)?;
577 }
578
579 let vm_jit = interp.vm_jit_enabled && interp.profiler.is_none();
580 let mut vm = vm::VM::new(chunk, interp);
581 vm.set_jit_enabled(vm_jit);
582 vm.ip = chunk.body_start_ip;
583 let _ = vm.execute()?;
584
585 let mut out = interp.scope.get_scalar("_").to_string();
586 out.push_str(&interp.ors);
587 Ok(Some(out))
588 })();
589 interp.line_mode_eof_pending = false;
590 result
591}
592
593pub fn lint_program(program: &ast::Program, interp: &mut Interpreter) -> PerlResult<()> {
596 interp.prepare_program_top_level(program)?;
597 static_analysis::analyze_program(program, &interp.file)?;
598 if interp.strict_refs || interp.strict_subs || interp.strict_vars {
599 return Ok(());
600 }
601 let comp = compiler::Compiler::new().with_source_file(interp.file.clone());
602 match comp.compile_program(program) {
603 Ok(_) => Ok(()),
604 Err(e) => Err(compile_error_to_perl(e)),
605 }
606}
607
608fn compile_error_to_perl(e: compiler::CompileError) -> PerlError {
609 match e {
610 compiler::CompileError::Unsupported(msg) => {
611 PerlError::runtime(format!("compile: {}", msg), 0)
612 }
613 compiler::CompileError::Frozen { line, detail } => PerlError::runtime(detail, line),
614 }
615}
616
617#[cfg(test)]
618mod tests {
619 use super::*;
620
621 #[test]
622 fn run_executes_last_expression_value() {
623 let p = parse("2 + 2").expect("parse");
625 assert!(!p.statements.is_empty());
626 let _ = run("2 + 2").expect("run");
627 }
628
629 #[test]
630 fn run_propagates_parse_errors() {
631 assert!(run("sub f {").is_err());
632 }
633
634 #[test]
635 fn interpreter_scope_persists_global_scalar_across_execute_calls() {
636 let mut interp = Interpreter::new();
637 let assign = parse("$persist_test = 100").expect("parse assign");
638 interp.execute(&assign).expect("assign");
639 let read = parse("$persist_test").expect("parse read");
640 let v = interp.execute(&read).expect("read");
641 assert_eq!(v.to_int(), 100);
642 }
643
644 #[test]
645 fn parse_empty_program() {
646 let p = parse("").expect("empty input should parse");
647 assert!(p.statements.is_empty());
648 }
649
650 #[test]
651 fn parse_expression_statement() {
652 let p = parse("2 + 2").expect("parse");
653 assert!(!p.statements.is_empty());
654 }
655
656 #[test]
657 fn parse_semicolon_only_statements() {
658 parse(";;").expect("semicolons only");
659 }
660
661 #[test]
662 fn parse_if_with_block() {
663 parse("if (1) { 2 }").expect("if");
664 }
665
666 #[test]
667 fn parse_fails_on_invalid_syntax() {
668 assert!(parse("sub f {").is_err());
669 }
670
671 #[test]
672 fn parse_qw_word_list() {
673 parse("my @a = qw(x y z)").expect("qw list");
674 }
675
676 #[test]
677 fn parse_c_style_for_loop() {
678 parse("for (my $i = 0; $i < 3; $i = $i + 1) { 1; }").expect("c-style for");
679 }
680
681 #[test]
682 fn parse_package_statement() {
683 parse("package Foo::Bar; 1").expect("package");
684 }
685
686 #[test]
687 fn parse_unless_block() {
688 parse("unless (0) { 1; }").expect("unless");
689 }
690
691 #[test]
692 fn parse_if_elsif_else() {
693 parse("if (0) { 1; } elsif (1) { 2; } else { 3; }").expect("if elsif");
694 }
695
696 #[test]
697 fn parse_q_constructor() {
698 parse(r#"my $s = q{braces}"#).expect("q{}");
699 parse(r#"my $t = qq(double)"#).expect("qq()");
700 }
701
702 #[test]
703 fn parse_regex_literals() {
704 parse("m/foo/").expect("m//");
705 parse("s/foo/bar/g").expect("s///");
706 }
707
708 #[test]
709 fn parse_begin_and_end_blocks() {
710 parse("BEGIN { 1; }").expect("BEGIN");
711 parse("END { 1; }").expect("END");
712 }
713
714 #[test]
715 fn parse_transliterate_y() {
716 parse("$_ = 'a'; y/a/A/").expect("y//");
717 }
718
719 #[test]
720 fn parse_foreach_with_my_iterator() {
721 parse("foreach my $x (1, 2) { $x; }").expect("foreach my");
722 }
723
724 #[test]
725 fn parse_our_declaration() {
726 parse("our $g = 1").expect("our");
727 }
728
729 #[test]
730 fn parse_local_declaration() {
731 parse("local $x = 1").expect("local");
732 }
733
734 #[test]
735 fn parse_use_no_statements() {
736 parse("use strict").expect("use");
737 parse("no warnings").expect("no");
738 }
739
740 #[test]
741 fn parse_sub_with_prototype() {
742 parse("fn add2 ($$) { return $_0 + $_1; }").expect("fn prototype");
743 parse("fn try_block (&;@) { my ( $try, @code_refs ) = @_; }").expect("prototype @ slurpy");
744 }
745
746 #[test]
747 fn parse_list_expression_in_parentheses() {
748 parse("my @a = (1, 2, 3)").expect("list");
749 }
750
751 #[test]
752 fn parse_require_expression() {
753 parse("require strict").expect("require");
754 }
755
756 #[test]
757 fn parse_do_string_eval_form() {
758 parse(r#"do "foo.pl""#).expect("do string");
759 }
760
761 #[test]
762 fn parse_package_qualified_name() {
763 parse("package Foo::Bar::Baz").expect("package ::");
764 }
765
766 #[test]
767 fn parse_my_multiple_declarations() {
768 parse("my ($a, $b, $c)").expect("my list");
769 }
770
771 #[test]
772 fn parse_eval_block_statement() {
773 parse("eval { 1; }").expect("eval block");
774 }
775
776 #[test]
777 fn parse_p_statement() {
778 parse("p 42").expect("p");
779 }
780
781 #[test]
782 fn parse_chop_scalar() {
783 parse("chop $s").expect("chop");
784 }
785
786 #[test]
787 fn vendor_perl_inc_path_points_at_vendor_perl() {
788 let p = vendor_perl_inc_path();
789 assert!(
790 p.ends_with("vendor/perl"),
791 "unexpected vendor path: {}",
792 p.display()
793 );
794 }
795
796 #[test]
797 fn format_program_roundtrips_simple_expression() {
798 let p = parse("$x + 1").expect("parse");
799 let out = format_program(&p);
800 assert!(!out.trim().is_empty());
801 }
802}
803
804#[cfg(test)]
805mod builtins_extended_tests;
806
807#[cfg(test)]
808mod lib_api_extended_tests;
809
810#[cfg(test)]
811mod parallel_api_tests;
812
813#[cfg(test)]
814mod parse_smoke_extended;
815
816#[cfg(test)]
817mod parse_smoke_batch2;
818
819#[cfg(test)]
820mod parse_smoke_batch3;
821
822#[cfg(test)]
823mod parse_smoke_batch4;
824
825#[cfg(test)]
826mod crate_api_tests;
827
828#[cfg(test)]
829mod parser_shape_tests;
830
831#[cfg(test)]
832mod interpreter_unit_tests;
833
834#[cfg(test)]
835mod run_semantics_tests;
836
837#[cfg(test)]
838mod run_semantics_more;
839
840#[cfg(test)]
841mod value_extra_tests;
842
843#[cfg(test)]
844mod lexer_extra_tests;
845
846#[cfg(test)]
847mod parser_extra_tests;
848
849#[cfg(test)]
850mod builtins_extra_tests;
851
852#[cfg(test)]
853mod thread_extra_tests;
854
855#[cfg(test)]
856mod error_extra_tests;
857
858#[cfg(test)]
859mod oo_extra_tests;
860
861#[cfg(test)]
862mod regex_extra_tests;
863
864#[cfg(test)]
865mod aot_extra_tests;