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() {
233 return Some(run_compiled_chunk(chunk, interp));
234 }
235
236 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 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 Err(compiler::CompileError::Frozen { line, detail }) => {
273 Some(Err(PerlError::runtime(detail, line)))
274 }
275 Err(compiler::CompileError::Unsupported(_)) => None,
278 }
279}
280
281fn 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 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 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 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 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 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 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 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 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 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
442pub 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 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;