bare-script 5.1.6__tar.gz → 5.1.7__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 (22) hide show
  1. {bare_script-5.1.6/src/bare_script.egg-info → bare_script-5.1.7}/PKG-INFO +12 -1
  2. {bare_script-5.1.6 → bare_script-5.1.7}/README.md +11 -0
  3. {bare_script-5.1.6 → bare_script-5.1.7}/pyproject.toml +1 -1
  4. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script/runtime.py +25 -16
  5. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script/runtime_c.c +26 -2
  6. {bare_script-5.1.6 → bare_script-5.1.7/src/bare_script.egg-info}/PKG-INFO +12 -1
  7. {bare_script-5.1.6 → bare_script-5.1.7}/LICENSE +0 -0
  8. {bare_script-5.1.6 → bare_script-5.1.7}/setup.cfg +0 -0
  9. {bare_script-5.1.6 → bare_script-5.1.7}/setup.py +0 -0
  10. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script/__init__.py +0 -0
  11. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script/__main__.py +0 -0
  12. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script/bare.py +0 -0
  13. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script/include.py +0 -0
  14. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script/include_source.py +0 -0
  15. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script/library.py +0 -0
  16. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script/options.py +0 -0
  17. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script/value.py +0 -0
  18. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script.egg-info/SOURCES.txt +0 -0
  19. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script.egg-info/dependency_links.txt +0 -0
  20. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script.egg-info/entry_points.txt +0 -0
  21. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script.egg-info/requires.txt +0 -0
  22. {bare_script-5.1.6 → bare_script-5.1.7}/src/bare_script.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bare-script
3
- Version: 5.1.6
3
+ Version: 5.1.7
4
4
  Summary: BareScript; a lightweight scripting and expression language
5
5
  Author-email: "Craig A. Hobbs" <craigahobbs@gmail.com>
6
6
  License-Expression: MIT
@@ -296,6 +296,17 @@ builds. See the Performance section below for benchmark results comparing the C
296
296
  pure-Python runtime, and native Python.
297
297
 
298
298
 
299
+ ## Thread Safety
300
+
301
+ Both runtimes may be used from multiple threads, including on the free-threaded (no-GIL) Python
302
+ builds. Script models and a `globals` dict may be shared across threads once populated - for example,
303
+ execute a script that defines functions once, then call those functions concurrently. Each
304
+ concurrently executing thread must pass its own `options` dict to `execute_script`, since the runtime
305
+ records the running `statementCount` there. The parser, linter, and include library stubs initialize
306
+ lazily and are safe to call from any thread. As in Python, unsynchronized concurrent mutation of a
307
+ shared array or object from several threads has unspecified results but never crashes the runtime.
308
+
309
+
299
310
  ## Performance
300
311
 
301
312
  The `make perf` target benchmarks the BareScript runtime with a suite of compute-intensive tests —
@@ -270,6 +270,17 @@ builds. See the Performance section below for benchmark results comparing the C
270
270
  pure-Python runtime, and native Python.
271
271
 
272
272
 
273
+ ## Thread Safety
274
+
275
+ Both runtimes may be used from multiple threads, including on the free-threaded (no-GIL) Python
276
+ builds. Script models and a `globals` dict may be shared across threads once populated - for example,
277
+ execute a script that defines functions once, then call those functions concurrently. Each
278
+ concurrently executing thread must pass its own `options` dict to `execute_script`, since the runtime
279
+ records the running `statementCount` there. The parser, linter, and include library stubs initialize
280
+ lazily and are safe to call from any thread. As in Python, unsynchronized concurrent mutation of a
281
+ shared array or object from several threads has unspecified results but never crashes the runtime.
282
+
283
+
273
284
  ## Performance
274
285
 
275
286
  The `make perf` target benchmarks the BareScript runtime with a suite of compute-intensive tests —
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "bare-script"
7
- version = "5.1.6"
7
+ version = "5.1.7"
8
8
  description = "BareScript; a lightweight scripting and expression language"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -10,6 +10,7 @@ import functools
10
10
  import json
11
11
  import math
12
12
  import sys
13
+ import threading
13
14
 
14
15
  from .include_source import SYSTEM_INCLUDES
15
16
  from .library import EXPRESSION_FUNCTIONS, INTRINSICS, SCRIPT_FUNCTIONS
@@ -199,20 +200,25 @@ def _execute_script_helper(script, statements, options, locals_, label_indexes):
199
200
  return None
200
201
 
201
202
 
202
- # The barescriptParser.bare include library script globals (lazily initialized)
203
+ # The barescriptParser.bare include library script globals (lazily initialized under a lock)
203
204
  _PARSER_GLOBALS = None
205
+ _PARSER_GLOBALS_INIT_LOCK = threading.Lock()
204
206
 
205
207
 
206
208
  def _parser_globals_init():
207
- # Execute the barescriptParser.bare include library script, if necessary
209
+ # Execute the barescriptParser.bare include library script, if necessary. The globals are built
210
+ # locally and published only once complete so a concurrent first-use caller never observes a
211
+ # partially-initialized parser; the lock serializes the one-time initialization.
208
212
  # pylint: disable-next=global-statement
209
213
  global _PARSER_GLOBALS
210
- if _PARSER_GLOBALS is None:
211
- _PARSER_GLOBALS = {}
212
- execute_script(
213
- {'statements': [{'include': {'includes': [{'url': 'barescriptParser.bare', 'system': True}]}}]},
214
- {'globals': _PARSER_GLOBALS}
215
- )
214
+ with _PARSER_GLOBALS_INIT_LOCK:
215
+ if _PARSER_GLOBALS is None:
216
+ parser_globals = {}
217
+ execute_script(
218
+ {'statements': [{'include': {'includes': [{'url': 'barescriptParser.bare', 'system': True}]}}]},
219
+ {'globals': parser_globals}
220
+ )
221
+ _PARSER_GLOBALS = parser_globals
216
222
 
217
223
 
218
224
  def _normalize_statements(statements):
@@ -281,8 +287,9 @@ def barescript_parse_expression(expr_text, line_number=None, script_name=None, a
281
287
  return result['result']
282
288
 
283
289
 
284
- # The barescriptLint.bare include library script globals (lazily initialized)
290
+ # The barescriptLint.bare include library script globals (lazily initialized under a lock)
285
291
  _LINT_GLOBALS = None
292
+ _LINT_GLOBALS_INIT_LOCK = threading.Lock()
286
293
 
287
294
 
288
295
  def barescript_lint_script(script, globals_=None):
@@ -297,15 +304,17 @@ def barescript_lint_script(script, globals_=None):
297
304
  :rtype: list[str]
298
305
  """
299
306
 
300
- # Execute the barescriptLint.bare include library script, if necessary
307
+ # Execute the barescriptLint.bare include library script, if necessary (see _parser_globals_init)
301
308
  # pylint: disable-next=global-statement
302
309
  global _LINT_GLOBALS
303
- if _LINT_GLOBALS is None:
304
- _LINT_GLOBALS = {}
305
- execute_script(
306
- {'statements': [{'include': {'includes': [{'url': 'barescriptLint.bare', 'system': True}]}}]},
307
- {'globals': _LINT_GLOBALS}
308
- )
310
+ with _LINT_GLOBALS_INIT_LOCK:
311
+ if _LINT_GLOBALS is None:
312
+ lint_globals = {}
313
+ execute_script(
314
+ {'statements': [{'include': {'includes': [{'url': 'barescriptLint.bare', 'system': True}]}}]},
315
+ {'globals': lint_globals}
316
+ )
317
+ _LINT_GLOBALS = lint_globals
309
318
 
310
319
  # Call the barescriptLint.bare lint function. Async function detection is not possible in
311
320
  # Python (all functions execute synchronously), so the async lint checks are skipped.
@@ -187,6 +187,7 @@ typedef struct {
187
187
  PyObject *str_method_upper;
188
188
  PyObject *str_method_lower;
189
189
  PyObject *str_method_strip;
190
+ PyObject *str_method_pop;
190
191
 
191
192
  // Imported objects
192
193
  PyObject *script_functions;
@@ -783,7 +784,12 @@ static UnaryOp parse_unary_op(PyObject *op)
783
784
  // value.value_args_validate model exactly and raise errors by calling the Python ValueArgsError
784
785
  // class so messages and error return values match. Intrinsics never touch the options object, so
785
786
  // no statement-counter sync is required around them. The table is populated once at module exec
786
- // and immutable afterwards (free-threading safe).
787
+ // and immutable afterwards (free-threading safe). A per-call args list is private to the calling
788
+ // thread, but the arrays and objects it references may be shared with other threads, so handlers
789
+ // touch those only through the bounds-checked, locking list/dict APIs (list_get_ref,
790
+ // PyList_SetItem, list.pop, dict_get_ref, ...) under the free-threaded build, where an
791
+ // unsynchronized concurrent mutation then raises instead of reading freed storage. The default
792
+ // (GIL) build keeps the direct macro accessors - the GIL already serializes those operations.
787
793
  //
788
794
 
789
795
 
@@ -1083,7 +1089,17 @@ static PyObject *intrinsic_array_get(PyObject *args)
1083
1089
  intrinsic_index_bounds_error(args, 1, "index");
1084
1090
  return NULL;
1085
1091
  }
1092
+ #ifdef Py_GIL_DISABLED
1093
+ // Bounds-checked under the list's lock - an unsynchronized concurrent shrink by another thread
1094
+ // raises IndexError instead of reading freed storage
1095
+ PyObject *value;
1096
+ if (list_get_ref(array, index, &value) < 0) {
1097
+ return NULL;
1098
+ }
1099
+ return value;
1100
+ #else
1086
1101
  return Py_NewRef(PyList_GET_ITEM(array, index));
1102
+ #endif
1087
1103
  }
1088
1104
 
1089
1105
 
@@ -1150,12 +1166,19 @@ static PyObject *intrinsic_array_pop(PyObject *args)
1150
1166
  intrinsic_args_error("array", array, NULL);
1151
1167
  return NULL;
1152
1168
  }
1169
+ #ifdef Py_GIL_DISABLED
1170
+ // list.pop() is atomic under the list's lock, like the reference implementation's array.pop() -
1171
+ // an unsynchronized concurrent pop by another thread then raises IndexError (empty list) rather
1172
+ // than returning an element this call did not remove
1173
+ return PyObject_CallMethodNoArgs(array, g.str_method_pop);
1174
+ #else
1153
1175
  PyObject *value = Py_NewRef(PyList_GET_ITEM(array, length - 1));
1154
1176
  if (PyList_SetSlice(array, length - 1, length, NULL) < 0) {
1155
1177
  Py_DECREF(value);
1156
1178
  return NULL;
1157
1179
  }
1158
1180
  return value;
1181
+ #endif
1159
1182
  }
1160
1183
 
1161
1184
 
@@ -5223,7 +5246,8 @@ static int runtime_c_exec(PyObject *module)
5223
5246
  intern_string(&g.str_copy, "copy") < 0 ||
5224
5247
  intern_string(&g.str_method_upper, "upper") < 0 ||
5225
5248
  intern_string(&g.str_method_lower, "lower") < 0 ||
5226
- intern_string(&g.str_method_strip, "strip") < 0) {
5249
+ intern_string(&g.str_method_strip, "strip") < 0 ||
5250
+ intern_string(&g.str_method_pop, "pop") < 0) {
5227
5251
  return -1;
5228
5252
  }
5229
5253
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bare-script
3
- Version: 5.1.6
3
+ Version: 5.1.7
4
4
  Summary: BareScript; a lightweight scripting and expression language
5
5
  Author-email: "Craig A. Hobbs" <craigahobbs@gmail.com>
6
6
  License-Expression: MIT
@@ -296,6 +296,17 @@ builds. See the Performance section below for benchmark results comparing the C
296
296
  pure-Python runtime, and native Python.
297
297
 
298
298
 
299
+ ## Thread Safety
300
+
301
+ Both runtimes may be used from multiple threads, including on the free-threaded (no-GIL) Python
302
+ builds. Script models and a `globals` dict may be shared across threads once populated - for example,
303
+ execute a script that defines functions once, then call those functions concurrently. Each
304
+ concurrently executing thread must pass its own `options` dict to `execute_script`, since the runtime
305
+ records the running `statementCount` there. The parser, linter, and include library stubs initialize
306
+ lazily and are safe to call from any thread. As in Python, unsynchronized concurrent mutation of a
307
+ shared array or object from several threads has unspecified results but never crashes the runtime.
308
+
309
+
299
310
  ## Performance
300
311
 
301
312
  The `make perf` target benchmarks the BareScript runtime with a suite of compute-intensive tests —
File without changes
File without changes
File without changes