synx-format 3.7.1__tar.gz → 3.7.2__tar.gz

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 (26) hide show
  1. {synx_format-3.7.1 → synx_format-3.7.2}/Cargo.lock +20 -60
  2. {synx_format-3.7.1 → synx_format-3.7.2}/Cargo.toml +1 -1
  3. {synx_format-3.7.1 → synx_format-3.7.2}/PKG-INFO +1 -1
  4. {synx_format-3.7.1 → synx_format-3.7.2}/bindings/python/Cargo.toml +6 -2
  5. {synx_format-3.7.1 → synx_format-3.7.2}/bindings/python/src/lib.rs +58 -13
  6. {synx_format-3.7.1 → synx_format-3.7.2}/bindings/python/src/synxl.rs +105 -51
  7. synx_format-3.7.2/bindings/python/tests/test_synxl_hardening.py +186 -0
  8. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/Cargo.toml +1 -1
  9. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/src/binary.rs +11 -0
  10. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/src/engine.rs +8 -0
  11. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/src/parser.rs +153 -86
  12. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/src/synxl.rs +179 -26
  13. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/src/value.rs +24 -1
  14. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/src/wasm.rs +22 -5
  15. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/tests/conformance_synxl.rs +61 -0
  16. synx_format-3.7.2/crates/synx-core/tests/property_synxl.rs +358 -0
  17. {synx_format-3.7.1 → synx_format-3.7.2}/pyproject.toml +1 -1
  18. {synx_format-3.7.1 → synx_format-3.7.2}/README.md +0 -0
  19. {synx_format-3.7.1 → synx_format-3.7.2}/bindings/python/tests/test_synxl.py +0 -0
  20. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/src/calc.rs +0 -0
  21. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/src/diff.rs +0 -0
  22. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/src/lib.rs +0 -0
  23. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/src/rng.rs +0 -0
  24. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/src/schema_json.rs +0 -0
  25. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/src/signing.rs +0 -0
  26. {synx_format-3.7.1 → synx_format-3.7.2}/crates/synx-core/tests/conformance.rs +0 -0
@@ -89,7 +89,7 @@ dependencies = [
89
89
 
90
90
  [[package]]
91
91
  name = "as-cli"
92
- version = "3.7.1"
92
+ version = "3.7.2"
93
93
  dependencies = [
94
94
  "clap",
95
95
  "dirs",
@@ -976,15 +976,6 @@ dependencies = [
976
976
  "hashbrown 0.16.1",
977
977
  ]
978
978
 
979
- [[package]]
980
- name = "indoc"
981
- version = "2.0.7"
982
- source = "registry+https://github.com/rust-lang/crates.io-index"
983
- checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
984
- dependencies = [
985
- "rustversion",
986
- ]
987
-
988
979
  [[package]]
989
980
  name = "ipnet"
990
981
  version = "2.12.0"
@@ -1156,15 +1147,6 @@ version = "2.8.0"
1156
1147
  source = "registry+https://github.com/rust-lang/crates.io-index"
1157
1148
  checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
1158
1149
 
1159
- [[package]]
1160
- name = "memoffset"
1161
- version = "0.9.1"
1162
- source = "registry+https://github.com/rust-lang/crates.io-index"
1163
- checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
1164
- dependencies = [
1165
- "autocfg",
1166
- ]
1167
-
1168
1150
  [[package]]
1169
1151
  name = "miniz_oxide"
1170
1152
  version = "0.8.9"
@@ -1466,38 +1448,32 @@ dependencies = [
1466
1448
 
1467
1449
  [[package]]
1468
1450
  name = "pyo3"
1469
- version = "0.24.2"
1451
+ version = "0.29.2"
1470
1452
  source = "registry+https://github.com/rust-lang/crates.io-index"
1471
- checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219"
1453
+ checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
1472
1454
  dependencies = [
1473
- "cfg-if",
1474
- "indoc",
1475
1455
  "libc",
1476
- "memoffset",
1477
1456
  "once_cell",
1478
1457
  "portable-atomic",
1479
1458
  "pyo3-build-config",
1480
1459
  "pyo3-ffi",
1481
1460
  "pyo3-macros",
1482
- "unindent",
1483
1461
  ]
1484
1462
 
1485
1463
  [[package]]
1486
1464
  name = "pyo3-build-config"
1487
- version = "0.24.2"
1465
+ version = "0.29.2"
1488
1466
  source = "registry+https://github.com/rust-lang/crates.io-index"
1489
- checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999"
1467
+ checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
1490
1468
  dependencies = [
1491
- "once_cell",
1492
- "python3-dll-a",
1493
1469
  "target-lexicon",
1494
1470
  ]
1495
1471
 
1496
1472
  [[package]]
1497
1473
  name = "pyo3-ffi"
1498
- version = "0.24.2"
1474
+ version = "0.29.2"
1499
1475
  source = "registry+https://github.com/rust-lang/crates.io-index"
1500
- checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33"
1476
+ checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
1501
1477
  dependencies = [
1502
1478
  "libc",
1503
1479
  "pyo3-build-config",
@@ -1505,9 +1481,9 @@ dependencies = [
1505
1481
 
1506
1482
  [[package]]
1507
1483
  name = "pyo3-macros"
1508
- version = "0.24.2"
1484
+ version = "0.29.2"
1509
1485
  source = "registry+https://github.com/rust-lang/crates.io-index"
1510
- checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9"
1486
+ checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
1511
1487
  dependencies = [
1512
1488
  "proc-macro2",
1513
1489
  "pyo3-macros-backend",
@@ -1517,26 +1493,16 @@ dependencies = [
1517
1493
 
1518
1494
  [[package]]
1519
1495
  name = "pyo3-macros-backend"
1520
- version = "0.24.2"
1496
+ version = "0.29.2"
1521
1497
  source = "registry+https://github.com/rust-lang/crates.io-index"
1522
- checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a"
1498
+ checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
1523
1499
  dependencies = [
1524
1500
  "heck",
1525
1501
  "proc-macro2",
1526
- "pyo3-build-config",
1527
1502
  "quote",
1528
1503
  "syn",
1529
1504
  ]
1530
1505
 
1531
- [[package]]
1532
- name = "python3-dll-a"
1533
- version = "0.2.14"
1534
- source = "registry+https://github.com/rust-lang/crates.io-index"
1535
- checksum = "d381ef313ae70b4da5f95f8a4de773c6aa5cd28f73adec4b4a31df70b66780d8"
1536
- dependencies = [
1537
- "cc",
1538
- ]
1539
-
1540
1506
  [[package]]
1541
1507
  name = "quote"
1542
1508
  version = "1.0.45"
@@ -2038,7 +2004,7 @@ dependencies = [
2038
2004
 
2039
2005
  [[package]]
2040
2006
  name = "synx-bench"
2041
- version = "3.7.1"
2007
+ version = "3.7.2"
2042
2008
  dependencies = [
2043
2009
  "criterion",
2044
2010
  "synx-core",
@@ -2046,7 +2012,7 @@ dependencies = [
2046
2012
 
2047
2013
  [[package]]
2048
2014
  name = "synx-c"
2049
- version = "3.7.1"
2015
+ version = "3.7.2"
2050
2016
  dependencies = [
2051
2017
  "serde_json",
2052
2018
  "synx-core",
@@ -2054,7 +2020,7 @@ dependencies = [
2054
2020
 
2055
2021
  [[package]]
2056
2022
  name = "synx-cli"
2057
- version = "3.7.1"
2023
+ version = "3.7.2"
2058
2024
  dependencies = [
2059
2025
  "clap",
2060
2026
  "dirs",
@@ -2069,7 +2035,7 @@ dependencies = [
2069
2035
 
2070
2036
  [[package]]
2071
2037
  name = "synx-core"
2072
- version = "3.7.1"
2038
+ version = "3.7.2"
2073
2039
  dependencies = [
2074
2040
  "ed25519-dalek",
2075
2041
  "itoa",
@@ -2087,14 +2053,14 @@ dependencies = [
2087
2053
 
2088
2054
  [[package]]
2089
2055
  name = "synx-format"
2090
- version = "3.7.1"
2056
+ version = "3.7.2"
2091
2057
  dependencies = [
2092
2058
  "synx-core",
2093
2059
  ]
2094
2060
 
2095
2061
  [[package]]
2096
2062
  name = "synx-lsp"
2097
- version = "3.7.1"
2063
+ version = "3.7.2"
2098
2064
  dependencies = [
2099
2065
  "serde_json",
2100
2066
  "synx-core",
@@ -2104,7 +2070,7 @@ dependencies = [
2104
2070
 
2105
2071
  [[package]]
2106
2072
  name = "synx-node"
2107
- version = "3.7.1"
2073
+ version = "3.7.2"
2108
2074
  dependencies = [
2109
2075
  "napi",
2110
2076
  "napi-build",
@@ -2114,7 +2080,7 @@ dependencies = [
2114
2080
 
2115
2081
  [[package]]
2116
2082
  name = "synx-python"
2117
- version = "3.7.1"
2083
+ version = "3.7.2"
2118
2084
  dependencies = [
2119
2085
  "pyo3",
2120
2086
  "serde_json",
@@ -2123,7 +2089,7 @@ dependencies = [
2123
2089
 
2124
2090
  [[package]]
2125
2091
  name = "synx-wasm"
2126
- version = "3.7.1"
2092
+ version = "3.7.2"
2127
2093
  dependencies = [
2128
2094
  "serde-wasm-bindgen",
2129
2095
  "serde_json",
@@ -2387,12 +2353,6 @@ version = "1.12.0"
2387
2353
  source = "registry+https://github.com/rust-lang/crates.io-index"
2388
2354
  checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
2389
2355
 
2390
- [[package]]
2391
- name = "unindent"
2392
- version = "0.2.4"
2393
- source = "registry+https://github.com/rust-lang/crates.io-index"
2394
- checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
2395
-
2396
2356
  [[package]]
2397
2357
  name = "untrusted"
2398
2358
  version = "0.9.0"
@@ -7,7 +7,7 @@ exclude = [
7
7
  ]
8
8
 
9
9
  [workspace.package]
10
- version = "3.7.1"
10
+ version = "3.7.2"
11
11
  edition = "2021"
12
12
  license = "MIT"
13
13
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synx-format
3
- Version: 3.7.1
3
+ Version: 3.7.2
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "synx-python"
3
- version = "3.7.1"
3
+ version = "3.7.2"
4
4
  edition = "2021"
5
5
  authors = ["APERTURESyndicate <support@aperturesyndicate.com>"]
6
6
  description = "SYNX Python native module via PyO3"
@@ -22,5 +22,9 @@ synx-core = { path = "../../crates/synx-core", features = ["serde", "wasm"] }
22
22
  # Windows linker needs a python3x.lib, and without this feature maturin stops
23
23
  # with "compiling for Windows without an interpreter requires PyO3's
24
24
  # generate-import-lib feature" — which is what failed the 3.7.0 run.
25
- pyo3 = { version = "0.24", features = ["generate-import-lib"] }
25
+ #
26
+ # 0.29 is the first line that knows CPython 3.14; 0.24 refused to build against
27
+ # it at all, and the `PYO3_USE_ABI3_FORWARD_COMPATIBILITY` escape hatch that
28
+ # worked around that is no longer needed.
29
+ pyo3 = { version = "0.29", features = ["generate-import-lib"] }
26
30
  serde_json = "1"
@@ -2,6 +2,7 @@
2
2
  //! SYNXL ("SYNX Lines") lives in [`synxl`].
3
3
 
4
4
  use pyo3::prelude::*;
5
+ use pyo3::exceptions::{PyTypeError, PyValueError};
5
6
  use pyo3::types::{PyDict, PyList};
6
7
  use synx_core::{self, Value, Mode, Options};
7
8
 
@@ -33,7 +34,7 @@ fn encode_hex(bytes: &[u8]) -> String {
33
34
  }
34
35
 
35
36
  /// Convert synx_core::Value to a Python object.
36
- fn value_to_py(py: Python<'_>, val: &Value) -> PyObject {
37
+ fn value_to_py(py: Python<'_>, val: &Value) -> Py<PyAny> {
37
38
  match val {
38
39
  Value::Null => py.None(),
39
40
  Value::Bool(b) => b.into_pyobject(py).unwrap().to_owned().into_any().unbind(),
@@ -42,7 +43,7 @@ fn value_to_py(py: Python<'_>, val: &Value) -> PyObject {
42
43
  Value::String(s) => s.into_pyobject(py).unwrap().into_any().unbind(),
43
44
  Value::Secret(_) => "[SECRET]".into_pyobject(py).unwrap().into_any().unbind(),
44
45
  Value::Array(arr) => {
45
- let items: Vec<PyObject> = arr.iter().map(|v| value_to_py(py, v)).collect();
46
+ let items: Vec<Py<PyAny>> = arr.iter().map(|v| value_to_py(py, v)).collect();
46
47
  let list = PyList::new(py, &items).unwrap();
47
48
  list.into_pyobject(py).unwrap().into_any().unbind()
48
49
  }
@@ -56,39 +57,83 @@ fn value_to_py(py: Python<'_>, val: &Value) -> PyObject {
56
57
  }
57
58
  }
58
59
 
60
+ /// Nesting depth accepted when converting a Python value.
61
+ ///
62
+ /// Matched to the SYNX parser's own cap, so anything this crate can *parse*
63
+ /// can also be converted back. Past it the conversion errors out instead of
64
+ /// recursing: `py_to_value` walks the object graph, and Python containers can
65
+ /// be arbitrarily deep or outright cyclic (`x["self"] = x`), neither of which
66
+ /// the caller can catch once the native stack is gone.
67
+ const MAX_PY_VALUE_DEPTH: usize = 128;
68
+
59
69
  /// Convert a Python object back to synx_core::Value.
60
70
  fn py_to_value(py: Python<'_>, obj: &Bound<'_, PyAny>) -> PyResult<Value> {
71
+ py_to_value_at(py, obj, 0)
72
+ }
73
+
74
+ fn py_to_value_at(py: Python<'_>, obj: &Bound<'_, PyAny>, depth: usize) -> PyResult<Value> {
75
+ if depth > MAX_PY_VALUE_DEPTH {
76
+ // A cycle reaches this too: it has no bottom, so it hits the cap. The
77
+ // alternative is a stack overflow, which on Windows kills the process
78
+ // outright — no traceback, nothing for `except` to catch.
79
+ return Err(PyValueError::new_err(format!(
80
+ "value nests deeper than {} levels, or contains a reference cycle",
81
+ MAX_PY_VALUE_DEPTH
82
+ )));
83
+ }
61
84
  if obj.is_none() {
62
85
  Ok(Value::Null)
63
86
  } else if let Ok(b) = obj.extract::<bool>() {
64
87
  Ok(Value::Bool(b))
65
88
  } else if let Ok(n) = obj.extract::<i64>() {
66
89
  Ok(Value::Int(n))
90
+ } else if obj.is_instance_of::<pyo3::types::PyInt>() {
91
+ // A Python int that did not fit i64. Falling through to `f64` would
92
+ // round it — a 20-digit id becomes a different id — and then to the
93
+ // catch-all, which would turn it into a string. Neither is a number
94
+ // the caller wrote, so this is refused instead.
95
+ Err(PyValueError::new_err(
96
+ "integer is outside the i64 range SYNX can represent",
97
+ ))
67
98
  } else if let Ok(f) = obj.extract::<f64>() {
68
99
  Ok(Value::Float(f))
69
100
  } else if let Ok(s) = obj.extract::<String>() {
70
101
  Ok(Value::String(s))
71
- } else if let Ok(list) = obj.downcast::<PyList>() {
102
+ } else if let Ok(list) = obj.cast::<PyList>() {
72
103
  let mut arr = Vec::new();
73
104
  for item in list.iter() {
74
- arr.push(py_to_value(py, &item)?);
105
+ arr.push(py_to_value_at(py, &item, depth + 1)?);
106
+ }
107
+ Ok(Value::Array(arr))
108
+ } else if let Ok(tuple) = obj.cast::<pyo3::types::PyTuple>() {
109
+ // A tuple is a sequence, so it maps to an array. The catch-all below
110
+ // would have stringified it to `(1, 2, 3)`.
111
+ let mut arr = Vec::new();
112
+ for item in tuple.iter() {
113
+ arr.push(py_to_value_at(py, &item, depth + 1)?);
75
114
  }
76
115
  Ok(Value::Array(arr))
77
- } else if let Ok(dict) = obj.downcast::<PyDict>() {
116
+ } else if let Ok(dict) = obj.cast::<PyDict>() {
78
117
  let mut map = std::collections::HashMap::new();
79
118
  for (k, v) in dict.iter() {
80
119
  let key: String = k.extract()?;
81
- map.insert(key, py_to_value(py, &v)?);
120
+ map.insert(key, py_to_value_at(py, &v, depth + 1)?);
82
121
  }
83
122
  Ok(Value::Object(map))
84
123
  } else {
85
- Ok(Value::String(format!("{}", obj)))
124
+ // Anything else has no SYNX representation. `str(obj)` used to stand in
125
+ // for one, which silently turned a `set`, a `bytes` or a custom class
126
+ // into its repr and wrote that to disk as if it were data.
127
+ Err(PyTypeError::new_err(format!(
128
+ "cannot convert {} to a SYNX value; pass a str, int, float, bool, None, list, tuple or dict",
129
+ obj.get_type().name()?
130
+ )))
86
131
  }
87
132
  }
88
133
 
89
134
  /// Parse a SYNX string and return a Python dict.
90
135
  #[pyfunction]
91
- fn parse(py: Python<'_>, text: &str) -> PyResult<PyObject> {
136
+ fn parse(py: Python<'_>, text: &str) -> PyResult<Py<PyAny>> {
92
137
  let result = synx_core::parse(text);
93
138
  Ok(value_to_py(py, &result.root))
94
139
  }
@@ -193,7 +238,7 @@ fn parse_active(
193
238
  text: &str,
194
239
  env: Option<std::collections::HashMap<String, String>>,
195
240
  base_path: Option<String>,
196
- ) -> PyResult<PyObject> {
241
+ ) -> PyResult<Py<PyAny>> {
197
242
  let mut result = synx_core::parse(text);
198
243
  if result.mode == Mode::Active {
199
244
  let mut opts = Options::default();
@@ -218,7 +263,7 @@ fn parse_tool(
218
263
  text: &str,
219
264
  env: Option<std::collections::HashMap<String, String>>,
220
265
  base_path: Option<String>,
221
- ) -> PyResult<PyObject> {
266
+ ) -> PyResult<Py<PyAny>> {
222
267
  let mut result = synx_core::parse(text);
223
268
  if result.mode == Mode::Active {
224
269
  let mut opts = Options::default();
@@ -285,7 +330,7 @@ fn to_prompt_block(text: &str, label: &str) -> PyResult<String> {
285
330
  /// Structural diff between two parsed SYNX dicts.
286
331
  /// Returns `{ "added": {...}, "removed": {...}, "changed": {...}, "unchanged": [...] }`.
287
332
  #[pyfunction]
288
- fn diff(py: Python<'_>, a: &Bound<'_, PyAny>, b: &Bound<'_, PyAny>) -> PyResult<PyObject> {
333
+ fn diff(py: Python<'_>, a: &Bound<'_, PyAny>, b: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
289
334
  let val_a = py_to_value(py, a)?;
290
335
  let val_b = py_to_value(py, b)?;
291
336
  let map_a = match val_a { Value::Object(m) => m, _ => std::collections::HashMap::new() };
@@ -307,7 +352,7 @@ fn diff_json(text_a: &str, text_b: &str) -> PyResult<String> {
307
352
 
308
353
  /// Load and parse a .synx file. Returns a Python dict.
309
354
  #[pyfunction]
310
- fn load(py: Python<'_>, file_path: &str) -> PyResult<PyObject> {
355
+ fn load(py: Python<'_>, file_path: &str) -> PyResult<Py<PyAny>> {
311
356
  let text = std::fs::read_to_string(file_path)
312
357
  .map_err(|e| PyErr::new::<pyo3::exceptions::PyIOError, _>(e.to_string()))?;
313
358
  let map = synx_core::Synx::parse(&text);
@@ -317,7 +362,7 @@ fn load(py: Python<'_>, file_path: &str) -> PyResult<PyObject> {
317
362
  /// Load, parse and resolve a .synx file with !active engine. Returns a Python dict.
318
363
  #[pyfunction]
319
364
  #[pyo3(signature = (file_path, base_path=None))]
320
- fn load_active(py: Python<'_>, file_path: &str, base_path: Option<&str>) -> PyResult<PyObject> {
365
+ fn load_active(py: Python<'_>, file_path: &str, base_path: Option<&str>) -> PyResult<Py<PyAny>> {
321
366
  let text = std::fs::read_to_string(file_path)
322
367
  .map_err(|e| PyErr::new::<pyo3::exceptions::PyIOError, _>(e.to_string()))?;
323
368
  let mut opts = synx_core::Options::default();