Skip to content

Commit c32122f

Browse files
committed
playground: fix print function arity
1 parent 36b7e31 commit c32122f

5 files changed

Lines changed: 174 additions & 28 deletions

File tree

pd-vm/pd-vm-runtime-wasm/src/lib.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,50 @@ mod tests {
787787
);
788788
}
789789

790+
#[test]
791+
fn run_supports_multi_arg_print_for_javascript() {
792+
let source = r#"
793+
print(1, 2);
794+
1;
795+
"#;
796+
let report = run_source_with_flavor(source, SourceFlavor::JavaScript);
797+
assert!(
798+
report.error.is_none(),
799+
"expected run to succeed with multi-arg print, got {:?}",
800+
report.error
801+
);
802+
assert!(
803+
report.output.iter().any(|line| line == "1 2"),
804+
"expected output to include joined print line, got {:?}",
805+
report.output
806+
);
807+
}
808+
809+
#[test]
810+
fn run_supports_mixed_print_call_arities_for_rustscript() {
811+
let source = r#"
812+
print(1);
813+
print("{}", 2);
814+
1;
815+
"#;
816+
let report = run_source_with_flavor(source, SourceFlavor::RustScript);
817+
assert!(
818+
report.error.is_none(),
819+
"expected run to succeed with mixed print arities, got {:?}",
820+
report.error
821+
);
822+
assert!(
823+
report.output.iter().any(|line| line == "1"),
824+
"expected output to include first print line, got {:?}",
825+
report.output
826+
);
827+
assert!(
828+
report.output.iter().any(|line| line == "2"),
829+
"expected output to include formatted print line, got {:?}",
830+
report.output
831+
);
832+
}
833+
790834
#[test]
791835
fn completion_catalog_reports_stdlib_and_host_entries() {
792836
let catalog = build_completion_catalog();

pd-vm/pd-vm-runtime-wasm/src/runtime.rs

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ use std::time::Instant;
1111

1212
use serde::Deserialize;
1313
use vm::{
14-
CallOutcome, FunctionDecl, HostAsyncBridge, HostFunction, HostOpId, LocalInfo,
15-
PrintHostFunction, PrintlnHostFunction, SourceFlavor, SourcePathError, Value, Vm, VmError,
16-
VmResult, VmStatus, VmYieldReason, compile_source_with_flavor_and_options, format_value,
17-
render_vm_error,
14+
CallOutcome, FunctionDecl, HostAsyncBridge, HostFunction, HostOpId, LocalInfo, SourceFlavor,
15+
SourcePathError, Value, Vm, VmError, VmResult, VmStatus, VmYieldReason,
16+
compile_source_with_flavor_and_options, format_value, render_vm_error,
1817
};
1918

2019
use crate::analyzer::{LintDiagnostic, lint_source_with_flavor};
@@ -1342,6 +1341,10 @@ fn register_functions(
13421341
functions: &[FunctionDecl],
13431342
print_output: &Arc<Mutex<Vec<String>>>,
13441343
) -> Result<(), String> {
1344+
let lines = Arc::clone(print_output);
1345+
vm.set_runtime_print_sink(move |rendered| {
1346+
push_output_line(&lines, rendered);
1347+
});
13451348
let async_state = functions
13461349
.iter()
13471350
.any(|decl| decl.name == "runtime::sleep")
@@ -1351,36 +1354,18 @@ fn register_functions(
13511354
state
13521355
});
13531356
for decl in functions {
1354-
register_named_function(vm, &decl.name, print_output, async_state.as_ref())?;
1357+
register_named_function(vm, &decl.name, async_state.as_ref())?;
13551358
}
13561359
Ok(())
13571360
}
13581361

13591362
fn register_named_function(
13601363
vm: &mut Vm,
13611364
name: &str,
1362-
print_output: &Arc<Mutex<Vec<String>>>,
13631365
async_state: Option<&Arc<Mutex<BrowserAsyncState>>>,
13641366
) -> Result<(), String> {
13651367
match name {
1366-
"print" => {
1367-
let lines = Arc::clone(print_output);
1368-
vm.bind_function(
1369-
"print",
1370-
Box::new(PrintHostFunction::new(move |rendered| {
1371-
push_output_line(&lines, rendered);
1372-
})),
1373-
);
1374-
}
1375-
"println" => {
1376-
let lines = Arc::clone(print_output);
1377-
vm.bind_function(
1378-
"println",
1379-
Box::new(PrintlnHostFunction::new(move |rendered| {
1380-
push_output_line(&lines, rendered);
1381-
})),
1382-
);
1383-
}
1368+
"print" | "println" => {}
13841369
"runtime::sleep" => {
13851370
let Some(state) = async_state else {
13861371
return Err("runtime::sleep async bridge not initialized".to_string());

pd-vm/src/compiler/parser.rs

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,8 +1890,25 @@ impl Parser {
18901890
let local = self.get_local(&name)?;
18911891
Expr::LocalCall(local, args)
18921892
} else if self.functions.contains_key(&name) {
1893-
let decl = self.resolve_function_for_call(&name, args.len())?;
1894-
Expr::Call(decl.index, args)
1893+
let builtin_alias_call = if matches!(name.as_str(), "print" | "println") {
1894+
self.functions
1895+
.get(&name)
1896+
.map(|decl| !self.function_impls.contains_key(&decl.index))
1897+
.unwrap_or(false)
1898+
} else {
1899+
false
1900+
};
1901+
if builtin_alias_call {
1902+
if let Some(expr) = self.try_build_language_builtin_call(&name, &args)? {
1903+
expr
1904+
} else {
1905+
let decl = self.resolve_function_for_call(&name, args.len())?;
1906+
Expr::Call(decl.index, args)
1907+
}
1908+
} else {
1909+
let decl = self.resolve_function_for_call(&name, args.len())?;
1910+
Expr::Call(decl.index, args)
1911+
}
18951912
} else if let Some(expr) = self.try_build_language_builtin_call(&name, &args)? {
18961913
expr
18971914
} else if let Some(host_name) = self.resolve_direct_host_call_target(&name) {
@@ -2640,9 +2657,11 @@ impl Parser {
26402657
"print" if self.dialect.allow_macro_calls() => {
26412658
Ok(Some(self.lower_print_call(args.to_vec())?))
26422659
}
2660+
"print" => Ok(Some(self.lower_plain_print_call(args.to_vec())?)),
26432661
"println" if self.dialect.allow_macro_calls() => {
26442662
Ok(Some(self.lower_println_call(args.to_vec())?))
26452663
}
2664+
"println" => Ok(Some(self.lower_plain_println_call(args.to_vec())?)),
26462665
"type" | "typeof" => {
26472666
if args.len() != 1 {
26482667
return Err(ParseError {
@@ -2694,6 +2713,11 @@ impl Parser {
26942713
self.build_print_call_expr(rendered)
26952714
}
26962715

2716+
fn lower_plain_print_call(&mut self, args: Vec<Expr>) -> Result<Expr, ParseError> {
2717+
let rendered = self.render_plain_print_args(args)?;
2718+
self.build_print_call_expr(rendered)
2719+
}
2720+
26972721
fn lower_println_call(&mut self, args: Vec<Expr>) -> Result<Expr, ParseError> {
26982722
let rendered = match args.as_slice() {
26992723
[] => Expr::String("\n".to_string()),
@@ -2714,6 +2738,42 @@ impl Parser {
27142738
self.build_print_call_expr(rendered)
27152739
}
27162740

2741+
fn lower_plain_println_call(&mut self, args: Vec<Expr>) -> Result<Expr, ParseError> {
2742+
let rendered = match args.is_empty() {
2743+
true => Expr::String("\n".to_string()),
2744+
false => {
2745+
let rendered = self.render_plain_print_args(args)?;
2746+
let value = self.build_to_string_expr(rendered)?;
2747+
self.append_newline_expr(value)
2748+
}
2749+
};
2750+
self.build_print_call_expr(rendered)
2751+
}
2752+
2753+
fn render_plain_print_args(&mut self, args: Vec<Expr>) -> Result<Expr, ParseError> {
2754+
match args.len() {
2755+
0 => Ok(Expr::String(String::new())),
2756+
1 => Ok(args
2757+
.into_iter()
2758+
.next()
2759+
.expect("single print arg should exist")),
2760+
_ => {
2761+
let mut args = args.into_iter();
2762+
let mut rendered =
2763+
self.build_to_string_expr(args.next().expect("first print arg should exist"))?;
2764+
for arg in args {
2765+
rendered =
2766+
Expr::Add(Box::new(rendered), Box::new(Expr::String(" ".to_string())));
2767+
rendered = Expr::Add(
2768+
Box::new(rendered),
2769+
Box::new(self.build_to_string_expr(arg)?),
2770+
);
2771+
}
2772+
Ok(rendered)
2773+
}
2774+
}
2775+
}
2776+
27172777
fn expect_format_literal<'a>(
27182778
&self,
27192779
callee: &str,
@@ -3126,8 +3186,9 @@ impl Parser {
31263186
let mut args = self.parse_call_args()?;
31273187

31283188
if base == "console" && segments.len() == 1 && segments[0] == "log" {
3129-
let decl = self.resolve_function_for_call(STDLIB_PRINT_NAME, args.len())?;
3130-
return Ok(Some(Expr::Call(decl.index, args)));
3189+
return Ok(Some(
3190+
self.lower_plain_print_call(std::mem::take(&mut args))?,
3191+
));
31313192
}
31323193

31333194
if segments.is_empty() {

pd-vm/tests/compiler_javascript_tests.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,43 @@ fn javascript_console_log_works_without_decl() {
344344
run_runtime_case_with_bindings(&case, &bindings);
345345
}
346346

347+
#[test]
348+
fn javascript_print_supports_multiple_arguments_without_decl() {
349+
let case = RuntimeCase {
350+
name: "print supports multiple arguments without decl",
351+
source: r#"
352+
print(40, 2);
353+
"#,
354+
flavor: SourceFlavor::JavaScript,
355+
expected_stack: vec![Value::string("40 2")],
356+
expected_locals: None,
357+
};
358+
let bindings = [HostBindingCase {
359+
name: "print",
360+
factory: make_print_builtin,
361+
}];
362+
run_runtime_case_with_bindings(&case, &bindings);
363+
}
364+
365+
#[test]
366+
fn javascript_print_alias_handles_mixed_call_arities() {
367+
let case = RuntimeCase {
368+
name: "print alias handles mixed call arities",
369+
source: r#"
370+
print(1);
371+
print(2, 3);
372+
"#,
373+
flavor: SourceFlavor::JavaScript,
374+
expected_stack: vec![Value::Int(1), Value::string("2 3")],
375+
expected_locals: None,
376+
};
377+
let bindings = [HostBindingCase {
378+
name: "print",
379+
factory: make_print_builtin,
380+
}];
381+
run_runtime_case_with_bindings(&case, &bindings);
382+
}
383+
347384
#[test]
348385
fn compile_source_file_with_javascript_complex_fixture() {
349386
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/example_complex.js");

pd-vm/tests/compiler_rustscript_tests.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,25 @@ fn rustscript_println_function_supports_rust_style_formatting() {
467467
run_runtime_case_with_bindings(&case, &bindings);
468468
}
469469

470+
#[test]
471+
fn rustscript_print_alias_handles_mixed_call_arities() {
472+
let case = RuntimeCase {
473+
name: "print alias handles mixed call arities",
474+
source: r#"
475+
print(1);
476+
print("{}", 2);
477+
"#,
478+
flavor: SourceFlavor::RustScript,
479+
expected_stack: vec![Value::Int(1), Value::string("2")],
480+
expected_locals: None,
481+
};
482+
let bindings = [HostBindingCase {
483+
name: "print",
484+
factory: make_print_builtin,
485+
}];
486+
run_runtime_case_with_bindings(&case, &bindings);
487+
}
488+
470489
#[test]
471490
fn rustscript_print_rejects_non_literal_format_string() {
472491
let case = ParseErrorCase {

0 commit comments

Comments
 (0)