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 ai;
11pub mod ai_sugar;
12pub mod aop;
13pub mod aot;
14pub mod ast;
15pub mod banner;
16pub mod builtins;
17pub mod builtins_bio_geom_markov;
18pub mod builtins_bits_music_stats;
19pub mod builtins_combin_audio_physics;
20pub mod builtins_const;
21pub mod builtins_data;
22pub mod builtins_games_ml_chem;
23pub mod builtins_geom;
24pub mod builtins_github;
25pub mod builtins_iter;
26pub mod builtins_linalg_graph_date;
27pub mod builtins_mathx;
28pub mod builtins_misc;
29pub mod builtins_misc2;
30pub mod builtins_net;
31pub mod builtins_phonetic_geo_codec;
32pub mod builtins_quant;
33pub mod builtins_ratings_geom_units;
34pub mod builtins_sync;
35pub mod builtins_validate;
36pub mod builtins_vision_ir_algorithms;
37pub mod bytecode;
38pub mod capture;
39pub mod cli_runners;
40pub mod cluster;
41pub mod compiler;
42pub mod controller;
43pub mod convert;
44mod crypt_util;
45pub mod dap;
46pub mod data_section;
47pub mod debugger;
48pub mod deconvert;
49pub mod deparse;
50pub mod doc_render;
51pub mod docs;
52pub mod english;
53pub mod error;
54mod fib_like_tail;
55pub mod fmt;
56pub mod format;
57pub mod getopts;
58mod jit;
59mod jwt;
60pub mod kvstore;
61pub mod lexer;
62pub mod list_builtins;
63pub mod lsp;
64pub mod lsp_docs_domains;
65pub mod lsp_extras;
66pub mod lsp_symbols;
67mod map_grep_fast;
68mod map_stream;
69pub mod mcp;
70pub mod minify;
71pub mod mro;
72mod nanbox;
73mod native_codec;
74pub mod native_data;
75pub mod pack;
76pub mod par_lines;
77mod par_list;
78pub mod par_pipeline;
79pub mod par_walk;
80pub mod parallel_trace;
81pub mod parser;
82pub mod pcache;
83pub mod pchannel;
84mod pending_destroy;
85pub mod perf_recorder;
86pub mod perl_decode;
87pub mod perl_fs;
88pub mod perl_inc;
89#[cfg(unix)]
90pub mod perl_pty;
91mod perl_regex;
92pub mod perl_signal;
93pub mod pkg;
94mod pmap_progress;
95pub mod ppool;
96pub mod profiler;
97pub mod pwatch;
98pub mod remote_wire;
99pub mod rust_ffi;
100pub mod rust_sugar;
101pub mod scope;
102pub mod script_cache;
103pub mod secrets;
104pub mod serialize_normalize;
105pub mod sketches;
106mod sort_fast;
107pub mod special_vars;
108pub mod static_analysis;
109pub mod stress;
110pub mod stryke_log;
111pub mod token;
112pub mod value;
113pub mod vm;
114pub mod vm_helper;
115pub mod web;
116pub mod web_orm;
117
118pub use zsh::exec as shell_exec;
120pub use zsh::fds as shell_fds;
121pub use zsh::history as shell_history;
122pub use zsh::jobs as shell_jobs;
123pub use zsh::lex as zsh_lex;
124pub use zsh::parse as shell_parse;
125pub use zsh::parse as zsh_parse;
126pub use zsh::signals as shell_signal;
127pub use zsh::tokens as zsh_tokens;
128pub use zsh::utils::errflag as zsh_errflag;
129pub use zsh::zle as shell_zle;
130pub use zsh::zsh_h::ERRFLAG_ERROR;
131pub use zsh::zwc as shell_zwc;
132
133pub use vm_helper::{
134 perl_bracket_version, FEAT_SAY, FEAT_STATE, FEAT_SWITCH, FEAT_UNICODE_STRINGS,
135};
136
137use error::{StrykeError, StrykeResult};
138use vm_helper::VMHelper;
139
140use std::cell::Cell;
143use std::sync::atomic::{AtomicBool, Ordering};
144
145static COMPAT_MODE: AtomicBool = AtomicBool::new(false);
149
150static NO_INTEROP_DEFAULT: AtomicBool = AtomicBool::new(false);
153
154thread_local! {
155 static NO_INTEROP_TLS: Cell<Option<bool>> = const { Cell::new(None) };
161}
162
163static BIGINT_PRAGMA: AtomicBool = AtomicBool::new(false);
168
169pub fn set_compat_mode(on: bool) {
171 COMPAT_MODE.store(on, Ordering::Relaxed);
172}
173
174#[inline]
176pub fn compat_mode() -> bool {
177 COMPAT_MODE.load(Ordering::Relaxed)
178}
179
180pub fn set_bigint_pragma(on: bool) {
183 BIGINT_PRAGMA.store(on, Ordering::Relaxed);
184}
185
186#[inline]
188pub fn bigint_pragma() -> bool {
189 BIGINT_PRAGMA.load(Ordering::Relaxed)
190}
191
192pub fn set_no_interop_mode(on: bool) {
195 NO_INTEROP_DEFAULT.store(on, Ordering::Relaxed);
196}
197
198pub fn set_no_interop_mode_tls(value: Option<bool>) {
202 NO_INTEROP_TLS.with(|c| c.set(value));
203}
204
205pub fn no_interop_mode_tls() -> Option<bool> {
208 NO_INTEROP_TLS.with(|c| c.get())
209}
210
211#[inline]
214pub fn no_interop_mode() -> bool {
215 if let Some(v) = NO_INTEROP_TLS.with(|c| c.get()) {
216 return v;
217 }
218 NO_INTEROP_DEFAULT.load(Ordering::Relaxed)
219}
220use value::StrykeValue;
221
222pub fn format_program(p: &ast::Program) -> String {
225 fmt::format_program(p)
226}
227
228pub fn convert_to_stryke(p: &ast::Program) -> String {
230 convert::convert_program(p)
231}
232
233pub fn convert_to_stryke_with_options(p: &ast::Program, opts: &convert::ConvertOptions) -> String {
235 convert::convert_program_with_options(p, opts)
236}
237
238pub fn deconvert_to_perl(p: &ast::Program) -> String {
240 deconvert::deconvert_program(p)
241}
242
243pub fn deconvert_to_perl_with_options(
245 p: &ast::Program,
246 opts: &deconvert::DeconvertOptions,
247) -> String {
248 deconvert::deconvert_program_with_options(p, opts)
249}
250
251pub fn parse(code: &str) -> StrykeResult<ast::Program> {
252 parse_with_file(code, "-e")
253}
254
255pub fn parse_with_file(code: &str, file: &str) -> StrykeResult<ast::Program> {
258 parse_with_file_inner(code, file, false)
259}
260
261pub fn parse_module_with_file(code: &str, file: &str) -> StrykeResult<ast::Program> {
264 parse_with_file_inner(code, file, true)
265}
266
267fn parse_with_file_inner(code: &str, file: &str, is_module: bool) -> StrykeResult<ast::Program> {
268 let desugared = if compat_mode() {
272 code.to_string()
273 } else {
274 let s = rust_sugar::desugar_rust_blocks(code);
275 ai_sugar::desugar(&s)
276 };
277 let mut lexer = lexer::Lexer::new_with_file(&desugared, file);
278 let tokens = lexer.tokenize()?;
279 let bare_positional_indices = std::mem::take(&mut lexer.bare_positional_indices);
280 let mut parser = parser::Parser::new_with_file(tokens, file);
281 parser.bare_positional_indices = bare_positional_indices;
282 parser.parsing_module = is_module;
283 parser.parse_program()
284}
285
286pub fn parse_and_run_string(code: &str, interp: &mut VMHelper) -> StrykeResult<StrykeValue> {
290 let file = interp.file.clone();
291 parse_and_run_string_in_file(code, interp, &file)
292}
293
294pub fn parse_and_run_string_in_file(
297 code: &str,
298 interp: &mut VMHelper,
299 file: &str,
300) -> StrykeResult<StrykeValue> {
301 parse_and_run_string_in_file_inner(code, interp, file, false)
302}
303
304pub fn parse_and_run_module_in_file(
307 code: &str,
308 interp: &mut VMHelper,
309 file: &str,
310) -> StrykeResult<StrykeValue> {
311 parse_and_run_string_in_file_inner(code, interp, file, true)
312}
313
314fn parse_and_run_string_in_file_inner(
315 code: &str,
316 interp: &mut VMHelper,
317 file: &str,
318 is_module: bool,
319) -> StrykeResult<StrykeValue> {
320 let program = if is_module {
321 parse_module_with_file(code, file)?
322 } else {
323 parse_with_file(code, file)?
324 };
325 let saved = interp.file.clone();
326 interp.file = file.to_string();
327 let r = interp.execute(&program);
328 interp.file = saved;
329 let v = r?;
330 interp.drain_pending_destroys(0)?;
331 Ok(v)
332}
333
334pub fn vendor_perl_inc_path() -> std::path::PathBuf {
337 std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("vendor/perl")
338}
339
340pub fn run_lsp_stdio() -> i32 {
342 match lsp::run_stdio() {
343 Ok(()) => 0,
344 Err(e) => {
345 eprintln!("stryke --lsp: {e}");
346 1
347 }
348 }
349}
350
351pub fn run(code: &str) -> StrykeResult<StrykeValue> {
353 let program = parse(code)?;
354 let mut interp = VMHelper::new();
355 let v = interp.execute(&program)?;
356 interp.run_global_teardown()?;
357 Ok(v)
358}
359
360pub fn try_vm_execute(
368 program: &ast::Program,
369 interp: &mut VMHelper,
370) -> Option<StrykeResult<StrykeValue>> {
371 if let Err(e) = interp.prepare_program_top_level(program) {
372 return Some(Err(e));
373 }
374
375 if let Some(chunk) = interp.cached_chunk.take() {
378 return Some(run_compiled_chunk(chunk, interp));
379 }
380
381 let comp = compiler::Compiler::new()
386 .with_source_file(interp.file.clone())
387 .with_strict_vars(interp.strict_vars);
388 let chunk = match comp.compile_program(program) {
389 Ok(chunk) => chunk,
390 Err(compiler::CompileError::Frozen { line, detail }) => {
391 let err = if detail.starts_with("Global symbol") {
392 StrykeError::syntax(detail, line)
393 } else {
394 StrykeError::runtime(detail, line)
395 };
396 return Some(Err(err));
397 }
398 Err(compiler::CompileError::Unsupported(reason)) => {
399 return Some(Err(StrykeError::runtime(
400 format!("VM compile error (unsupported): {}", reason),
401 0,
402 )));
403 }
404 };
405
406 if let Some(path) = interp.cache_script_path.take() {
408 let _ = script_cache::try_save(&path, program, &chunk);
409 }
410 Some(run_compiled_chunk(chunk, interp))
411}
412
413fn run_compiled_chunk(chunk: bytecode::Chunk, interp: &mut VMHelper) -> StrykeResult<StrykeValue> {
417 interp.clear_flip_flop_state();
418 interp.prepare_flip_flop_vm_slots(chunk.flip_flop_slots);
419 if interp.disasm_bytecode {
420 eprintln!("{}", chunk.disassemble());
421 }
422 interp.clear_begin_end_blocks_after_vm_compile();
423 for def in &chunk.struct_defs {
424 interp
425 .struct_defs
426 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
427 }
428 for def in &chunk.enum_defs {
429 interp
430 .enum_defs
431 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
432 }
433 for def in &chunk.trait_defs {
435 interp
436 .trait_defs
437 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
438 }
439 for def in &chunk.class_defs {
440 let mut def = def.clone();
441 for parent_name in &def.extends.clone() {
443 if let Some(parent_def) = interp.class_defs.get(parent_name) {
444 if parent_def.is_final {
445 return Err(crate::error::StrykeError::runtime(
446 format!("cannot extend final class `{}`", parent_name),
447 0,
448 ));
449 }
450 for m in &def.methods {
451 if let Some(parent_method) = parent_def.method(&m.name) {
452 if parent_method.is_final {
453 return Err(crate::error::StrykeError::runtime(
454 format!(
455 "cannot override final method `{}` from class `{}`",
456 m.name, parent_name
457 ),
458 0,
459 ));
460 }
461 }
462 }
463 }
464 }
465 for trait_name in &def.implements.clone() {
467 if let Some(trait_def) = interp.trait_defs.get(trait_name) {
468 for required in trait_def.required_methods() {
469 let has_method = def.methods.iter().any(|m| m.name == required.name);
470 if !has_method {
471 return Err(crate::error::StrykeError::runtime(
472 format!(
473 "class `{}` implements trait `{}` but does not define required method `{}`",
474 def.name, trait_name, required.name
475 ),
476 0,
477 ));
478 }
479 }
480 for tm in &trait_def.methods {
482 if tm.body.is_some() && !def.methods.iter().any(|m| m.name == tm.name) {
483 def.methods.push(tm.clone());
484 }
485 }
486 }
487 }
488 if !def.is_abstract {
491 for parent_name in &def.extends.clone() {
492 if let Some(parent_def) = interp.class_defs.get(parent_name) {
493 if parent_def.is_abstract {
494 for m in &parent_def.methods {
495 if m.body.is_none() && !def.methods.iter().any(|dm| dm.name == m.name) {
496 return Err(crate::error::StrykeError::runtime(
497 format!(
498 "class `{}` must implement abstract method `{}` from `{}`",
499 def.name, m.name, parent_name
500 ),
501 0,
502 ));
503 }
504 }
505 }
506 }
507 }
508 }
509 for sf in &def.static_fields {
511 let val = if let Some(ref expr) = sf.default {
512 match interp.eval_expr(expr) {
513 Ok(v) => v,
514 Err(crate::vm_helper::FlowOrError::Error(e)) => return Err(e),
515 Err(_) => crate::value::StrykeValue::UNDEF,
516 }
517 } else {
518 crate::value::StrykeValue::UNDEF
519 };
520 let key = format!("{}::{}", def.name, sf.name);
521 interp.scope.declare_scalar(&key, val);
522 }
523 for m in &def.methods {
525 if let Some(ref body) = m.body {
526 let fq = format!("{}::{}", def.name, m.name);
527 let sub = std::sync::Arc::new(crate::value::StrykeSub {
528 name: fq.clone(),
529 params: m.params.clone(),
530 body: body.clone(),
531 closure_env: None,
532 prototype: None,
533 fib_like: None,
534 });
535 interp.subs.insert(fq, sub);
536 }
537 }
538 if !def.extends.is_empty() {
540 let isa_key = format!("{}::ISA", def.name);
541 let parents: Vec<crate::value::StrykeValue> = def
542 .extends
543 .iter()
544 .map(|p| crate::value::StrykeValue::string(p.clone()))
545 .collect();
546 interp.scope.declare_array(&isa_key, parents);
547 }
548 interp
549 .class_defs
550 .insert(def.name.clone(), std::sync::Arc::new(def));
551 }
552 let vm_jit = interp.vm_jit_enabled && interp.profiler.is_none();
553 let mut vm = vm::VM::new(&chunk, interp);
554 vm.set_jit_enabled(vm_jit);
555 match vm.execute() {
556 Ok(val) => {
557 interp.drain_pending_destroys(0)?;
558 Ok(val)
559 }
560 Err(e)
565 if e.message.starts_with("VM: unimplemented op")
566 || e.message.starts_with("Unimplemented builtin") =>
567 {
568 Err(StrykeError::runtime(e.message, 0))
569 }
570 Err(e) => Err(e),
571 }
572}
573
574pub fn compile_and_run_prelude(program: &ast::Program, interp: &mut VMHelper) -> StrykeResult<()> {
577 interp.prepare_program_top_level(program)?;
578 let comp = compiler::Compiler::new()
579 .with_source_file(interp.file.clone())
580 .with_strict_vars(interp.strict_vars);
581 let mut chunk = match comp.compile_program(program) {
582 Ok(chunk) => chunk,
583 Err(compiler::CompileError::Frozen { line, detail }) => {
584 let err = if detail.starts_with("Global symbol") {
585 StrykeError::syntax(detail, line)
586 } else {
587 StrykeError::runtime(detail, line)
588 };
589 return Err(err);
590 }
591 Err(compiler::CompileError::Unsupported(reason)) => {
592 return Err(StrykeError::runtime(
593 format!("VM compile error (unsupported): {}", reason),
594 0,
595 ));
596 }
597 };
598
599 interp.clear_flip_flop_state();
600 interp.prepare_flip_flop_vm_slots(chunk.flip_flop_slots);
601 if interp.disasm_bytecode {
602 eprintln!("{}", chunk.disassemble());
603 }
604 interp.clear_begin_end_blocks_after_vm_compile();
605 for def in &chunk.struct_defs {
606 interp
607 .struct_defs
608 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
609 }
610 for def in &chunk.enum_defs {
611 interp
612 .enum_defs
613 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
614 }
615 for def in &chunk.trait_defs {
616 interp
617 .trait_defs
618 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
619 }
620 for def in &chunk.class_defs {
621 interp
622 .class_defs
623 .insert(def.name.clone(), std::sync::Arc::new(def.clone()));
624 }
625 for def in &chunk.class_defs {
627 for m in &def.methods {
628 if let Some(ref body) = m.body {
629 let fq = format!("{}::{}", def.name, m.name);
630 let sub = std::sync::Arc::new(crate::value::StrykeSub {
631 name: fq.clone(),
632 params: m.params.clone(),
633 body: body.clone(),
634 closure_env: None,
635 prototype: None,
636 fib_like: None,
637 });
638 interp.subs.insert(fq, sub);
639 }
640 }
641 }
642
643 let body_ip = chunk.body_start_ip;
644 if body_ip > 0 && body_ip < chunk.ops.len() {
645 let saved_op = chunk.ops[body_ip].clone();
647 chunk.ops[body_ip] = bytecode::Op::Halt;
648 let vm_jit = interp.vm_jit_enabled && interp.profiler.is_none();
649 let mut vm = vm::VM::new(&chunk, interp);
650 vm.set_jit_enabled(vm_jit);
651 let _ = vm.execute()?;
652 chunk.ops[body_ip] = saved_op;
653 }
654
655 interp.line_mode_chunk = Some(chunk);
656 Ok(())
657}
658
659pub fn run_line_body(
662 chunk: &bytecode::Chunk,
663 interp: &mut VMHelper,
664 line_str: &str,
665 is_last_input_line: bool,
666) -> StrykeResult<Option<String>> {
667 interp.line_mode_eof_pending = is_last_input_line;
668 let result: StrykeResult<Option<String>> = (|| {
669 interp.line_number += 1;
670 interp
671 .scope
672 .set_topic(value::StrykeValue::string(line_str.to_string()));
673
674 if interp.auto_split {
675 let sep = interp.field_separator.as_deref().unwrap_or(" ");
676 let re = regex::Regex::new(sep).unwrap_or_else(|_| regex::Regex::new(" ").unwrap());
677 let fields: Vec<value::StrykeValue> = re
678 .split(line_str)
679 .map(|s| value::StrykeValue::string(s.to_string()))
680 .collect();
681 interp.scope.set_array("F", fields)?;
682 }
683
684 let vm_jit = interp.vm_jit_enabled && interp.profiler.is_none();
685 let mut vm = vm::VM::new(chunk, interp);
686 vm.set_jit_enabled(vm_jit);
687 vm.ip = chunk.body_start_ip;
688 let _ = vm.execute()?;
689
690 let mut out = interp.scope.get_scalar("_").to_string();
691 out.push_str(&interp.ors);
692 Ok(Some(out))
693 })();
694 interp.line_mode_eof_pending = false;
695 result
696}
697
698pub fn lint_program(program: &ast::Program, interp: &mut VMHelper) -> StrykeResult<()> {
701 interp.prepare_program_top_level(program)?;
702 static_analysis::analyze_program_with_strict(program, &interp.file, interp.strict_vars)?;
708 if interp.strict_refs || interp.strict_subs || interp.strict_vars {
709 return Ok(());
710 }
711 let comp = compiler::Compiler::new().with_source_file(interp.file.clone());
712 match comp.compile_program(program) {
713 Ok(_) => Ok(()),
714 Err(e) => Err(compile_error_to_perl(e)),
715 }
716}
717
718fn compile_error_to_perl(e: compiler::CompileError) -> StrykeError {
719 match e {
720 compiler::CompileError::Unsupported(msg) => {
721 StrykeError::runtime(format!("compile: {}", msg), 0)
722 }
723 compiler::CompileError::Frozen { line, detail } => {
724 if detail.starts_with("Global symbol") {
729 StrykeError::syntax(detail, line)
730 } else {
731 StrykeError::runtime(detail, line)
732 }
733 }
734 }
735}
736
737#[cfg(test)]
738mod tests {
739 use super::*;
740
741 #[test]
742 fn run_executes_last_expression_value() {
743 let p = parse("2 + 2").expect("parse");
745 assert!(!p.statements.is_empty());
746 let _ = run("2 + 2").expect("run");
747 }
748
749 #[test]
750 fn run_propagates_parse_errors() {
751 assert!(run("sub f {").is_err());
752 }
753
754 #[test]
755 fn interpreter_scope_persists_global_scalar_across_execute_calls() {
756 let mut interp = VMHelper::new();
757 let assign = parse("$persist_test = 100").expect("parse assign");
758 interp.execute(&assign).expect("assign");
759 let read = parse("$persist_test").expect("parse read");
760 let v = interp.execute(&read).expect("read");
761 assert_eq!(v.to_int(), 100);
762 }
763
764 #[test]
765 fn parse_empty_program() {
766 let p = parse("").expect("empty input should parse");
767 assert!(p.statements.is_empty());
768 }
769
770 #[test]
771 fn parse_expression_statement() {
772 let p = parse("2 + 2").expect("parse");
773 assert!(!p.statements.is_empty());
774 }
775
776 #[test]
777 fn parse_semicolon_only_statements() {
778 parse(";;").expect("semicolons only");
779 }
780
781 #[test]
782 fn parse_if_with_block() {
783 parse("if (1) { 2 }").expect("if");
784 }
785
786 #[test]
787 fn parse_fails_on_invalid_syntax() {
788 assert!(parse("sub f {").is_err());
789 }
790
791 #[test]
792 fn parse_qw_word_list() {
793 parse("my @a = qw(x y z)").expect("qw list");
794 }
795
796 #[test]
797 fn parse_c_style_for_loop() {
798 parse("for (my $i = 0; $i < 3; $i = $i + 1) { 1; }").expect("c-style for");
799 }
800
801 #[test]
802 fn parse_package_statement() {
803 parse("package Foo::Bar; 1").expect("package");
804 }
805
806 #[test]
807 fn parse_unless_block() {
808 parse("unless (0) { 1; }").expect("unless");
809 }
810
811 #[test]
812 fn parse_if_elsif_else() {
813 parse("if (0) { 1; } elsif (1) { 2; } else { 3; }").expect("if elsif");
814 }
815
816 #[test]
817 fn parse_q_constructor() {
818 parse(r#"my $s = q{braces}"#).expect("q{}");
819 parse(r#"my $t = qq(double)"#).expect("qq()");
820 }
821
822 #[test]
823 fn parse_regex_literals() {
824 parse("m/foo/").expect("m//");
825 parse("s/foo/bar/g").expect("s///");
826 }
827
828 #[test]
829 fn parse_begin_and_end_blocks() {
830 parse("BEGIN { 1; }").expect("BEGIN");
831 parse("END { 1; }").expect("END");
832 }
833
834 #[test]
835 fn parse_transliterate_y() {
836 parse("$_ = 'a'; y/a/A/").expect("y//");
837 }
838
839 #[test]
840 fn parse_foreach_with_my_iterator() {
841 parse("foreach my $x (1, 2) { $x; }").expect("foreach my");
842 }
843
844 #[test]
845 fn parse_our_declaration() {
846 parse("our $g = 1").expect("our");
847 }
848
849 #[test]
850 fn parse_local_declaration() {
851 parse("local $x = 1").expect("local");
852 }
853
854 #[test]
855 fn parse_use_no_statements() {
856 parse("use strict").expect("use");
857 parse("no warnings").expect("no");
858 }
859
860 #[test]
861 fn parse_sub_with_prototype() {
862 parse("fn add2 ($$) { return $_0 + $_1; }").expect("fn prototype");
863 parse("fn try_block (&;@) { my ( $try, @code_refs ) = @_; }").expect("prototype @ slurpy");
864 }
865
866 #[test]
867 fn parse_list_expression_in_parentheses() {
868 parse("my @a = (1, 2, 3)").expect("list");
869 }
870
871 #[test]
872 fn parse_require_expression() {
873 parse("require strict").expect("require");
874 }
875
876 #[test]
877 fn parse_do_string_eval_form() {
878 parse(r#"do "foo.pl""#).expect("do string");
879 }
880
881 #[test]
882 fn parse_package_qualified_name() {
883 parse("package Foo::Bar::Baz").expect("package ::");
884 }
885
886 #[test]
887 fn parse_my_multiple_declarations() {
888 parse("my ($a, $b, $c)").expect("my list");
889 }
890
891 #[test]
892 fn parse_eval_block_statement() {
893 parse("eval { 1; }").expect("eval block");
894 }
895
896 #[test]
897 fn parse_p_statement() {
898 parse("p 42").expect("p");
899 }
900
901 #[test]
902 fn parse_chop_scalar() {
903 parse("chop $s").expect("chop");
904 }
905
906 #[test]
907 fn vendor_perl_inc_path_points_at_vendor_perl() {
908 let p = vendor_perl_inc_path();
909 assert!(
910 p.ends_with("vendor/perl"),
911 "unexpected vendor path: {}",
912 p.display()
913 );
914 }
915
916 #[test]
917 fn format_program_roundtrips_simple_expression() {
918 let p = parse("$x + 1").expect("parse");
919 let out = format_program(&p);
920 assert!(!out.trim().is_empty());
921 }
922}
923
924#[cfg(test)]
925mod builtins_extended_tests;
926
927#[cfg(test)]
928mod lib_api_extended_tests;
929
930#[cfg(test)]
931mod parallel_api_tests;
932
933#[cfg(test)]
934mod parse_smoke_extended;
935
936#[cfg(test)]
937mod parse_smoke_batch2;
938
939#[cfg(test)]
940mod parse_smoke_batch3;
941
942#[cfg(test)]
943mod parse_smoke_batch4;
944
945#[cfg(test)]
946mod crate_api_tests;
947
948#[cfg(test)]
949mod parser_shape_tests;
950
951#[cfg(test)]
952mod interpreter_unit_tests;
953
954#[cfg(test)]
955mod run_semantics_tests;
956
957#[cfg(test)]
958mod run_semantics_more;
959
960#[cfg(test)]
961mod value_extra_tests;
962
963#[cfg(test)]
964mod lexer_extra_tests;
965
966#[cfg(test)]
967mod parser_extra_tests;
968
969#[cfg(test)]
970mod builtins_extra_tests;
971
972#[cfg(test)]
973mod keywords_hash_tests;
974
975#[cfg(test)]
976mod thread_extra_tests;
977
978#[cfg(test)]
979mod error_extra_tests;
980
981#[cfg(test)]
982mod oo_extra_tests;
983
984#[cfg(test)]
985mod regex_extra_tests;
986
987#[cfg(test)]
988mod aot_extra_tests;
989
990#[cfg(test)]
991mod builtins_sync_tests;