1#![allow(rustdoc::private_intra_doc_links)]
6#![allow(rustdoc::broken_intra_doc_links)]
7
8pub mod aot;
9pub mod ast;
10pub mod builtins;
11pub mod bytecode;
12pub mod capture;
13pub mod cluster;
14pub mod compiler;
15pub mod convert;
16mod crypt_util;
17pub mod data_section;
18pub mod debugger;
19pub mod deconvert;
20pub mod deparse;
21pub mod english;
22pub mod error;
23mod fib_like_tail;
24pub mod fmt;
25pub mod format;
26pub mod interpreter;
27mod jit;
28mod jwt;
29pub mod lexer;
30pub mod list_util;
31pub mod lsp;
32mod map_grep_fast;
33mod map_stream;
34pub mod mro;
35mod nanbox;
36mod native_codec;
37pub mod native_data;
38pub mod pack;
39pub mod par_lines;
40mod par_list;
41pub mod par_pipeline;
42pub mod par_walk;
43pub mod parallel_trace;
44pub mod parser;
45pub mod pcache;
46pub mod pchannel;
47pub mod pec;
48mod pending_destroy;
49pub mod perl_decode;
50pub mod perl_fs;
51pub mod perl_inc;
52mod perl_regex;
53pub mod perl_signal;
54mod pmap_progress;
55pub mod ppool;
56pub mod profiler;
57pub mod pwatch;
58pub mod remote_wire;
59pub mod rust_ffi;
60pub mod rust_sugar;
61pub mod scope;
62mod sort_fast;
63pub mod special_vars;
64pub mod static_analysis;
65pub mod token;
66pub mod value;
67pub mod vm;
68
69pub use interpreter::{
70 perl_bracket_version, FEAT_SAY, FEAT_STATE, FEAT_SWITCH, FEAT_UNICODE_STRINGS,
71};
72
73use error::{PerlError, PerlResult};
74use interpreter::Interpreter;
75
76use std::sync::atomic::{AtomicBool, Ordering};
79
80static COMPAT_MODE: AtomicBool = AtomicBool::new(false);
84
85pub fn set_compat_mode(on: bool) {
87 COMPAT_MODE.store(on, Ordering::Relaxed);
88}
89
90#[inline]
92pub fn compat_mode() -> bool {
93 COMPAT_MODE.load(Ordering::Relaxed)
94}
95use value::PerlValue;
96
97pub fn format_program(p: &ast::Program) -> String {
100 fmt::format_program(p)
101}
102
103pub fn convert_to_stryke(p: &ast::Program) -> String {
105 convert::convert_program(p)
106}
107
108pub fn convert_to_stryke_with_options(p: &ast::Program, opts: &convert::ConvertOptions) -> String {
110 convert::convert_program_with_options(p, opts)
111}
112
113pub fn deconvert_to_perl(p: &ast::Program) -> String {
115 deconvert::deconvert_program(p)
116}
117
118pub fn deconvert_to_perl_with_options(
120 p: &ast::Program,
121 opts: &deconvert::DeconvertOptions,
122) -> String {
123 deconvert::deconvert_program_with_options(p, opts)
124}
125
126pub fn parse(code: &str) -> PerlResult<ast::Program> {
127 parse_with_file(code, "-e")
128}
129
130pub fn parse_with_file(code: &str, file: &str) -> PerlResult<ast::Program> {
133 let desugared = if compat_mode() {
137 code.to_string()
138 } else {
139 rust_sugar::desugar_rust_blocks(code)
140 };
141 let mut lexer = lexer::Lexer::new_with_file(&desugared, file);
142 let tokens = lexer.tokenize()?;
143 let mut parser = parser::Parser::new_with_file(tokens, file);
144 parser.parse_program()
145}
146
147pub fn parse_and_run_string(code: &str, interp: &mut Interpreter) -> PerlResult<PerlValue> {
151 let file = interp.file.clone();
152 parse_and_run_string_in_file(code, interp, &file)
153}
154
155pub fn parse_and_run_string_in_file(
158 code: &str,
159 interp: &mut Interpreter,
160 file: &str,
161) -> PerlResult<PerlValue> {
162 let program = parse_with_file(code, file)?;
163 let saved = interp.file.clone();
164 interp.file = file.to_string();
165 let r = interp.execute(&program);
166 interp.file = saved;
167 let v = r?;
168 interp.drain_pending_destroys(0)?;
169 Ok(v)
170}
171
172pub fn vendor_perl_inc_path() -> std::path::PathBuf {
175 std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("vendor/perl")
176}
177
178pub fn run_lsp_stdio() -> i32 {
180 match lsp::run_stdio() {
181 Ok(()) => 0,
182 Err(e) => {
183 eprintln!("stryke --lsp: {e}");
184 1
185 }
186 }
187}
188
189pub fn run(code: &str) -> PerlResult<PerlValue> {
191 let program = parse(code)?;
192 let mut interp = Interpreter::new();
193 let v = interp.execute(&program)?;
194 interp.run_global_teardown()?;
195 Ok(v)
196}
197
198pub fn try_vm_execute(
206 program: &ast::Program,
207 interp: &mut Interpreter,
208) -> Option<PerlResult<PerlValue>> {
209 if let Err(e) = interp.prepare_program_top_level(program) {
210 return Some(Err(e));
211 }
212
213 if let Some(chunk) = interp.pec_precompiled_chunk.take() {
219 return Some(run_compiled_chunk(chunk, interp));
220 }
221
222 let comp = compiler::Compiler::new()
227 .with_source_file(interp.file.clone())
228 .with_strict_vars(interp.strict_vars);
229 match comp.compile_program(program) {
230 Ok(chunk) => {
231 if let Some(fp) = interp.pec_cache_fingerprint.take() {
235 let bundle =
236 pec::PecBundle::new(interp.strict_vars, fp, program.clone(), chunk.clone());
237 let _ = pec::try_save(&bundle);
238 }
239 match run_compiled_chunk(chunk, interp) {
240 Ok(result) => Some(Ok(result)),
241 Err(e) => {
242 let msg = e.message.as_str();
243 if msg.starts_with("VM: unimplemented op")
244 || msg.starts_with("Unimplemented builtin")
245 {
246 None
247 } else {
248 Some(Err(e))
249 }
250 }
251 }
252 }
253 Err(compiler::CompileError::Frozen { line, detail }) => {
259 Some(Err(PerlError::runtime(detail, line)))
260 }
261 Err(compiler::CompileError::Unsupported(_)) => None,
264 }
265}
266
267fn run_compiled_chunk(chunk: bytecode::Chunk, interp: &mut Interpreter) -> PerlResult<PerlValue> {
271 interp.clear_flip_flop_state();
272 interp.prepare_flip_flop_vm_slots(chunk.flip_flop_slots);
273 if interp.disasm_bytecode {
274 eprintln!("{}", chunk.disassemble());
275 }
276 interp.clear_begin_end_blocks_after_vm_compile();
277 for def in &chunk.struct_defs {
278 interp
279 .struct_defs
280 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
281 }
282 for def in &chunk.enum_defs {
283 interp
284 .enum_defs
285 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
286 }
287 for def in &chunk.trait_defs {
289 interp
290 .trait_defs
291 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
292 }
293 for def in &chunk.class_defs {
294 let mut def = def.clone();
295 for parent_name in &def.extends.clone() {
297 if let Some(parent_def) = interp.class_defs.get(parent_name) {
298 if parent_def.is_final {
299 return Err(crate::error::PerlError::runtime(
300 format!("cannot extend final class `{}`", parent_name),
301 0,
302 ));
303 }
304 for m in &def.methods {
305 if let Some(parent_method) = parent_def.method(&m.name) {
306 if parent_method.is_final {
307 return Err(crate::error::PerlError::runtime(
308 format!(
309 "cannot override final method `{}` from class `{}`",
310 m.name, parent_name
311 ),
312 0,
313 ));
314 }
315 }
316 }
317 }
318 }
319 for trait_name in &def.implements.clone() {
321 if let Some(trait_def) = interp.trait_defs.get(trait_name) {
322 for required in trait_def.required_methods() {
323 let has_method = def.methods.iter().any(|m| m.name == required.name);
324 if !has_method {
325 return Err(crate::error::PerlError::runtime(
326 format!(
327 "class `{}` implements trait `{}` but does not define required method `{}`",
328 def.name, trait_name, required.name
329 ),
330 0,
331 ));
332 }
333 }
334 for tm in &trait_def.methods {
336 if tm.body.is_some() && !def.methods.iter().any(|m| m.name == tm.name) {
337 def.methods.push(tm.clone());
338 }
339 }
340 }
341 }
342 if !def.is_abstract {
345 for parent_name in &def.extends.clone() {
346 if let Some(parent_def) = interp.class_defs.get(parent_name) {
347 if parent_def.is_abstract {
348 for m in &parent_def.methods {
349 if m.body.is_none() && !def.methods.iter().any(|dm| dm.name == m.name) {
350 return Err(crate::error::PerlError::runtime(
351 format!(
352 "class `{}` must implement abstract method `{}` from `{}`",
353 def.name, m.name, parent_name
354 ),
355 0,
356 ));
357 }
358 }
359 }
360 }
361 }
362 }
363 for sf in &def.static_fields {
365 let val = if let Some(ref expr) = sf.default {
366 match interp.eval_expr(expr) {
367 Ok(v) => v,
368 Err(crate::interpreter::FlowOrError::Error(e)) => return Err(e),
369 Err(_) => crate::value::PerlValue::UNDEF,
370 }
371 } else {
372 crate::value::PerlValue::UNDEF
373 };
374 let key = format!("{}::{}", def.name, sf.name);
375 interp.scope.declare_scalar(&key, val);
376 }
377 interp
378 .class_defs
379 .insert(def.name.clone(), std::sync::Arc::new(def));
380 }
381 let vm_jit = interp.vm_jit_enabled && interp.profiler.is_none();
382 let mut vm = vm::VM::new(&chunk, interp);
383 vm.set_jit_enabled(vm_jit);
384 match vm.execute() {
385 Ok(val) => {
386 interp.drain_pending_destroys(0)?;
387 Ok(val)
388 }
389 Err(e)
394 if e.message.starts_with("VM: unimplemented op")
395 || e.message.starts_with("Unimplemented builtin") =>
396 {
397 Err(PerlError::runtime(e.message, 0))
398 }
399 Err(e) => Err(e),
400 }
401}
402
403pub fn lint_program(program: &ast::Program, interp: &mut Interpreter) -> PerlResult<()> {
406 interp.prepare_program_top_level(program)?;
407 static_analysis::analyze_program(program, &interp.file)?;
408 if interp.strict_refs || interp.strict_subs || interp.strict_vars {
409 return Ok(());
410 }
411 let comp = compiler::Compiler::new().with_source_file(interp.file.clone());
412 match comp.compile_program(program) {
413 Ok(_) => Ok(()),
414 Err(e) => Err(compile_error_to_perl(e)),
415 }
416}
417
418fn compile_error_to_perl(e: compiler::CompileError) -> PerlError {
419 match e {
420 compiler::CompileError::Unsupported(msg) => {
421 PerlError::runtime(format!("compile: {}", msg), 0)
422 }
423 compiler::CompileError::Frozen { line, detail } => PerlError::runtime(detail, line),
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 #[test]
432 fn run_executes_last_expression_value() {
433 let p = parse("2 + 2;").expect("parse");
435 assert!(!p.statements.is_empty());
436 let _ = run("2 + 2;").expect("run");
437 }
438
439 #[test]
440 fn run_propagates_parse_errors() {
441 assert!(run("sub f {").is_err());
442 }
443
444 #[test]
445 fn interpreter_scope_persists_global_scalar_across_execute_tree_calls() {
446 let mut interp = Interpreter::new();
447 let assign = parse("$persist_test = 100;").expect("parse assign");
448 interp.execute_tree(&assign).expect("assign");
449 let read = parse("$persist_test").expect("parse read");
450 let v = interp.execute_tree(&read).expect("read");
451 assert_eq!(v.to_int(), 100);
452 }
453
454 #[test]
455 fn parse_empty_program() {
456 let p = parse("").expect("empty input should parse");
457 assert!(p.statements.is_empty());
458 }
459
460 #[test]
461 fn parse_expression_statement() {
462 let p = parse("2 + 2;").expect("parse");
463 assert!(!p.statements.is_empty());
464 }
465
466 #[test]
467 fn parse_semicolon_only_statements() {
468 parse(";;;").expect("semicolons only");
469 }
470
471 #[test]
472 fn parse_subroutine_declaration() {
473 parse("sub foo { return 1; }").expect("sub");
474 }
475
476 #[test]
477 fn parse_if_with_block() {
478 parse("if (1) { 2 }").expect("if");
479 }
480
481 #[test]
482 fn parse_fails_on_invalid_syntax() {
483 assert!(parse("sub f {").is_err());
484 }
485
486 #[test]
487 fn parse_qw_word_list() {
488 parse("my @a = qw(x y z);").expect("qw list");
489 }
490
491 #[test]
492 fn parse_c_style_for_loop() {
493 parse("for (my $i = 0; $i < 3; $i = $i + 1) { 1; }").expect("c-style for");
494 }
495
496 #[test]
497 fn parse_package_statement() {
498 parse("package Foo::Bar; 1;").expect("package");
499 }
500
501 #[test]
502 fn parse_unless_block() {
503 parse("unless (0) { 1; }").expect("unless");
504 }
505
506 #[test]
507 fn parse_if_elsif_else() {
508 parse("if (0) { 1; } elsif (1) { 2; } else { 3; }").expect("if elsif");
509 }
510
511 #[test]
512 fn parse_q_constructor() {
513 parse(r#"my $s = q{braces};"#).expect("q{}");
514 parse(r#"my $t = qq(double);"#).expect("qq()");
515 }
516
517 #[test]
518 fn parse_regex_literals() {
519 parse("m/foo/;").expect("m//");
520 parse("s/foo/bar/g;").expect("s///");
521 }
522
523 #[test]
524 fn parse_begin_and_end_blocks() {
525 parse("BEGIN { 1; }").expect("BEGIN");
526 parse("END { 1; }").expect("END");
527 }
528
529 #[test]
530 fn parse_transliterate_y() {
531 parse("$_ = 'a'; y/a/A/;").expect("y//");
532 }
533
534 #[test]
535 fn parse_foreach_with_my_iterator() {
536 parse("foreach my $x (1, 2) { $x; }").expect("foreach my");
537 }
538
539 #[test]
540 fn parse_our_declaration() {
541 parse("our $g = 1;").expect("our");
542 }
543
544 #[test]
545 fn parse_local_declaration() {
546 parse("local $x = 1;").expect("local");
547 }
548
549 #[test]
550 fn parse_use_no_statements() {
551 parse("use strict;").expect("use");
552 parse("no warnings;").expect("no");
553 }
554
555 #[test]
556 fn parse_sub_with_prototype() {
557 parse("sub sum ($$) { return $_0 + $_1; }").expect("sub prototype");
558 parse("sub try (&;@) { my ( $try, @code_refs ) = @_; }").expect("prototype @ slurpy");
559 }
560
561 #[test]
562 fn parse_list_expression_in_parentheses() {
563 parse("my @a = (1, 2, 3);").expect("list");
564 }
565
566 #[test]
567 fn parse_require_expression() {
568 parse("require strict;").expect("require");
569 }
570
571 #[test]
572 fn parse_do_string_eval_form() {
573 parse(r#"do "foo.pl";"#).expect("do string");
574 }
575
576 #[test]
577 fn parse_package_qualified_name() {
578 parse("package Foo::Bar::Baz;").expect("package ::");
579 }
580
581 #[test]
582 fn parse_my_multiple_declarations() {
583 parse("my ($a, $b, $c);").expect("my list");
584 }
585
586 #[test]
587 fn parse_eval_block_statement() {
588 parse("eval { 1; };").expect("eval block");
589 }
590
591 #[test]
592 fn parse_say_statement() {
593 parse("say 42;").expect("say");
594 }
595
596 #[test]
597 fn parse_chop_scalar() {
598 parse("chop $s;").expect("chop");
599 }
600
601 #[test]
602 fn vendor_perl_inc_path_points_at_vendor_perl() {
603 let p = vendor_perl_inc_path();
604 assert!(
605 p.ends_with("vendor/perl"),
606 "unexpected vendor path: {}",
607 p.display()
608 );
609 }
610
611 #[test]
612 fn format_program_roundtrips_simple_expression() {
613 let p = parse("$x + 1;").expect("parse");
614 let out = format_program(&p);
615 assert!(!out.trim().is_empty());
616 }
617}
618
619#[cfg(test)]
620mod builtins_extended_tests;
621
622#[cfg(test)]
623mod lib_api_extended_tests;
624
625#[cfg(test)]
626mod parallel_api_tests;
627
628#[cfg(test)]
629mod parse_smoke_extended;
630
631#[cfg(test)]
632mod parse_smoke_batch2;
633
634#[cfg(test)]
635mod parse_smoke_batch3;
636
637#[cfg(test)]
638mod parse_smoke_batch4;
639
640#[cfg(test)]
641mod crate_api_tests;
642
643#[cfg(test)]
644mod parser_shape_tests;
645
646#[cfg(test)]
647mod interpreter_unit_tests;
648
649#[cfg(test)]
650mod run_semantics_tests;
651
652#[cfg(test)]
653mod run_semantics_more;