@tishlang/tish-lsp 2.39.0 → 3.0.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.
Files changed (89) hide show
  1. package/Cargo.toml +4 -1
  2. package/bin/tish-lsp +0 -0
  3. package/crates/tish/src/cli_help.rs +28 -0
  4. package/crates/tish/src/main.rs +41 -5
  5. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +21 -0
  6. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/tish.d.tish +4 -0
  7. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +5 -1
  8. package/crates/tish/tests/regr_typed_externs.rs +59 -0
  9. package/crates/tish/tests/scope_merge.rs +79 -0
  10. package/crates/tish_build_utils/src/lib.rs +27 -1
  11. package/crates/tish_builtins/Cargo.toml +14 -6
  12. package/crates/tish_builtins/src/array.rs +130 -54
  13. package/crates/tish_builtins/src/collections.rs +9 -5
  14. package/crates/tish_builtins/src/construct.rs +7 -1
  15. package/crates/tish_builtins/src/date.rs +15 -1
  16. package/crates/tish_builtins/src/globals.rs +65 -3
  17. package/crates/tish_builtins/src/helpers.rs +7 -3
  18. package/crates/tish_builtins/src/iterator.rs +5 -1
  19. package/crates/tish_builtins/src/lib.rs +3 -0
  20. package/crates/tish_builtins/src/math.rs +14 -0
  21. package/crates/tish_builtins/src/number.rs +72 -6
  22. package/crates/tish_builtins/src/object.rs +6 -1
  23. package/crates/tish_builtins/src/string.rs +13 -2
  24. package/crates/tish_builtins/src/symbol.rs +17 -17
  25. package/crates/tish_builtins/src/typedarrays.rs +9 -2
  26. package/crates/tish_bytecode/src/compiler.rs +76 -2
  27. package/crates/tish_bytecode/src/lib.rs +1 -1
  28. package/crates/tish_bytecode/src/opcode.rs +77 -2
  29. package/crates/tish_compile/src/codegen.rs +2154 -225
  30. package/crates/tish_compile/src/infer.rs +37 -41
  31. package/crates/tish_compile/src/lib.rs +13 -1
  32. package/crates/tish_compile/src/platform_resolve.rs +361 -0
  33. package/crates/tish_compile/src/resolve.rs +195 -11
  34. package/crates/tish_compile/src/schemes.rs +333 -0
  35. package/crates/tish_compile/src/types.rs +205 -34
  36. package/crates/tish_compile/tests/perf_codegen_320.rs +2 -2
  37. package/crates/tish_compile/tests/perf_codegen_module_const_forof.rs +3 -1
  38. package/crates/tish_compile/tests/platform_resolve_cli.rs +164 -0
  39. package/crates/tish_compile/tests/regr_556_param_name_collision.rs +40 -0
  40. package/crates/tish_compile/tests/regr_558_struct_array_write.rs +65 -0
  41. package/crates/tish_compile/tests/regr_558b_struct_field_read.rs +21 -0
  42. package/crates/tish_compile/tests/regr_558c_wrapped_struct_array.rs +64 -0
  43. package/crates/tish_compile/tests/regr_558d_native_vec_mutators.rs +156 -0
  44. package/crates/tish_compile/tests/regr_562_wrapped_struct_array_push.rs +50 -0
  45. package/crates/tish_compile/tests/regr_fixed_native_coercions.rs +69 -0
  46. package/crates/tish_compile/tests/regr_gba_numerics_gated.rs +58 -0
  47. package/crates/tish_compile/tests/regr_gba_struct_reflection.rs +106 -0
  48. package/crates/tish_compile/tests/regression/name_collision_lib.tish +6 -0
  49. package/crates/tish_compile/tests/regression/name_collision_main.tish +13 -0
  50. package/crates/tish_compile/tests/regression/struct_array_write.tish +22 -0
  51. package/crates/tish_compile/tests/regression/struct_field_return_read.tish +10 -0
  52. package/crates/tish_compiler_wasm/Cargo.toml +1 -0
  53. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +56 -6
  54. package/crates/tish_core/Cargo.toml +39 -4
  55. package/crates/tish_core/src/compat.rs +506 -0
  56. package/crates/tish_core/src/console_style.rs +23 -1
  57. package/crates/tish_core/src/json.rs +42 -10
  58. package/crates/tish_core/src/lib.rs +121 -25
  59. package/crates/tish_core/src/macros.rs +1 -1
  60. package/crates/tish_core/src/shape.rs +4 -2
  61. package/crates/tish_core/src/uri.rs +8 -1
  62. package/crates/tish_core/src/value.rs +305 -54
  63. package/crates/tish_core/src/vmref.rs +17 -5
  64. package/crates/tish_eval/Cargo.toml +2 -0
  65. package/crates/tish_eval/src/eval.rs +90 -3
  66. package/crates/tish_eval/src/value.rs +8 -0
  67. package/crates/tish_eval/src/value_convert.rs +316 -21
  68. package/crates/tish_lsp/Cargo.toml +3 -1
  69. package/crates/tish_lsp/src/import_goto.rs +41 -14
  70. package/crates/tish_lsp/src/main.rs +18 -0
  71. package/crates/tish_native/src/build.rs +246 -0
  72. package/crates/tish_native/src/config.rs +20 -0
  73. package/crates/tish_native/src/lib.rs +16 -6
  74. package/crates/tish_parser/src/lib.rs +28 -0
  75. package/crates/tish_parser/src/parser.rs +25 -4
  76. package/crates/tish_runtime/src/lib.rs +55 -9
  77. package/crates/tish_runtime_gba/Cargo.lock +837 -0
  78. package/crates/tish_runtime_gba/Cargo.toml +16 -0
  79. package/crates/tish_runtime_gba/src/gba.rs +211 -0
  80. package/crates/tish_runtime_gba/src/lib.rs +804 -0
  81. package/crates/tish_vm/src/jit.rs +1446 -164
  82. package/crates/tish_vm/src/vm.rs +583 -173
  83. package/justfile +9 -0
  84. package/package.json +1 -1
  85. package/platform/darwin-arm64/tish-lsp +0 -0
  86. package/platform/darwin-x64/tish-lsp +0 -0
  87. package/platform/linux-arm64/tish-lsp +0 -0
  88. package/platform/linux-x64/tish-lsp +0 -0
  89. package/platform/win32-x64/tish-lsp.exe +0 -0
package/Cargo.toml CHANGED
@@ -36,7 +36,10 @@ members = [
36
36
  ]
37
37
  # Generated native-build projects (`tish build`) land under `target/` when TMPDIR points there;
38
38
  # never enroll anything beneath `target/` as a workspace member.
39
- exclude = ["target"]
39
+ # GBA-only: depends on `agb`, which builds solely for thumbv4t. Excluded so host
40
+ # `cargo build` doesn't try to cross-compile it; built explicitly with the target
41
+ # (and consumed by path from generated GBA projects, not as a workspace member).
42
+ exclude = ["target", "crates/tish_runtime_gba"]
40
43
  resolver = "2"
41
44
 
42
45
  [workspace.package]
package/bin/tish-lsp CHANGED
Binary file
@@ -557,6 +557,12 @@ pub(crate) struct BuildArgs {
557
557
  pub crate_type: String,
558
558
  #[arg(long, help_heading = "Options")]
559
559
  pub no_optimize: bool,
560
+ /// Platform for RN-style module resolution (`macos`, `ios`, `windows`, `linux`, `web`).
561
+ #[arg(long, value_name = "NAME", help_heading = "Options")]
562
+ pub platform: Option<String>,
563
+ /// Surface for module resolution: `native`, `webview`, or `web` (also `TISH_SURFACE`).
564
+ #[arg(long, value_name = "NAME", help_heading = "Options")]
565
+ pub surface: Option<String>,
560
566
  /// 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).
561
567
  #[arg(long, help_heading = "Options")]
562
568
  pub source_map: bool,
@@ -599,6 +605,12 @@ pub(crate) struct CompileModuleArgs {
599
605
  /// Project root for resolving bare specifiers / `node_modules` (defaults to the file's parent).
600
606
  #[arg(long, value_name = "DIR", help_heading = "Options")]
601
607
  pub project_root: Option<String>,
608
+ /// Platform for RN-style module resolution (`macos`, `ios`, `windows`, `linux`, `web`).
609
+ #[arg(long, value_name = "NAME", help_heading = "Options")]
610
+ pub platform: Option<String>,
611
+ /// Surface for module resolution: `native`, `webview`, or `web`.
612
+ #[arg(long, value_name = "NAME", help_heading = "Options")]
613
+ pub surface: Option<String>,
602
614
  /// Disable AST optimizations (forced on when a source map is emitted).
603
615
  #[arg(long, help_heading = "Options")]
604
616
  pub no_optimize: bool,
@@ -626,6 +638,22 @@ pub(crate) enum Commands {
626
638
  /// Compile a single `.tish` module to one ES module (for Vite dev / HMR); prints to stdout
627
639
  #[command(name = "compile-module")]
628
640
  CompileModule(CompileModuleArgs),
641
+ /// Resolve a relative `.tish` import with platform/surface cascade (for Vite / tooling)
642
+ #[command(name = "resolve-id")]
643
+ ResolveId {
644
+ /// Import specifier (must be relative, e.g. `./Button` or `./Button.tish`).
645
+ #[arg(required = true, value_name = "SOURCE", help_heading = "Arguments")]
646
+ source: String,
647
+ /// Absolute path of the importing file (directory used as resolve base).
648
+ #[arg(long, value_name = "FILE", help_heading = "Options")]
649
+ importer: Option<String>,
650
+ /// Platform token (`macos`, `ios`, `windows`, `linux`, `web`).
651
+ #[arg(long, value_name = "NAME", help_heading = "Options")]
652
+ platform: Option<String>,
653
+ /// Surface token (`native`, `webview`, `web`).
654
+ #[arg(long, value_name = "NAME", help_heading = "Options")]
655
+ surface: Option<String>,
656
+ },
629
657
  /// Parse and dump AST
630
658
  #[command(name = "dump-ast")]
631
659
  DumpAst {
@@ -73,7 +73,8 @@ fn native_build_features_from_cli(cli_features: &[String]) -> Vec<String> {
73
73
  fn argv_with_implicit_run(mut argv: Vec<String>) -> Vec<String> {
74
74
  if argv.len() >= 2 {
75
75
  let first = argv[1].as_str();
76
- const SUBCOMMANDS: &[&str] = &["run", "repl", "build", "compile-module", "dump-ast"];
76
+ const SUBCOMMANDS: &[&str] =
77
+ &["run", "repl", "build", "compile-module", "resolve-id", "dump-ast"];
77
78
  let looks_like_file = !first.starts_with('-') && !SUBCOMMANDS.contains(&first);
78
79
  if looks_like_file {
79
80
  argv.insert(1, "run".to_string());
@@ -126,6 +127,13 @@ fn main() {
126
127
  if let Some(mode) = &a.check {
127
128
  std::env::set_var("TISH_CHECK", mode);
128
129
  }
130
+ if let Err(e) = tishlang_compile::apply_resolve_env(
131
+ a.platform.as_deref(),
132
+ a.surface.as_deref(),
133
+ ) {
134
+ eprintln!("Error: {}", e);
135
+ std::process::exit(1);
136
+ }
129
137
  build_file(
130
138
  &a.file,
131
139
  &a.output,
@@ -140,7 +148,30 @@ fn main() {
140
148
  &a.jsx_import_source,
141
149
  )
142
150
  }
143
- Some(Commands::CompileModule(a)) => compile_module(&a),
151
+ Some(Commands::CompileModule(a)) => {
152
+ if let Err(e) = tishlang_compile::apply_resolve_env(
153
+ a.platform.as_deref(),
154
+ a.surface.as_deref(),
155
+ ) {
156
+ eprintln!("Error: {}", e);
157
+ std::process::exit(1);
158
+ }
159
+ compile_module(&a)
160
+ }
161
+ Some(Commands::ResolveId {
162
+ source,
163
+ importer,
164
+ platform,
165
+ surface,
166
+ }) => tishlang_compile::resolve_id_public(
167
+ &source,
168
+ importer.as_deref(),
169
+ platform.as_deref(),
170
+ surface.as_deref(),
171
+ )
172
+ .map(|path| {
173
+ println!("{path}");
174
+ }),
144
175
  Some(Commands::DumpAst {
145
176
  file,
146
177
  ignore_indent,
@@ -883,9 +914,9 @@ fn build_file(
883
914
  .map_err(|e| e.to_string());
884
915
  }
885
916
 
886
- if target != "native" {
917
+ if target != "native" && target != "gba" {
887
918
  return Err(format!(
888
- "Unknown target: {}. Use 'native', 'js', 'wasm', 'wasm-gpu', 'wasi', or 'bytecode'.",
919
+ "Unknown target: {}. Use 'native', 'gba', 'js', 'wasm', 'wasm-gpu', 'wasi', or 'bytecode'.",
889
920
  target
890
921
  ));
891
922
  }
@@ -899,7 +930,12 @@ fn build_file(
899
930
  });
900
931
  let features: Vec<String> = native_build_features_from_cli(cli_features);
901
932
 
902
- let build_config = if let Some(triple) = ios_triple {
933
+ let build_config = if target == "gba" {
934
+ if is_js {
935
+ return Err("tish build --target gba requires a .tish entry file.".to_string());
936
+ }
937
+ tishlang_native::NativeBuildConfig::gba()
938
+ } else if let Some(triple) = ios_triple {
903
939
  tishlang_native::NativeBuildConfig::ios_staticlib(triple)
904
940
  } else if crate_type == "staticlib" {
905
941
  tishlang_native::NativeBuildConfig {
@@ -10,3 +10,24 @@ pub fn greet(args: &[Value]) -> Value {
10
10
  };
11
11
  Value::String(Arc::from(format!("Hello, {}!", name)))
12
12
  }
13
+
14
+ /// Boxed shim kept for dynamic call sites (`Value::Function` dispatch / a spread call).
15
+ pub fn add(args: &[Value]) -> Value {
16
+ let a = match args.first() {
17
+ Some(Value::Number(n)) => *n,
18
+ _ => 0.0,
19
+ };
20
+ let b = match args.get(1) {
21
+ Some(Value::Number(n)) => *n,
22
+ _ => 0.0,
23
+ };
24
+ Value::Number(a + b)
25
+ }
26
+
27
+ /// Typed extern (`declare fn add(a: number, b: number): number` in `tish.d.tish`): the direct, unboxed
28
+ /// entry codegen calls as `demo_shim::add_typed(..)` when the args lower to the declared native types.
29
+ /// `number` lowers to `f64` on every native target (unlike `i32`, which is native only under
30
+ /// `--target gba`), so this direct call fires on the default desktop build too.
31
+ pub fn add_typed(a: f64, b: f64) -> f64 {
32
+ a + b
33
+ }
@@ -0,0 +1,4 @@
1
+ // Typed externs shipped by the `demo_shim` crate (auto-loaded from beside its source by codegen).
2
+ // Declares the native signatures of exports so a call lowers to a DIRECT `demo_shim::<name>_typed(..)`
3
+ // instead of the boxed `value_call` path — the consuming program never writes these declarations.
4
+ declare fn add(a: number, b: number): number
@@ -1,3 +1,7 @@
1
- import { greet } from 'cargo:demo_shim'
1
+ import { greet, add } from 'cargo:demo_shim'
2
2
 
3
3
  console.log(greet('from cargo'))
4
+
5
+ // `add` has a typed signature shipped in the crate's `tish.d.tish` (auto-loaded) — this call lowers
6
+ // to a direct `demo_shim::add_typed(2, 3)`, not the boxed namespace-object dispatch.
7
+ console.log(add(2, 3))
@@ -0,0 +1,59 @@
1
+ //! Typed externs (perf lever): a native `cargo:` import whose typed signature is shipped in the
2
+ //! crate's `tish.d.tish` (auto-loaded from beside the crate source) must lower a matching call to a
3
+ //! DIRECT `<crate>::<name>_typed(..)` — no `Value` boxing of the args, no `value_call` dispatch —
4
+ //! while a call with no typed signature keeps the boxed namespace path. Uses the in-repo
5
+ //! `tests/fixtures/cargo_example_project/` fixture: `demo_shim` exports `greet` (untyped, stays boxed)
6
+ //! and `add` (declared `add(a: i32, b: i32): i32` in `crates/demo-shim/tish.d.tish`).
7
+ //!
8
+ //! Covers the three branch pieces with no other coverage: the `tish.d.tish` auto-load
9
+ //! (`compile_project_full_emit`), `collect_extern_fns` pairing, and the direct-call emission.
10
+
11
+ use std::path::PathBuf;
12
+
13
+ use tishlang_compile::compile_project_full;
14
+
15
+ fn emit_fixture() -> String {
16
+ let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
17
+ let input_path = manifest_dir
18
+ .join("tests/fixtures/cargo_example_project/src/main.tish")
19
+ .canonicalize()
20
+ .expect("cargo_example_project test fixture");
21
+ let project_root = input_path.parent().map(|p| {
22
+ if p.file_name().and_then(|n| n.to_str()) == Some("src") {
23
+ p.parent().unwrap_or(p)
24
+ } else {
25
+ p
26
+ }
27
+ });
28
+ compile_project_full(&input_path, project_root, &[], true)
29
+ .expect("compile_project_full on the cargo example fixture")
30
+ .0
31
+ }
32
+
33
+ #[test]
34
+ fn declared_extern_call_lowers_to_a_direct_typed_call() {
35
+ let rust = emit_fixture();
36
+ // The `add(2, 3)` call (typed via the auto-loaded tish.d.tish) must be a direct call into the
37
+ // crate's `add_typed`, not a boxed dispatch through the namespace object.
38
+ assert!(
39
+ rust.contains("add_typed"),
40
+ "typed extern `add` must lower to a direct `demo_shim::add_typed(..)` call; \
41
+ the auto-loaded tish.d.tish declaration was not honored.\n{rust}"
42
+ );
43
+ // The direct call targets the backing crate.
44
+ assert!(
45
+ rust.contains("demo_shim :: add_typed") || rust.contains("demo_shim::add_typed"),
46
+ "the direct typed-extern call must target the `demo_shim` crate.\n{rust}"
47
+ );
48
+ }
49
+
50
+ #[test]
51
+ fn untyped_extern_call_stays_boxed() {
52
+ let rust = emit_fixture();
53
+ // `greet` has no typed signature (no tish.d.tish entry), so it must NOT gain a `_typed` call —
54
+ // it keeps the boxed namespace/`value_call` path. Guards against collect_extern_fns over-matching.
55
+ assert!(
56
+ !rust.contains("greet_typed"),
57
+ "untyped extern `greet` must stay boxed (no `greet_typed` direct call).\n{rust}"
58
+ );
59
+ }
@@ -0,0 +1,79 @@
1
+ //! Regression for #38: a merged `@scope/pkg` package (declared via `tish.module`, `exports.tish`,
2
+ //! etc.) must resolve as merged **Tish source**, not be routed to the native Rust-crate path (which
3
+ //! would reject it with "not a Tish native module" or mis-treat it as a crate). Native `@scope`
4
+ //! crates (marked with `tish.crate` / `tish.rustDependencies`) remain native and are exercised by
5
+ //! the downstream regression (tish-apple/tish-macos).
6
+
7
+ use std::fs;
8
+
9
+ use tishlang_ast::{Expr, Statement};
10
+ use tishlang_compile::{merge_modules, resolve_project};
11
+
12
+ /// Build a tempdir project whose `main.tish` imports a merged `@test/greet` package. `tish_field`
13
+ /// is the extra package.json snippet declaring how the package presents itself (`,"tish":{…}` etc.).
14
+ fn setup(tish_field: &str) -> tempfile::TempDir {
15
+ let dir = tempfile::tempdir().unwrap();
16
+ let root = dir.path();
17
+ let pkg = root.join("node_modules/@test/greet");
18
+ fs::create_dir_all(&pkg).unwrap();
19
+ fs::write(
20
+ pkg.join("package.json"),
21
+ format!(r#"{{"name":"@test/greet","version":"1.0.0"{tish_field}}}"#),
22
+ )
23
+ .unwrap();
24
+ fs::write(pkg.join("index.tish"), "export fn greet() { return \"hi\" }\n").unwrap();
25
+ fs::write(
26
+ root.join("main.tish"),
27
+ "import { greet } from \"@test/greet\"\nconsole.log(greet())\n",
28
+ )
29
+ .unwrap();
30
+ dir
31
+ }
32
+
33
+ fn assert_merged_as_source(dir: &tempfile::TempDir) {
34
+ let root = dir.path();
35
+ let modules = resolve_project(&root.join("main.tish"), Some(root)).expect("resolve");
36
+ let merged = merge_modules(modules).expect("merge");
37
+ // Merged as source ⇒ the package's `greet` fn is folded into the top-level program.
38
+ let has_greet = merged
39
+ .program
40
+ .statements
41
+ .iter()
42
+ .any(|s| matches!(s, Statement::FunDecl { name, .. } if name.as_ref() == "greet"));
43
+ assert!(
44
+ has_greet,
45
+ "merged @scope package's fn must be folded into the program (merged as Tish source)"
46
+ );
47
+ // ...and NOT lowered to a native module load (the #38 mis-routing).
48
+ let native_scope = merged.program.statements.iter().any(|s| {
49
+ matches!(
50
+ s,
51
+ Statement::VarDecl { init: Some(Expr::NativeModuleLoad { spec, .. }), .. }
52
+ if spec.contains("@test")
53
+ )
54
+ });
55
+ assert!(
56
+ !native_scope,
57
+ "a merged @scope package must NOT be routed to the native Rust-crate path (#38)"
58
+ );
59
+ }
60
+
61
+ #[test]
62
+ fn scope_tish_module_true_merges_as_source() {
63
+ assert_merged_as_source(&setup(r#","tish":{"module":true}"#));
64
+ }
65
+
66
+ #[test]
67
+ fn scope_tish_module_string_path_merges_as_source() {
68
+ assert_merged_as_source(&setup(r#","tish":{"module":"index.tish"}"#));
69
+ }
70
+
71
+ #[test]
72
+ fn scope_exports_tish_merges_as_source() {
73
+ assert_merged_as_source(&setup(r#","exports":{"tish":"./index.tish"}"#));
74
+ }
75
+
76
+ #[test]
77
+ fn scope_plain_main_merges_as_source() {
78
+ assert_merged_as_source(&setup(r#","main":"index.tish""#));
79
+ }
@@ -340,7 +340,22 @@ pub fn find_crate_path(crate_name: &str) -> Result<PathBuf, String> {
340
340
 
341
341
  /// Sanitize a user-chosen output stem for Cargo `[lib]` / `[[bin]]` target names.
342
342
  pub fn cargo_target_name(stem: &str) -> String {
343
- stem.replace('-', "_")
343
+ // The result is emitted verbatim into a generated `Cargo.toml` (`name = "<stem>"`) and used as
344
+ // the crate/target name, so it must be a valid Rust identifier. Map every char that isn't
345
+ // ASCII-alphanumeric or `_` to `_` — a `-` (cargo normalizes it to `_` anyway), but also a `.`,
346
+ // space, or `"` from an odd `-o` path, which would otherwise break or inject the manifest.
347
+ // A leading digit is prefixed with `_`; an empty result falls back to a fixed default.
348
+ let mut name: String = stem
349
+ .chars()
350
+ .map(|c| if c.is_ascii_alphanumeric() || c == '_' { c } else { '_' })
351
+ .collect();
352
+ if name.is_empty() {
353
+ return "tish_output".to_string();
354
+ }
355
+ if name.starts_with(|c: char| c.is_ascii_digit()) {
356
+ name.insert(0, '_');
357
+ }
358
+ name
344
359
  }
345
360
 
346
361
  /// Create a temp build directory with src subdir.
@@ -428,6 +443,17 @@ mod protoc_tests {
428
443
  assert_eq!(cargo_target_name("hello-ios"), "hello_ios");
429
444
  assert_eq!(cargo_target_name("tish_out"), "tish_out");
430
445
  }
446
+
447
+ #[test]
448
+ fn cargo_target_name_sanitizes_invalid_chars() {
449
+ // Quotes / dots / spaces from an odd `-o` stem can't break or inject the manifest.
450
+ assert_eq!(cargo_target_name("a\"b"), "a_b");
451
+ assert_eq!(cargo_target_name("my.app"), "my_app");
452
+ assert_eq!(cargo_target_name("my game"), "my_game");
453
+ // A leading digit is made identifier-safe; empty falls back.
454
+ assert_eq!(cargo_target_name("3d"), "_3d");
455
+ assert_eq!(cargo_target_name(""), "tish_output");
456
+ }
431
457
  }
432
458
 
433
459
  /// Find the built static library in target/release (or target/$TRIPLE/release).
@@ -8,15 +8,23 @@ description = "Shared builtin implementations for Tish (array, string, object, m
8
8
  license-file = { workspace = true }
9
9
  repository = { workspace = true }
10
10
  [dependencies]
11
- ahash = "0.8.11"
12
- indexmap = "2"
13
- rand = "0.10.1"
14
- unicode-normalization = "0.1.25"
15
- tishlang_core = { path = "../tish_core", version = ">=0.1" }
11
+ # std-only deps, optional so the `portable` build drops them (rand needs OS
12
+ # entropy; ahash needs atomics — both replaced by tishlang_core hooks/FxHasher).
13
+ ahash = { version = "0.8.11", optional = true }
14
+ indexmap = { version = "2", default-features = false }
15
+ rand = { version = "0.10.1", optional = true }
16
+ # default-features=false keeps it no_std (it works on core+alloc via tinyvec);
17
+ # tish only uses NFC/NFD/NFKC/NFKD, none of which need std.
18
+ unicode-normalization = { version = "0.1.25", default-features = false }
19
+ tishlang_core = { path = "../tish_core", version = ">=0.1", default-features = false }
16
20
 
17
21
  [features]
18
- default = []
22
+ default = ["std"]
23
+ std = ["tishlang_core/std", "dep:ahash", "dep:rand"]
19
24
  regex = ["tishlang_core/regex"]
25
+ # no_std + alloc build for GBA. Inherits tishlang_core's portable vocabulary and
26
+ # its clock/RNG hooks; float math routes through tishlang_core::FloatExt (libm).
27
+ portable = ["tishlang_core/portable"]
20
28
  # Propagate the `send-values` feature from `tishlang_core` so that closures
21
29
  # handed to us via `NativeFn` pick up the `Send + Sync` bound automatically
22
30
  # when multi-threaded `Value`s are enabled upstream.