shellsim 0.1.5__tar.gz → 0.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.
- {shellsim-0.1.5 → shellsim-0.1.7}/Cargo.lock +1 -1
- {shellsim-0.1.5 → shellsim-0.1.7}/Cargo.toml +1 -1
- {shellsim-0.1.5 → shellsim-0.1.7}/PKG-INFO +1 -1
- {shellsim-0.1.5 → shellsim-0.1.7}/python/native/Cargo.lock +2 -2
- {shellsim-0.1.5 → shellsim-0.1.7}/python/native/Cargo.toml +1 -1
- {shellsim-0.1.5 → shellsim-0.1.7}/python/shellsim/_cli.py +4 -1
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/heap.rs +41 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/object_model.rs +1 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/core.rs +290 -1
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/frozen.rs +2 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/math.rs +150 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/mod.rs +1 -0
- shellsim-0.1.7/src/python/stdlib/source/cmath.py +65 -0
- shellsim-0.1.7/src/python/stdlib/source/complex.py +112 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/calls.rs +93 -4
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/namespace.rs +12 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm.rs +2 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/vfs.rs +163 -41
- {shellsim-0.1.5 → shellsim-0.1.7}/LICENSE +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/README.md +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/pyproject.toml +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/python/native/src/lib.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/python/shellsim/__init__.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/python/shellsim/__main__.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/python/shellsim/_api.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/python/shellsim/py.typed +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/python/shellsim/python.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/rust-toolchain.toml +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/bin/shellsim-python.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/clock.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/archives.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/awk/ast.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/awk/eval.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/awk/lexer.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/awk/parser.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/awk.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/builtins.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/dd.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/echo.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/find.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/fs.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/apply.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/commit.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/compare.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/config.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/conflict.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/diff.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/ignore.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/import.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/log.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/merge.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/mod.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/plumbing.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/rebase.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/refs.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/repo.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/stash.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/switch.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/worktree.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/hashing.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/makecmd.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/mod.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/net.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/options.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/patch.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/pkg.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/printf.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/proc.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/regex_compat.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/rgcmd.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/sed.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/sort.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/system.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/tarcmd.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/text.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/unavailable.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/util.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/zipcmd.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/descriptors.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/exec.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/expand.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/harness.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/harness_manager.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/hashes.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/host_ingest.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/interp.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/jqcmd.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/lib.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/main.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/mcp.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/net.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/netcmd.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/process.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/pseudo_fs.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/ast.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/bytecode.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/compiler.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/filesystem.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/http.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/lexer.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/mapping.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/mod.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/native.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/number.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/parser.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/process.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/protocol.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/slice.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/source.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/argparse.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/base64.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/bisect.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/collections.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/dataclasses.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/enum.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/functools.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/hashlib.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/heapq.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/http.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/importlib.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/itertools.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/json.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/numpy.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/os.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/pytest.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/re.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/abc.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/base64.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/codecs.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/collections.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/csv.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/datetime.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/glob.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/hashlib.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/http.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/http_client.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/importlib.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/io.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/json.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/logging.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/numpy_random.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/os.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/pathlib.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/random.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/statistics.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/struct.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/subprocess.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/tempfile.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/urllib.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/urllib_error.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/urllib_request.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/uuid.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/zipfile.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/zlib.py +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/string.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/struct.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/subprocess.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/sys.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/time.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/typing.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/unittest.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/vfs.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/zlib.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/string.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/token.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/dispatch.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/host.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/iteration.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/native_runtime.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/objects.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/operations.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/resources.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/sandbox.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/scenario.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/scheduler.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/shell.rs +0 -0
- {shellsim-0.1.5 → shellsim-0.1.7}/src/telemetry.rs +0 -0
|
@@ -406,7 +406,7 @@ dependencies = [
|
|
|
406
406
|
|
|
407
407
|
[[package]]
|
|
408
408
|
name = "shellsim"
|
|
409
|
-
version = "0.1.
|
|
409
|
+
version = "0.1.7"
|
|
410
410
|
dependencies = [
|
|
411
411
|
"base64",
|
|
412
412
|
"crc32fast",
|
|
@@ -427,7 +427,7 @@ dependencies = [
|
|
|
427
427
|
|
|
428
428
|
[[package]]
|
|
429
429
|
name = "shellsim-python-bindings"
|
|
430
|
-
version = "0.1.
|
|
430
|
+
version = "0.1.7"
|
|
431
431
|
dependencies = [
|
|
432
432
|
"pyo3",
|
|
433
433
|
"serde",
|
|
@@ -148,7 +148,10 @@ def _interactive(environment: Environment, stdin: Any, stdout: Any, stderr: Any)
|
|
|
148
148
|
def main(arguments: Optional[Sequence[str]] = None) -> int:
|
|
149
149
|
"""Run the package CLI and return the simulated or usage exit status."""
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
parsed_arguments = list(sys.argv[1:] if arguments is None else arguments)
|
|
152
|
+
if parsed_arguments[-1:] == ["--"]:
|
|
153
|
+
parsed_arguments.pop()
|
|
154
|
+
options = _parser().parse_args(parsed_arguments)
|
|
152
155
|
environment, preparation_status = _prepare_environment(options, sys.stdout, sys.stderr)
|
|
153
156
|
if environment is None:
|
|
154
157
|
return preparation_status
|
|
@@ -48,6 +48,10 @@ impl SymbolId {
|
|
|
48
48
|
pub(super) const fn index(self) -> usize {
|
|
49
49
|
self.0 as usize
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
pub(super) fn from_index(index: usize) -> Option<Self> {
|
|
53
|
+
u32::try_from(index).ok().map(Self)
|
|
54
|
+
}
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
/// Runtime-local identity for one append-only instance storage layout.
|
|
@@ -386,6 +390,43 @@ impl Heap {
|
|
|
386
390
|
self.symbol_ids.get(name).copied()
|
|
387
391
|
}
|
|
388
392
|
|
|
393
|
+
/// Resolve a symbol identity back to its interpreter-owned name.
|
|
394
|
+
pub fn symbol_name(&self, symbol: SymbolId) -> Option<&str> {
|
|
395
|
+
self.symbol_names.get(symbol.index()).map(AsRef::as_ref)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/// Snapshot the names stored directly on an instance in either attribute representation.
|
|
399
|
+
pub fn instance_attribute_names(&self, id: ObjectId) -> Result<Vec<String>, String> {
|
|
400
|
+
let object = self
|
|
401
|
+
.objects
|
|
402
|
+
.get(id.0)
|
|
403
|
+
.and_then(Option::as_ref)
|
|
404
|
+
.ok_or("invalid object reference")?;
|
|
405
|
+
let Object::Instance { attributes, .. } = &object.payload else {
|
|
406
|
+
return Err("object does not have instance attributes".into());
|
|
407
|
+
};
|
|
408
|
+
match attributes {
|
|
409
|
+
InstanceAttributes::Shaped { shape, values } => (0..values.len())
|
|
410
|
+
.map(|slot| {
|
|
411
|
+
let symbol = self
|
|
412
|
+
.shape_attribute_at(*shape, slot)
|
|
413
|
+
.ok_or("invalid instance shape slot")?;
|
|
414
|
+
self.symbol_name(symbol)
|
|
415
|
+
.map(str::to_string)
|
|
416
|
+
.ok_or_else(|| "invalid instance attribute symbol".into())
|
|
417
|
+
})
|
|
418
|
+
.collect(),
|
|
419
|
+
InstanceAttributes::Dictionary(values) => values
|
|
420
|
+
.keys()
|
|
421
|
+
.map(|symbol| {
|
|
422
|
+
self.symbol_name(*symbol)
|
|
423
|
+
.map(str::to_string)
|
|
424
|
+
.ok_or_else(|| "invalid instance attribute symbol".into())
|
|
425
|
+
})
|
|
426
|
+
.collect(),
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
389
430
|
/// Intern one identifier, charging its process-lifetime storage before mutation.
|
|
390
431
|
pub fn intern_symbol(
|
|
391
432
|
&mut self,
|
|
@@ -883,6 +883,7 @@ fn install_builtin_slots(types: &mut [PyType]) {
|
|
|
883
883
|
slots.add = Some(intrinsic(super::stdlib::core::slot_string_add));
|
|
884
884
|
slots.multiply = Some(intrinsic(super::stdlib::core::slot_string_multiply));
|
|
885
885
|
slots.reflected_multiply = Some(intrinsic(super::stdlib::core::slot_string_multiply));
|
|
886
|
+
slots.remainder = Some(intrinsic(super::stdlib::core::slot_string_remainder));
|
|
886
887
|
|
|
887
888
|
let slots = &mut types[BuiltinType::Bytes as usize].slots;
|
|
888
889
|
slots.length = Some(unary(super::stdlib::core::slot_bytes_length));
|
|
@@ -13,7 +13,7 @@ use super::super::native::PyValue as Value;
|
|
|
13
13
|
use super::super::native::{
|
|
14
14
|
CallArgs, FunctionDef, MethodDef, NativeTypeDef, OwnedPyString, PyByteArray, PyBytes,
|
|
15
15
|
PyCallable, PyDict, PyError, PyKind, PyList, PyProperty, PyResult, PyRuntime, PySequence,
|
|
16
|
-
PySet, PyValue, PyValueCast,
|
|
16
|
+
PySet, PyTuple, PyValue, PyValueCast,
|
|
17
17
|
};
|
|
18
18
|
use super::super::number::PyNumber;
|
|
19
19
|
use super::super::slice::SlicePlan;
|
|
@@ -56,6 +56,8 @@ pub(crate) static STRING_TYPE: NativeTypeDef = NativeTypeDef {
|
|
|
56
56
|
method("str", "join", string_join),
|
|
57
57
|
method("str", "replace", string_replace),
|
|
58
58
|
method("str", "format", string_format),
|
|
59
|
+
method("str", "ljust", string_ljust),
|
|
60
|
+
method("str", "rjust", string_rjust),
|
|
59
61
|
method("str", "encode", string_encode),
|
|
60
62
|
method("str", "lower", string_lower),
|
|
61
63
|
method("str", "upper", string_upper),
|
|
@@ -737,6 +739,293 @@ fn string_replace(runtime: &mut dyn PyRuntime, receiver: PyValue, args: CallArgs
|
|
|
737
739
|
runtime.new_string(result)
|
|
738
740
|
}
|
|
739
741
|
|
|
742
|
+
fn string_ljust(runtime: &mut dyn PyRuntime, receiver: PyValue, args: CallArgs) -> PyResult {
|
|
743
|
+
string_justify(runtime, receiver, args, false)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
fn string_rjust(runtime: &mut dyn PyRuntime, receiver: PyValue, args: CallArgs) -> PyResult {
|
|
747
|
+
string_justify(runtime, receiver, args, true)
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
fn string_justify(
|
|
751
|
+
runtime: &mut dyn PyRuntime,
|
|
752
|
+
receiver: PyValue,
|
|
753
|
+
args: CallArgs,
|
|
754
|
+
right: bool,
|
|
755
|
+
) -> PyResult {
|
|
756
|
+
let name = if right { "str.rjust" } else { "str.ljust" };
|
|
757
|
+
args.expect_positional(name, 1, 2)?;
|
|
758
|
+
args.reject_keywords(name)?;
|
|
759
|
+
let OwnedPyString(value) = receiver.cast(runtime)?;
|
|
760
|
+
let width = runtime
|
|
761
|
+
.int_value(&args.positional()[0])
|
|
762
|
+
.ok_or_else(|| PyError::type_error("width must be an integer"))?;
|
|
763
|
+
let fill = if let Some(fill) = args.positional().get(1) {
|
|
764
|
+
let OwnedPyString(fill) = (*fill).cast(runtime)?;
|
|
765
|
+
if fill.chars().count() != 1 {
|
|
766
|
+
return Err(PyError::type_error(
|
|
767
|
+
"the fill character must be exactly one character long",
|
|
768
|
+
));
|
|
769
|
+
}
|
|
770
|
+
fill
|
|
771
|
+
} else {
|
|
772
|
+
" ".to_string()
|
|
773
|
+
};
|
|
774
|
+
let padding = usize::try_from(width)
|
|
775
|
+
.ok()
|
|
776
|
+
.unwrap_or_default()
|
|
777
|
+
.saturating_sub(value.chars().count());
|
|
778
|
+
let added = fill
|
|
779
|
+
.len()
|
|
780
|
+
.checked_mul(padding)
|
|
781
|
+
.ok_or_else(|| PyError::resource_error("justified string is too large"))?;
|
|
782
|
+
let capacity = value
|
|
783
|
+
.len()
|
|
784
|
+
.checked_add(added)
|
|
785
|
+
.ok_or_else(|| PyError::resource_error("justified string is too large"))?;
|
|
786
|
+
runtime.reserve_memory(capacity)?;
|
|
787
|
+
runtime.charge_cpu(u64::try_from(capacity).unwrap_or(u64::MAX))?;
|
|
788
|
+
let padding = fill.repeat(padding);
|
|
789
|
+
runtime.new_string(if right {
|
|
790
|
+
format!("{padding}{value}")
|
|
791
|
+
} else {
|
|
792
|
+
format!("{value}{padding}")
|
|
793
|
+
})
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/// Implement the established string `%` protocol without routing numeric remainder through it.
|
|
797
|
+
pub(crate) fn slot_string_remainder(
|
|
798
|
+
runtime: &mut dyn PyRuntime,
|
|
799
|
+
left: PyValue,
|
|
800
|
+
right: PyValue,
|
|
801
|
+
) -> PyResult<Option<PyValue>> {
|
|
802
|
+
let OwnedPyString(template) = left.cast(runtime)?;
|
|
803
|
+
runtime.reserve_memory(template.len())?;
|
|
804
|
+
let arguments = if runtime.kind(&right)? == PyKind::Tuple {
|
|
805
|
+
right.cast::<PyTuple>(runtime)?.items(runtime)?
|
|
806
|
+
} else {
|
|
807
|
+
vec![right]
|
|
808
|
+
};
|
|
809
|
+
let mut argument = 0usize;
|
|
810
|
+
let mut used_mapping = false;
|
|
811
|
+
let mut output = String::new();
|
|
812
|
+
let characters = template.chars().collect::<Vec<_>>();
|
|
813
|
+
let mut index = 0usize;
|
|
814
|
+
while index < characters.len() {
|
|
815
|
+
runtime.charge_cpu(1)?;
|
|
816
|
+
if characters[index] != '%' {
|
|
817
|
+
output.push(characters[index]);
|
|
818
|
+
index += 1;
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
index += 1;
|
|
822
|
+
if characters.get(index) == Some(&'%') {
|
|
823
|
+
output.push('%');
|
|
824
|
+
index += 1;
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
let mapping_key = if characters.get(index) == Some(&'(') {
|
|
828
|
+
index += 1;
|
|
829
|
+
let start = index;
|
|
830
|
+
while characters
|
|
831
|
+
.get(index)
|
|
832
|
+
.is_some_and(|character| *character != ')')
|
|
833
|
+
{
|
|
834
|
+
index += 1;
|
|
835
|
+
}
|
|
836
|
+
if characters.get(index) != Some(&')') {
|
|
837
|
+
return Err(PyError::value_error("incomplete format key"));
|
|
838
|
+
}
|
|
839
|
+
let key = characters[start..index].iter().collect::<String>();
|
|
840
|
+
index += 1;
|
|
841
|
+
Some(key)
|
|
842
|
+
} else {
|
|
843
|
+
None
|
|
844
|
+
};
|
|
845
|
+
let mut left_align = false;
|
|
846
|
+
let mut plus = false;
|
|
847
|
+
let mut space = false;
|
|
848
|
+
let mut alternate = false;
|
|
849
|
+
let mut zero = false;
|
|
850
|
+
while let Some(flag) = characters.get(index) {
|
|
851
|
+
match flag {
|
|
852
|
+
'-' => left_align = true,
|
|
853
|
+
'+' => plus = true,
|
|
854
|
+
' ' => space = true,
|
|
855
|
+
'#' => alternate = true,
|
|
856
|
+
'0' => zero = true,
|
|
857
|
+
_ => break,
|
|
858
|
+
}
|
|
859
|
+
index += 1;
|
|
860
|
+
}
|
|
861
|
+
let width = parse_format_digits(&characters, &mut index)?;
|
|
862
|
+
let precision = if characters.get(index) == Some(&'.') {
|
|
863
|
+
index += 1;
|
|
864
|
+
Some(parse_format_digits(&characters, &mut index)?.unwrap_or(0))
|
|
865
|
+
} else {
|
|
866
|
+
None
|
|
867
|
+
};
|
|
868
|
+
let requested = width.unwrap_or_default().max(precision.unwrap_or_default());
|
|
869
|
+
runtime.reserve_memory(requested)?;
|
|
870
|
+
runtime.charge_cpu(u64::try_from(requested).unwrap_or(u64::MAX))?;
|
|
871
|
+
let conversion = *characters
|
|
872
|
+
.get(index)
|
|
873
|
+
.ok_or_else(|| PyError::value_error("incomplete format"))?;
|
|
874
|
+
index += 1;
|
|
875
|
+
let value = if let Some(key) = mapping_key {
|
|
876
|
+
used_mapping = true;
|
|
877
|
+
let mapping = right.cast::<PyDict>(runtime)?;
|
|
878
|
+
let key_value = runtime.new_string(key.clone())?;
|
|
879
|
+
runtime
|
|
880
|
+
.dict_get(mapping, &key_value)?
|
|
881
|
+
.ok_or_else(|| PyError::exception("KeyError", key))?
|
|
882
|
+
} else {
|
|
883
|
+
let value = arguments
|
|
884
|
+
.get(argument)
|
|
885
|
+
.copied()
|
|
886
|
+
.ok_or_else(|| PyError::type_error("not enough arguments for format string"))?;
|
|
887
|
+
argument += 1;
|
|
888
|
+
value
|
|
889
|
+
};
|
|
890
|
+
let mut rendered = match conversion {
|
|
891
|
+
's' => runtime.display(&value)?,
|
|
892
|
+
'r' | 'a' => runtime.repr(&value)?,
|
|
893
|
+
'd' | 'i' | 'u' => runtime
|
|
894
|
+
.integer_text(&value)?
|
|
895
|
+
.ok_or_else(|| PyError::type_error("%d format: a real number is required"))?,
|
|
896
|
+
'x' | 'X' | 'o' => {
|
|
897
|
+
let decimal = runtime
|
|
898
|
+
.integer_text(&value)?
|
|
899
|
+
.ok_or_else(|| PyError::type_error("integer format requires an integer"))?;
|
|
900
|
+
let integer = decimal
|
|
901
|
+
.parse::<BigInt>()
|
|
902
|
+
.map_err(|_| PyError::runtime_error("invalid internal integer"))?;
|
|
903
|
+
let digits = match conversion {
|
|
904
|
+
'x' => format!("{integer:x}"),
|
|
905
|
+
'X' => format!("{integer:X}"),
|
|
906
|
+
'o' => format!("{integer:o}"),
|
|
907
|
+
_ => unreachable!(),
|
|
908
|
+
};
|
|
909
|
+
if alternate && integer != BigInt::zero() {
|
|
910
|
+
match conversion {
|
|
911
|
+
'x' => format!("0x{digits}"),
|
|
912
|
+
'X' => format!("0X{digits}"),
|
|
913
|
+
'o' => format!("0o{digits}"),
|
|
914
|
+
_ => unreachable!(),
|
|
915
|
+
}
|
|
916
|
+
} else {
|
|
917
|
+
digits
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
'f' | 'F' | 'e' | 'E' | 'g' | 'G' => {
|
|
921
|
+
let number = value.cast::<PyNumber>(runtime)?.into_f64()?;
|
|
922
|
+
let precision = precision.unwrap_or(6);
|
|
923
|
+
match conversion {
|
|
924
|
+
'f' | 'F' => format!("{number:.precision$}"),
|
|
925
|
+
'e' => format!("{number:.precision$e}"),
|
|
926
|
+
'E' => format!("{number:.precision$E}"),
|
|
927
|
+
'g' | 'G' => {
|
|
928
|
+
let mut text = format!("{number:.precision$}");
|
|
929
|
+
if conversion == 'G' {
|
|
930
|
+
text.make_ascii_uppercase();
|
|
931
|
+
}
|
|
932
|
+
text
|
|
933
|
+
}
|
|
934
|
+
_ => unreachable!(),
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
'c' => {
|
|
938
|
+
if let Some(integer) = runtime.int_value(&value) {
|
|
939
|
+
u32::try_from(integer)
|
|
940
|
+
.ok()
|
|
941
|
+
.and_then(char::from_u32)
|
|
942
|
+
.ok_or_else(|| PyError::overflow_error("%c arg not in range"))?
|
|
943
|
+
.to_string()
|
|
944
|
+
} else {
|
|
945
|
+
let OwnedPyString(text) = value.cast(runtime)?;
|
|
946
|
+
if text.chars().count() != 1 {
|
|
947
|
+
return Err(PyError::type_error("%c requires int or char"));
|
|
948
|
+
}
|
|
949
|
+
text
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
other => {
|
|
953
|
+
return Err(PyError::value_error(format!(
|
|
954
|
+
"unsupported format character {other:?}"
|
|
955
|
+
)))
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
if matches!(conversion, 's' | 'r' | 'a') {
|
|
959
|
+
if let Some(precision) = precision {
|
|
960
|
+
rendered = rendered.chars().take(precision).collect();
|
|
961
|
+
}
|
|
962
|
+
} else if matches!(conversion, 'd' | 'i' | 'u') {
|
|
963
|
+
rendered = pad_integer_precision(rendered, precision);
|
|
964
|
+
}
|
|
965
|
+
if matches!(
|
|
966
|
+
conversion,
|
|
967
|
+
'd' | 'i' | 'u' | 'f' | 'F' | 'e' | 'E' | 'g' | 'G'
|
|
968
|
+
) && !rendered.starts_with('-')
|
|
969
|
+
{
|
|
970
|
+
if plus {
|
|
971
|
+
rendered.insert(0, '+');
|
|
972
|
+
} else if space {
|
|
973
|
+
rendered.insert(0, ' ');
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if let Some(width) = width {
|
|
977
|
+
let padding = width.saturating_sub(rendered.chars().count());
|
|
978
|
+
if padding > 0 {
|
|
979
|
+
let fill = if zero && !left_align { '0' } else { ' ' };
|
|
980
|
+
if left_align {
|
|
981
|
+
rendered.extend(std::iter::repeat_n(fill, padding));
|
|
982
|
+
} else if fill == '0' && matches!(rendered.chars().next(), Some('+' | '-' | ' ')) {
|
|
983
|
+
let sign = rendered.remove(0);
|
|
984
|
+
rendered = format!("{sign}{}{rendered}", "0".repeat(padding));
|
|
985
|
+
} else {
|
|
986
|
+
rendered = format!("{}{rendered}", fill.to_string().repeat(padding));
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
runtime.reserve_memory(rendered.len())?;
|
|
991
|
+
output.push_str(&rendered);
|
|
992
|
+
}
|
|
993
|
+
if !used_mapping && argument < arguments.len() {
|
|
994
|
+
return Err(PyError::type_error(
|
|
995
|
+
"not all arguments converted during string formatting",
|
|
996
|
+
));
|
|
997
|
+
}
|
|
998
|
+
runtime.reserve_memory(output.len())?;
|
|
999
|
+
Ok(Some(runtime.new_string(output)?))
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
fn parse_format_digits(characters: &[char], index: &mut usize) -> PyResult<Option<usize>> {
|
|
1003
|
+
let start = *index;
|
|
1004
|
+
let mut value = 0usize;
|
|
1005
|
+
while let Some(character) = characters.get(*index).and_then(|value| value.to_digit(10)) {
|
|
1006
|
+
value = value
|
|
1007
|
+
.checked_mul(10)
|
|
1008
|
+
.and_then(|value| value.checked_add(character as usize))
|
|
1009
|
+
.ok_or_else(|| PyError::resource_error("format width is too large"))?;
|
|
1010
|
+
*index += 1;
|
|
1011
|
+
}
|
|
1012
|
+
Ok((*index != start).then_some(value))
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
fn pad_integer_precision(mut value: String, precision: Option<usize>) -> String {
|
|
1016
|
+
let Some(precision) = precision else {
|
|
1017
|
+
return value;
|
|
1018
|
+
};
|
|
1019
|
+
let sign = value.starts_with('-').then(|| value.remove(0));
|
|
1020
|
+
if value.len() < precision {
|
|
1021
|
+
value = format!("{}{value}", "0".repeat(precision - value.len()));
|
|
1022
|
+
}
|
|
1023
|
+
if let Some(sign) = sign {
|
|
1024
|
+
value.insert(0, sign);
|
|
1025
|
+
}
|
|
1026
|
+
value
|
|
1027
|
+
}
|
|
1028
|
+
|
|
740
1029
|
fn string_format(runtime: &mut dyn PyRuntime, receiver: PyValue, args: CallArgs) -> PyResult {
|
|
741
1030
|
let OwnedPyString(template) = receiver.cast(runtime)?;
|
|
742
1031
|
let mut result = String::new();
|
|
@@ -10,6 +10,8 @@ pub(super) fn module_source(name: &str) -> Option<&'static str> {
|
|
|
10
10
|
"abc" => Some(include_str!("source/abc.py")),
|
|
11
11
|
"base64" => Some(include_str!("source/base64.py")),
|
|
12
12
|
"codecs" => Some(include_str!("source/codecs.py")),
|
|
13
|
+
"_complex" => Some(include_str!("source/complex.py")),
|
|
14
|
+
"cmath" => Some(include_str!("source/cmath.py")),
|
|
13
15
|
"csv" => Some(include_str!("source/csv.py")),
|
|
14
16
|
"datetime" => Some(include_str!("source/datetime.py")),
|
|
15
17
|
"collections" => Some(include_str!("source/collections.py")),
|
|
@@ -17,6 +17,26 @@ use super::super::number::PyNumber;
|
|
|
17
17
|
pub(super) static MODULE: ModuleDef = ModuleDef {
|
|
18
18
|
name: "math",
|
|
19
19
|
functions: &[
|
|
20
|
+
FunctionDef {
|
|
21
|
+
module: "math",
|
|
22
|
+
name: "acos",
|
|
23
|
+
call: native_acos,
|
|
24
|
+
},
|
|
25
|
+
FunctionDef {
|
|
26
|
+
module: "math",
|
|
27
|
+
name: "asin",
|
|
28
|
+
call: native_asin,
|
|
29
|
+
},
|
|
30
|
+
FunctionDef {
|
|
31
|
+
module: "math",
|
|
32
|
+
name: "atan",
|
|
33
|
+
call: native_atan,
|
|
34
|
+
},
|
|
35
|
+
FunctionDef {
|
|
36
|
+
module: "math",
|
|
37
|
+
name: "atan2",
|
|
38
|
+
call: native_atan2,
|
|
39
|
+
},
|
|
20
40
|
FunctionDef {
|
|
21
41
|
module: "math",
|
|
22
42
|
name: "ceil",
|
|
@@ -27,11 +47,21 @@ pub(super) static MODULE: ModuleDef = ModuleDef {
|
|
|
27
47
|
name: "cos",
|
|
28
48
|
call: native_cos,
|
|
29
49
|
},
|
|
50
|
+
FunctionDef {
|
|
51
|
+
module: "math",
|
|
52
|
+
name: "degrees",
|
|
53
|
+
call: native_degrees,
|
|
54
|
+
},
|
|
30
55
|
FunctionDef {
|
|
31
56
|
module: "math",
|
|
32
57
|
name: "exp",
|
|
33
58
|
call: native_exp,
|
|
34
59
|
},
|
|
60
|
+
FunctionDef {
|
|
61
|
+
module: "math",
|
|
62
|
+
name: "hypot",
|
|
63
|
+
call: native_hypot,
|
|
64
|
+
},
|
|
35
65
|
FunctionDef {
|
|
36
66
|
module: "math",
|
|
37
67
|
name: "isinf",
|
|
@@ -47,6 +77,21 @@ pub(super) static MODULE: ModuleDef = ModuleDef {
|
|
|
47
77
|
name: "log",
|
|
48
78
|
call: native_log,
|
|
49
79
|
},
|
|
80
|
+
FunctionDef {
|
|
81
|
+
module: "math",
|
|
82
|
+
name: "log10",
|
|
83
|
+
call: native_log10,
|
|
84
|
+
},
|
|
85
|
+
FunctionDef {
|
|
86
|
+
module: "math",
|
|
87
|
+
name: "pow",
|
|
88
|
+
call: native_pow,
|
|
89
|
+
},
|
|
90
|
+
FunctionDef {
|
|
91
|
+
module: "math",
|
|
92
|
+
name: "radians",
|
|
93
|
+
call: native_radians,
|
|
94
|
+
},
|
|
50
95
|
FunctionDef {
|
|
51
96
|
module: "math",
|
|
52
97
|
name: "sin",
|
|
@@ -87,6 +132,22 @@ pub(super) static MODULE: ModuleDef = ModuleDef {
|
|
|
87
132
|
],
|
|
88
133
|
};
|
|
89
134
|
|
|
135
|
+
fn native_acos(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
136
|
+
native_call(runtime, args, "acos")
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fn native_asin(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
140
|
+
native_call(runtime, args, "asin")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fn native_atan(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
144
|
+
native_call(runtime, args, "atan")
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
fn native_atan2(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
148
|
+
native_call(runtime, args, "atan2")
|
|
149
|
+
}
|
|
150
|
+
|
|
90
151
|
fn native_ceil(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
91
152
|
native_call(runtime, args, "ceil")
|
|
92
153
|
}
|
|
@@ -95,10 +156,18 @@ fn native_cos(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
|
95
156
|
native_call(runtime, args, "cos")
|
|
96
157
|
}
|
|
97
158
|
|
|
159
|
+
fn native_degrees(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
160
|
+
native_call(runtime, args, "degrees")
|
|
161
|
+
}
|
|
162
|
+
|
|
98
163
|
fn native_exp(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
99
164
|
native_call(runtime, args, "exp")
|
|
100
165
|
}
|
|
101
166
|
|
|
167
|
+
fn native_hypot(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
168
|
+
native_call(runtime, args, "hypot")
|
|
169
|
+
}
|
|
170
|
+
|
|
102
171
|
fn native_isinf(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
103
172
|
native_call(runtime, args, "isinf")
|
|
104
173
|
}
|
|
@@ -111,6 +180,18 @@ fn native_log(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
|
111
180
|
native_call(runtime, args, "log")
|
|
112
181
|
}
|
|
113
182
|
|
|
183
|
+
fn native_log10(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
184
|
+
native_call(runtime, args, "log10")
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn native_pow(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
188
|
+
native_call(runtime, args, "pow")
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
fn native_radians(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
192
|
+
native_call(runtime, args, "radians")
|
|
193
|
+
}
|
|
194
|
+
|
|
114
195
|
fn native_sin(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
115
196
|
native_call(runtime, args, "sin")
|
|
116
197
|
}
|
|
@@ -220,9 +301,19 @@ pub fn constant(name: &str) -> Option<MathValue> {
|
|
|
220
301
|
/// Dispatch one of the observed/reviewer-requested `math` functions.
|
|
221
302
|
pub fn call(name: &str, args: &[f64]) -> MathResult {
|
|
222
303
|
match name {
|
|
304
|
+
"acos" => unary("acos", args, acos),
|
|
305
|
+
"asin" => unary("asin", args, asin),
|
|
306
|
+
"atan" => unary("atan", args, |value| Ok(MathValue::Float(value.atan()))),
|
|
307
|
+
"atan2" => binary("atan2", args, |y, x| Ok(MathValue::Float(y.atan2(x)))),
|
|
223
308
|
"ceil" => unary("ceil", args, ceil),
|
|
224
309
|
"cos" => unary("cos", args, cos),
|
|
310
|
+
"degrees" => unary("degrees", args, |value| {
|
|
311
|
+
Ok(MathValue::Float(value.to_degrees()))
|
|
312
|
+
}),
|
|
225
313
|
"exp" => unary("exp", args, exp),
|
|
314
|
+
"hypot" => Ok(MathValue::Float(
|
|
315
|
+
args.iter().copied().fold(0.0_f64, f64::hypot),
|
|
316
|
+
)),
|
|
226
317
|
"isinf" => unary("isinf", args, |value| Ok(MathValue::Bool(isinf(value)))),
|
|
227
318
|
"isnan" => unary("isnan", args, |value| Ok(MathValue::Bool(isnan(value)))),
|
|
228
319
|
"log" => match args {
|
|
@@ -234,6 +325,11 @@ pub fn call(name: &str, args: &[f64]) -> MathResult {
|
|
|
234
325
|
actual: args.len(),
|
|
235
326
|
}),
|
|
236
327
|
},
|
|
328
|
+
"log10" => unary("log10", args, log10),
|
|
329
|
+
"pow" => binary("pow", args, pow),
|
|
330
|
+
"radians" => unary("radians", args, |value| {
|
|
331
|
+
Ok(MathValue::Float(value.to_radians()))
|
|
332
|
+
}),
|
|
237
333
|
"sin" => unary("sin", args, sin),
|
|
238
334
|
"sqrt" => unary("sqrt", args, sqrt),
|
|
239
335
|
"tan" => unary("tan", args, tan),
|
|
@@ -241,6 +337,21 @@ pub fn call(name: &str, args: &[f64]) -> MathResult {
|
|
|
241
337
|
}
|
|
242
338
|
}
|
|
243
339
|
|
|
340
|
+
fn binary(
|
|
341
|
+
function: &'static str,
|
|
342
|
+
args: &[f64],
|
|
343
|
+
operation: impl FnOnce(f64, f64) -> MathResult,
|
|
344
|
+
) -> MathResult {
|
|
345
|
+
let [left, right] = args else {
|
|
346
|
+
return Err(MathError::Arity {
|
|
347
|
+
function,
|
|
348
|
+
expected: "exactly 2",
|
|
349
|
+
actual: args.len(),
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
operation(*left, *right)
|
|
353
|
+
}
|
|
354
|
+
|
|
244
355
|
fn unary(
|
|
245
356
|
function: &'static str,
|
|
246
357
|
args: &[f64],
|
|
@@ -288,6 +399,45 @@ pub fn exp(value: f64) -> MathResult {
|
|
|
288
399
|
Ok(MathValue::Float(result))
|
|
289
400
|
}
|
|
290
401
|
|
|
402
|
+
/// Return the inverse sine in radians.
|
|
403
|
+
pub fn asin(value: f64) -> MathResult {
|
|
404
|
+
if !(-1.0..=1.0).contains(&value) {
|
|
405
|
+
return Err(MathError::ValueError("math domain error"));
|
|
406
|
+
}
|
|
407
|
+
Ok(MathValue::Float(value.asin()))
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/// Return the inverse cosine in radians.
|
|
411
|
+
pub fn acos(value: f64) -> MathResult {
|
|
412
|
+
if !(-1.0..=1.0).contains(&value) {
|
|
413
|
+
return Err(MathError::ValueError("math domain error"));
|
|
414
|
+
}
|
|
415
|
+
Ok(MathValue::Float(value.acos()))
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/// Return the base-ten logarithm of a positive value.
|
|
419
|
+
pub fn log10(value: f64) -> MathResult {
|
|
420
|
+
if value <= 0.0 {
|
|
421
|
+
return Err(MathError::ValueError("math domain error"));
|
|
422
|
+
}
|
|
423
|
+
Ok(MathValue::Float(value.log10()))
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/// Return `left**right` with Python's real-number domain and overflow errors.
|
|
427
|
+
pub fn pow(left: f64, right: f64) -> MathResult {
|
|
428
|
+
if left == 0.0 && right < 0.0 {
|
|
429
|
+
return Err(MathError::ValueError("math domain error"));
|
|
430
|
+
}
|
|
431
|
+
let value = left.powf(right);
|
|
432
|
+
if value.is_nan() && !(left.is_nan() || right.is_nan()) {
|
|
433
|
+
return Err(MathError::ValueError("math domain error"));
|
|
434
|
+
}
|
|
435
|
+
if value.is_infinite() && left.is_finite() && right.is_finite() {
|
|
436
|
+
return Err(MathError::OverflowError("math range error"));
|
|
437
|
+
}
|
|
438
|
+
Ok(MathValue::Float(value))
|
|
439
|
+
}
|
|
440
|
+
|
|
291
441
|
/// Return whether `value` is positive or negative infinity.
|
|
292
442
|
pub const fn isinf(value: f64) -> bool {
|
|
293
443
|
value.is_infinite()
|