1#![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
70pub 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
90use std::sync::atomic::{AtomicBool, Ordering};
93
94static COMPAT_MODE: AtomicBool = AtomicBool::new(false);
98
99pub fn set_compat_mode(on: bool) {
101 COMPAT_MODE.store(on, Ordering::Relaxed);
102}
103
104#[inline]
106pub fn compat_mode() -> bool {
107 COMPAT_MODE.load(Ordering::Relaxed)
108}
109use value::PerlValue;
110
111pub fn format_program(p: &ast::Program) -> String {
114 fmt::format_program(p)
115}
116
117pub fn convert_to_stryke(p: &ast::Program) -> String {
119 convert::convert_program(p)
120}
121
122pub fn convert_to_stryke_with_options(p: &ast::Program, opts: &convert::ConvertOptions) -> String {
124 convert::convert_program_with_options(p, opts)
125}
126
127pub fn deconvert_to_perl(p: &ast::Program) -> String {
129 deconvert::deconvert_program(p)
130}
131
132pub 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
144pub fn parse_with_file(code: &str, file: &str) -> PerlResult<ast::Program> {
147 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
161pub 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
169pub 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
186pub fn vendor_perl_inc_path() -> std::path::PathBuf {
189 std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("vendor/perl")
190}
191
192pub 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
203pub 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
212pub 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 if let Some(chunk) = interp.pec_precompiled_chunk.take() {
232 return Some(run_compiled_chunk(chunk, interp));
233 }
234
235 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
262fn 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 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 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 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 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 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 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 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 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 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
423pub 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 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 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
503pub 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
542pub 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 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;