@tishlang/tish 1.10.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/tish CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "tishlang"
3
- version = "1.10.0"
3
+ version = "1.13.0"
4
4
  edition = "2021"
5
5
  description = "Tish CLI - run, REPL, compile to native"
6
6
  license-file = { workspace = true }
@@ -405,6 +405,8 @@ pub fn build_after_help() -> String {
405
405
  WebAssembly (.tish project; .js source supported on some paths)
406
406
  {t}wasi{r}
407
407
  WASI WebAssembly
408
+ {t}bytecode{r}
409
+ Raw serialized bytecode chunk (no VM binary/JS/HTML); for hosts that already ship the runtime
408
410
 
409
411
  {oh}Native backends{r} (--native-backend, only with --target native, default: rust):
410
412
  {t}rust{r}
@@ -506,7 +508,7 @@ pub(crate) struct BuildArgs {
506
508
  help_heading = "Options"
507
509
  )]
508
510
  pub output: String,
509
- /// `native`, `js`, `wasm`, or `wasi` (see long help below).
511
+ /// `native`, `js`, `wasm`, `wasi`, or `bytecode` (see long help below).
510
512
  #[arg(
511
513
  long,
512
514
  default_value = "native",
@@ -530,6 +532,12 @@ pub(crate) struct BuildArgs {
530
532
  help_heading = "Options"
531
533
  )]
532
534
  pub features: Vec<String>,
535
+ /// Cross-compile to an Apple iOS triple (e.g. `aarch64-apple-ios-sim`). Implies `--crate-type staticlib`.
536
+ #[arg(long, value_name = "TRIPLE", help_heading = "Options")]
537
+ pub ios_triple: Option<String>,
538
+ /// Output artifact for `--target native` (default: `bin`; use `staticlib` for embedded iOS).
539
+ #[arg(long, value_name = "TYPE", default_value = "bin", help_heading = "Options")]
540
+ pub crate_type: String,
533
541
  #[arg(long, help_heading = "Options")]
534
542
  pub no_optimize: bool,
535
543
  /// For `--target js` project builds: emit `OUTPUT.js.map` and `//# sourceMappingURL=…` so JS/TS tools can jump to original `.tish` (implies `--no-optimize` for that build).
@@ -111,6 +111,8 @@ fn main() {
111
111
  &a.features,
112
112
  a.no_optimize || no_opt_env,
113
113
  a.source_map,
114
+ a.ios_triple.as_deref(),
115
+ &a.crate_type,
114
116
  ),
115
117
  Some(Commands::DumpAst { file }) => dump_ast(&file),
116
118
  None => {
@@ -568,6 +570,8 @@ fn build_file(
568
570
  cli_features: &[String],
569
571
  no_optimize: bool,
570
572
  source_map: bool,
573
+ ios_triple: Option<&str>,
574
+ crate_type: &str,
571
575
  ) -> Result<(), String> {
572
576
  let optimize = !no_optimize;
573
577
  let input_path = Path::new(input_path)
@@ -623,9 +627,26 @@ fn build_file(
623
627
  .map_err(|e| e.to_string());
624
628
  }
625
629
 
630
+ if target == "bytecode" {
631
+ let project_root = input_path.parent().and_then(|p| {
632
+ if p.file_name().and_then(|n| n.to_str()) == Some("src") {
633
+ p.parent()
634
+ } else {
635
+ Some(p)
636
+ }
637
+ });
638
+ return tishlang_wasm::compile_to_bytecode(
639
+ &input_path,
640
+ project_root,
641
+ Path::new(output_path),
642
+ optimize,
643
+ )
644
+ .map_err(|e| e.to_string());
645
+ }
646
+
626
647
  if target != "native" {
627
648
  return Err(format!(
628
- "Unknown target: {}. Use 'native', 'js', 'wasm', or 'wasi'.",
649
+ "Unknown target: {}. Use 'native', 'js', 'wasm', 'wasi', or 'bytecode'.",
629
650
  target
630
651
  ));
631
652
  }
@@ -639,9 +660,31 @@ fn build_file(
639
660
  });
640
661
  let features: Vec<String> = native_build_features_from_cli(cli_features);
641
662
 
663
+ let build_config = if let Some(triple) = ios_triple {
664
+ tishlang_native::NativeBuildConfig::ios_staticlib(triple)
665
+ } else if crate_type == "staticlib" {
666
+ tishlang_native::NativeBuildConfig {
667
+ artifact: tishlang_native::NativeArtifact::StaticLib,
668
+ cargo_target: None,
669
+ emit_mode: tishlang_compile::NativeEmitMode::EmbeddedLib,
670
+ }
671
+ } else if crate_type != "bin" {
672
+ return Err(format!(
673
+ "Unknown --crate-type: {}. Use 'bin' or 'staticlib'.",
674
+ crate_type
675
+ ));
676
+ } else {
677
+ tishlang_native::NativeBuildConfig::desktop()
678
+ };
679
+
642
680
  if is_js {
643
681
  let source = fs::read_to_string(&input_path).map_err(|e| format!("{}", e))?;
644
682
  let program = tishlang_js_to_tish::convert(&source).map_err(|e| format!("{}", e))?;
683
+ if build_config.artifact != tishlang_native::NativeArtifact::Bin {
684
+ return Err(
685
+ "--crate-type staticlib / --ios-triple require a .tish entry file.".to_string(),
686
+ );
687
+ }
645
688
  tishlang_native::compile_program_to_native(
646
689
  &program,
647
690
  project_root,
@@ -652,13 +695,14 @@ fn build_file(
652
695
  )
653
696
  .map_err(|e| e.to_string())?;
654
697
  } else {
655
- tishlang_native::compile_to_native(
698
+ tishlang_native::compile_to_native_with_config(
656
699
  &input_path,
657
700
  project_root,
658
701
  Path::new(output_path),
659
702
  &features,
660
703
  native_backend,
661
704
  optimize,
705
+ &build_config,
662
706
  )
663
707
  .map_err(|e| e.to_string())?;
664
708
  }
@@ -667,7 +711,15 @@ fn build_file(
667
711
  .file_stem()
668
712
  .and_then(|s| s.to_str())
669
713
  .unwrap_or("tish_out");
670
- let built_path = if output_path.ends_with('/') || Path::new(output_path).is_dir() {
714
+ let built_path = if build_config.artifact == tishlang_native::NativeArtifact::StaticLib {
715
+ if output_path.ends_with('/') || Path::new(output_path).is_dir() {
716
+ Path::new(output_path).join(format!("lib{out_name}.a"))
717
+ } else if output_path.ends_with(".a") {
718
+ Path::new(output_path).to_path_buf()
719
+ } else {
720
+ Path::new(output_path).with_extension("a")
721
+ }
722
+ } else if output_path.ends_with('/') || Path::new(output_path).is_dir() {
671
723
  Path::new(output_path).join(out_name)
672
724
  } else {
673
725
  Path::new(output_path).to_path_buf()
@@ -338,6 +338,11 @@ pub fn find_crate_path(crate_name: &str) -> Result<PathBuf, String> {
338
338
  Ok(crate_path)
339
339
  }
340
340
 
341
+ /// Sanitize a user-chosen output stem for Cargo `[lib]` / `[[bin]]` target names.
342
+ pub fn cargo_target_name(stem: &str) -> String {
343
+ stem.replace('-', "_")
344
+ }
345
+
341
346
  /// Create a temp build directory with src subdir.
342
347
  pub fn create_build_dir(prefix: &str, out_name: &str) -> Result<PathBuf, String> {
343
348
  let build_dir =
@@ -380,8 +385,12 @@ fn protoc_for_nested_cargo() -> Option<PathBuf> {
380
385
  }
381
386
 
382
387
  /// Run cargo build in the given directory.
383
- /// If target_dir is Some, use that for --target-dir (e.g. workspace target for caching).
384
- pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<(), String> {
388
+ /// If `cross_target` is Some, passes `--target` and skips `-C target-cpu=native`.
389
+ pub fn run_cargo_build(
390
+ build_dir: &Path,
391
+ target_dir: Option<&Path>,
392
+ cross_target: Option<&str>,
393
+ ) -> Result<(), String> {
385
394
  let _nested_guard = NESTED_CARGO_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
386
395
 
387
396
  let target_dir = target_dir
@@ -389,11 +398,10 @@ pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<()
389
398
  .unwrap_or_else(|| build_dir.join("target"));
390
399
  let fast_native = std::env::var("TISH_FAST_NATIVE_BUILD").as_deref() == Ok("1");
391
400
 
392
- // Default to target-cpu=native so the emitted binary uses every SIMD / ISA
393
- // extension the build host supports. Callers can override by pre-setting
394
- // RUSTFLAGS in the environment. Skipped for fast nested builds (integration tests).
395
401
  let mut merged_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
396
- if fast_native {
402
+ if cross_target.is_some() {
403
+ // Cross-compiling (e.g. iOS): do not use host target-cpu.
404
+ } else if fast_native {
397
405
  if cfg!(target_os = "linux")
398
406
  && mold_available()
399
407
  && !merged_rustflags.contains("fuse-ld=mold")
@@ -407,10 +415,6 @@ pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<()
407
415
  merged_rustflags = format!("{} -C target-cpu=native", merged_rustflags);
408
416
  }
409
417
 
410
- // Nested `cargo build` (e.g. `tish build --native-backend rust`) inherits the parent
411
- // environment. CI often sets `RUSTC_WRAPPER=sccache`; wrapping this inner compile too can
412
- // cause flaky or failed builds (LTO / temp-crate paths). Use plain rustc here; the main
413
- // workspace build still benefits from the wrapper.
414
418
  let mut cmd = Command::new("cargo");
415
419
  cmd.args(["build", "--release", "--target-dir"])
416
420
  .arg(&target_dir)
@@ -421,6 +425,9 @@ pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<()
421
425
  .env_remove("CARGO_BUILD_RUSTC_WRAPPER")
422
426
  .env("CARGO_TERM_PROGRESS", "always")
423
427
  .env("RUSTFLAGS", &merged_rustflags);
428
+ if let Some(triple) = cross_target {
429
+ cmd.arg("--target").arg(triple);
430
+ }
424
431
  if fast_native {
425
432
  cmd.env("CARGO_INCREMENTAL", "1");
426
433
  } else {
@@ -448,6 +455,12 @@ pub fn run_cargo_build(build_dir: &Path, target_dir: Option<&Path>) -> Result<()
448
455
  mod protoc_tests {
449
456
  use super::*;
450
457
 
458
+ #[test]
459
+ fn cargo_target_name_replaces_hyphens() {
460
+ assert_eq!(cargo_target_name("hello-ios"), "hello_ios");
461
+ assert_eq!(cargo_target_name("tish_out"), "tish_out");
462
+ }
463
+
451
464
  #[test]
452
465
  fn protoc_for_nested_cargo_without_env_uses_vendored_or_path() {
453
466
  let _lock = std::sync::Mutex::new(());
@@ -458,6 +471,16 @@ mod protoc_tests {
458
471
  }
459
472
  }
460
473
 
474
+ /// Find the built static library in target/release (or target/$TRIPLE/release).
475
+ pub fn find_release_staticlib(binary_dir: &Path, lib_name: &str) -> Result<PathBuf, String> {
476
+ let path = binary_dir.join(format!("lib{lib_name}.a"));
477
+ if path.exists() {
478
+ Ok(path)
479
+ } else {
480
+ Err(format!("Static library not found at {}", path.display()))
481
+ }
482
+ }
483
+
461
484
  /// Find the built binary in target/release.
462
485
  pub fn find_release_binary(binary_dir: &Path, bin_name: &str) -> Result<PathBuf, String> {
463
486
  let binary_no_ext = binary_dir.join(bin_name);
@@ -80,3 +80,10 @@ pub fn hypot(args: &[Value]) -> Value {
80
80
  let y = extract_num(args.get(1)).unwrap_or(0.0);
81
81
  Value::Number(x.hypot(y))
82
82
  }
83
+
84
+ /// ES6 `Math.imul`: 32-bit integer multiply (used by xmur3 PRNG in juke-cards).
85
+ pub fn imul(args: &[Value]) -> Value {
86
+ let a = extract_num(args.first()).unwrap_or(0.0) as i32;
87
+ let b = extract_num(args.get(1)).unwrap_or(0.0) as i32;
88
+ Value::Number(a.wrapping_mul(b) as f64)
89
+ }
@@ -189,6 +189,32 @@ pub fn substring(s: &Value, start: &Value, end: &Value) -> Value {
189
189
  }
190
190
  }
191
191
 
192
+ /// JS `String.prototype.substr(start, length)`.
193
+ pub fn substr(s: &Value, start: &Value, length: &Value) -> Value {
194
+ if let Value::String(s) = s {
195
+ let chars: Vec<char> = s.chars().collect();
196
+ let len = chars.len();
197
+ let mut start_idx = match start {
198
+ Value::Number(n) => *n as i64,
199
+ _ => 0,
200
+ };
201
+ if start_idx < 0 {
202
+ start_idx = (len as i64 + start_idx).max(0);
203
+ }
204
+ let start_idx = (start_idx as usize).min(len);
205
+ let count = match length {
206
+ Value::Null => len - start_idx,
207
+ Value::Number(n) => (*n as i64).max(0) as usize,
208
+ _ => len - start_idx,
209
+ };
210
+ let end_idx = (start_idx + count).min(len);
211
+ let result: String = chars[start_idx..end_idx].iter().collect();
212
+ Value::String(result.into())
213
+ } else {
214
+ Value::Null
215
+ }
216
+ }
217
+
192
218
  pub fn split(s: &Value, sep: &Value) -> Value {
193
219
  if let Value::String(s) = s {
194
220
  let separator = match sep {