chemql-python 1.4.0__py3-none-any.whl → 1.4.2__py3-none-any.whl
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.
- chemql/__init__.py +245 -41
- chemql/stdlib/README.md +28 -0
- chemql/stdlib/decimal.cql +23 -0
- chemql/stdlib/fractions.cql +29 -0
- chemql/stdlib/pathlib.cql +38 -0
- chemql/stdlib/random.cql +29 -0
- {chemql_python-1.4.0.dist-info → chemql_python-1.4.2.dist-info}/METADATA +76 -41
- {chemql_python-1.4.0.dist-info → chemql_python-1.4.2.dist-info}/RECORD +12 -8
- {chemql_python-1.4.0.dist-info → chemql_python-1.4.2.dist-info}/WHEEL +1 -1
- {chemql_python-1.4.0.dist-info → chemql_python-1.4.2.dist-info}/entry_points.txt +0 -0
- {chemql_python-1.4.0.dist-info → chemql_python-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {chemql_python-1.4.0.dist-info → chemql_python-1.4.2.dist-info}/top_level.txt +0 -0
chemql/__init__.py
CHANGED
|
@@ -61,7 +61,7 @@ __all__ = [
|
|
|
61
61
|
"__version__"
|
|
62
62
|
]
|
|
63
63
|
|
|
64
|
-
__version__ = "1.4.
|
|
64
|
+
__version__ = "1.4.2"
|
|
65
65
|
|
|
66
66
|
# Persistent preprocessor/function state for the current ChemQL session.
|
|
67
67
|
DEFINITIONS = {}
|
|
@@ -407,6 +407,104 @@ _HELP_TOPICS = {
|
|
|
407
407
|
"chemistry": ("Chemistry Functions", ["molar_mass{formula}", "count_atoms{formula, element_symbol}", "empirical_formula{formula}", "element{symbol, field?}"]),
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
_STDLIB_HELP = {
|
|
411
|
+
"math": (
|
|
412
|
+
"Mathematical constants and numeric functions.",
|
|
413
|
+
[
|
|
414
|
+
"PI, E, TAU — Common mathematical constants.",
|
|
415
|
+
"sqrt{number}, cbrt{number}, abs{number}, floor{number}, ceil{number}, factorial{integer}",
|
|
416
|
+
"gcd{integer, ...}, lcm{integer, ...}, hypot{number, ...}, pow{x, y}",
|
|
417
|
+
"sin{radians}, cos{radians}, tan{radians}, asin{x}, acos{x}, atan{x}, atan2{y, x}",
|
|
418
|
+
"log{x}, log10{x}, log2{x}, exp{x}, degrees{radians}, radians{degrees}",
|
|
419
|
+
"isfinite{x}, isinf{x}, isnan{x}, isclose{x, y}",
|
|
420
|
+
"Example: math::sqrt{81} # 9.0",
|
|
421
|
+
],
|
|
422
|
+
),
|
|
423
|
+
"math::statistics": (
|
|
424
|
+
"Descriptive statistics for numeric data.",
|
|
425
|
+
[
|
|
426
|
+
"mean{values...}, median{values...}, median_low{values...}, median_high{values...}",
|
|
427
|
+
"mode{values...}, multimode{values...}, quantiles{values...}",
|
|
428
|
+
"variance{values...}, stdev{values...}, pvariance{values...}, pstdev{values...}",
|
|
429
|
+
"harmonic_mean{values...}, geometric_mean{values...}",
|
|
430
|
+
"Example: math::statistics::mean{2, 4, 6} # 4",
|
|
431
|
+
],
|
|
432
|
+
),
|
|
433
|
+
"text": (
|
|
434
|
+
"String transformations, searching, splitting, and regular expressions.",
|
|
435
|
+
[
|
|
436
|
+
"upper{text}, lower{text}, title{text}, capitalize{text}, casefold{text}",
|
|
437
|
+
"strip{text, chars?}, lstrip{text}, rstrip{text}, reverse{text}",
|
|
438
|
+
"startswith{text, prefix}, endswith{text, suffix}, replace{text, old, new}",
|
|
439
|
+
"split{text, separator?}, join{separator, values...}, count{text, substring}, find{text, substring}",
|
|
440
|
+
"isalpha{text}, isdigit{text}, isalnum{text}, isspace{text}",
|
|
441
|
+
"regex_search{pattern, text}, regex_findall{pattern, text}, regex_replace{pattern, replacement, text}",
|
|
442
|
+
'Example: text::upper{"hello"} # HELLO',
|
|
443
|
+
],
|
|
444
|
+
),
|
|
445
|
+
"collections": (
|
|
446
|
+
"Collection helpers for sequences and iterables.",
|
|
447
|
+
[
|
|
448
|
+
"unique{values...}, flatten{groups...}, zip{values...}, pairwise{values...}",
|
|
449
|
+
"frequencies{values...}, group_count{values...}, product{numbers...}",
|
|
450
|
+
"sorted{values...}, reversed{values...}",
|
|
451
|
+
'Example: collections::frequencies{"red", "blue", "red"}',
|
|
452
|
+
],
|
|
453
|
+
),
|
|
454
|
+
"datetime": (
|
|
455
|
+
"Date and time values, ISO parsing, timestamps, and date arithmetic.",
|
|
456
|
+
[
|
|
457
|
+
"today{}, now{}, utcnow{}",
|
|
458
|
+
"date{year, month, day}, time{hour, minute?, second?, microsecond?}",
|
|
459
|
+
"timestamp{iso_datetime}, from_timestamp{seconds}, weekday{iso_date}",
|
|
460
|
+
"date_add_days{iso_date, days}, date_difference_days{date1, date2}",
|
|
461
|
+
'Example: datetime::date_add_days{"2026-10-20", 7}',
|
|
462
|
+
],
|
|
463
|
+
),
|
|
464
|
+
"json": (
|
|
465
|
+
"JSON serialization, parsing, and decoded value type inspection.",
|
|
466
|
+
[
|
|
467
|
+
"dumps{value}, dumps_pretty{value}, loads{json_text}, json_type{json_text}",
|
|
468
|
+
'Example: json::loads{"{\\"formula\\": \\"H2O\\"}"}',
|
|
469
|
+
],
|
|
470
|
+
),
|
|
471
|
+
"random": (
|
|
472
|
+
"Pseudo-random values and sequence sampling using Python's random module.",
|
|
473
|
+
[
|
|
474
|
+
"seed{value}, random{}, randint{minimum, maximum}, randrange{stop} or randrange{start, stop, step}",
|
|
475
|
+
"choice{values}, sample{values, count}, shuffle{values}, uniform{minimum, maximum}",
|
|
476
|
+
"Use seed{value} when repeatable pseudo-random output is required.",
|
|
477
|
+
"Example: random::randint{1, 6}",
|
|
478
|
+
],
|
|
479
|
+
),
|
|
480
|
+
"decimal": (
|
|
481
|
+
"Base-10 decimal arithmetic, useful when decimal rounding matters.",
|
|
482
|
+
[
|
|
483
|
+
"create{value}, add{x, y}, subtract{x, y}, multiply{x, y}, divide{x, y}",
|
|
484
|
+
"quantize{value, quantum}, to_string{value}",
|
|
485
|
+
'Pass decimal values as strings for exact input, e.g. decimal::add{"0.1", "0.2"}.',
|
|
486
|
+
],
|
|
487
|
+
),
|
|
488
|
+
"fractions": (
|
|
489
|
+
"Rational arithmetic using reduced fractions.",
|
|
490
|
+
[
|
|
491
|
+
"create{value} or create{numerator, denominator}",
|
|
492
|
+
"add{x, y}, subtract{x, y}, multiply{x, y}, divide{x, y}",
|
|
493
|
+
"numerator{value}, denominator{value}, to_float{value}",
|
|
494
|
+
"Example: fractions::create{1, 3}",
|
|
495
|
+
],
|
|
496
|
+
),
|
|
497
|
+
"pathlib": (
|
|
498
|
+
"Path inspection and UTF-8 text file operations using pathlib.",
|
|
499
|
+
[
|
|
500
|
+
"name{path}, stem{path}, suffix{path}, parent{path}, join{path, part...}",
|
|
501
|
+
"exists{path}, is_file{path}, is_dir{path}",
|
|
502
|
+
"read_text{path, encoding?}, write_text{path, content, encoding?}",
|
|
503
|
+
"File reads and writes operate with the current process permissions.",
|
|
504
|
+
],
|
|
505
|
+
),
|
|
506
|
+
}
|
|
507
|
+
|
|
410
508
|
|
|
411
509
|
def _help_text(topic=None):
|
|
412
510
|
if topic is None or not str(topic).strip():
|
|
@@ -415,16 +513,34 @@ def _help_text(topic=None):
|
|
|
415
513
|
for name in names:
|
|
416
514
|
category = _BUILTIN_HELP[name][0]
|
|
417
515
|
categories.setdefault(category, []).append(name)
|
|
418
|
-
lines = [f"ChemQL {__version__} help", "", "Usage: help{topic} or help topic", "Use help{function_name} for details, or help{syntax}, help{errors}, help{files}, help{chemistry}.", ""]
|
|
516
|
+
lines = [f"ChemQL {__version__} help", "", "Usage: help{topic} or help topic", "Use help{function_name} for details, or help{syntax}, help{errors}, help{files}, help{chemistry}, help{stdlib}.", ""]
|
|
419
517
|
for category, items in categories.items():
|
|
420
518
|
lines.append(f"{category}: " + ", ".join(items))
|
|
421
519
|
lines += ["", "User-defined functions:"]
|
|
422
520
|
lines += [f" {name}{{...}}" for name in sorted(FUNCTIONS)] or [" (none defined in this session)"]
|
|
423
521
|
lines += ["", "Packages: load package_name; then use package_name::function{...}."]
|
|
522
|
+
lines += ["Standard library: " + ", ".join(_STDLIB_HELP)]
|
|
424
523
|
return "\n".join(lines)
|
|
425
524
|
|
|
426
525
|
key = str(topic).strip().strip("`\"'")
|
|
427
|
-
key
|
|
526
|
+
if key == "stdlib":
|
|
527
|
+
return "\n".join([
|
|
528
|
+
"ChemQL Standard Library",
|
|
529
|
+
"Load a package with `load package_name`, then call `package_name::function{...}`.",
|
|
530
|
+
"Get module details with `help{module}` and function details with `help{module::function}`.",
|
|
531
|
+
"",
|
|
532
|
+
*[f"{name} — {details[0]}" for name, details in _STDLIB_HELP.items()],
|
|
533
|
+
])
|
|
534
|
+
resolved_key = _resolve_package_qualified_name(key)
|
|
535
|
+
if resolved_key in _STDLIB_HELP:
|
|
536
|
+
description, entries = _STDLIB_HELP[resolved_key]
|
|
537
|
+
return "\n".join([
|
|
538
|
+
f"{resolved_key} — Standard library",
|
|
539
|
+
description,
|
|
540
|
+
f"Load with: load {resolved_key}",
|
|
541
|
+
"Functions and constants:",
|
|
542
|
+
*[f" {entry}" for entry in entries],
|
|
543
|
+
])
|
|
428
544
|
if key in _BUILTIN_HELP:
|
|
429
545
|
category, signature, description, examples = _BUILTIN_HELP[key]
|
|
430
546
|
return "\n".join([f"{key} — {category}", f"Signature: {signature}", description, "", "Examples:", *[f" {line}" for line in examples]])
|
|
@@ -434,12 +550,16 @@ def _help_text(topic=None):
|
|
|
434
550
|
if key in FUNCTIONS:
|
|
435
551
|
return f"User-defined ChemQL function: {key}{{...}}\nDefined in the current session. Use `view`/`dump` where appropriate to inspect program state."
|
|
436
552
|
if "::" in key:
|
|
437
|
-
|
|
438
|
-
if
|
|
439
|
-
|
|
440
|
-
|
|
553
|
+
package, _, member = resolved_key.rpartition("::")
|
|
554
|
+
if package in _STDLIB_HELP:
|
|
555
|
+
_, entries = _STDLIB_HELP[package]
|
|
556
|
+
match = next((entry for entry in entries if entry.split("{", 1)[0].endswith(member)), None)
|
|
557
|
+
if match:
|
|
558
|
+
return f"{package}::{member} — Standard library\n{match}"
|
|
559
|
+
if resolved_key in FUNCTIONS:
|
|
560
|
+
return f"ChemQL package function: {key}{{...}}\nCanonical name: {resolved_key}"
|
|
441
561
|
return f"Package/member help is not registered for `{key}`.\nTry `load {package}` and inspect the package namespace, or use `help{{{package}}}` if documented."
|
|
442
|
-
return f"No help available for `{key}`. Try `help` to list built-ins, or `help{{syntax}}`, `help{{errors}}`, `help{{files}}`, and `help{{chemistry}}`."
|
|
562
|
+
return f"No help available for `{key}`. Try `help` to list built-ins, `help{{stdlib}}` for standard-library packages, or `help{{syntax}}`, `help{{errors}}`, `help{{files}}`, and `help{{chemistry}}`."
|
|
443
563
|
|
|
444
564
|
|
|
445
565
|
def _call_builtin(name, arguments):
|
|
@@ -587,7 +707,13 @@ def _call_function(name, arguments):
|
|
|
587
707
|
return result
|
|
588
708
|
python_globals["error"] = None
|
|
589
709
|
except Exception as exc:
|
|
590
|
-
python_globals["error"] =
|
|
710
|
+
python_globals["error"] = (
|
|
711
|
+
exc.error_type
|
|
712
|
+
if isinstance(exc, ChemQLSignal)
|
|
713
|
+
else "ChemQLError"
|
|
714
|
+
if isinstance(exc, ChemQLError)
|
|
715
|
+
else type(exc).__name__
|
|
716
|
+
)
|
|
591
717
|
if sections["fix"]:
|
|
592
718
|
result = execute_function_lines(sections["fix"])
|
|
593
719
|
if result is not _NO_RETURN:
|
|
@@ -822,6 +948,9 @@ def _expand_function_calls(text):
|
|
|
822
948
|
continue
|
|
823
949
|
if ch in ("'", '"'):
|
|
824
950
|
quote = ch; i += 1; continue
|
|
951
|
+
if i and re.match(r"[A-Za-z0-9_.:]", text[i - 1]):
|
|
952
|
+
i += 1
|
|
953
|
+
continue
|
|
825
954
|
match = re.match(r"([A-Za-z_][A-Za-z0-9_]*(?:::[A-Za-z_][A-Za-z0-9_]*)*(?:\.[A-Za-z_][A-Za-z0-9_]*)?)\{", text[i:])
|
|
826
955
|
if match:
|
|
827
956
|
name = match.group(1)
|
|
@@ -835,9 +964,18 @@ def _expand_function_calls(text):
|
|
|
835
964
|
value = _call_function(resolved_name, args)
|
|
836
965
|
elif "::" not in resolved_name and resolved_name in BUILTIN_FUNCTIONS:
|
|
837
966
|
value = _call_builtin(resolved_name, args)
|
|
967
|
+
elif (
|
|
968
|
+
"::" not in resolved_name
|
|
969
|
+
and isinstance(python_globals.get(resolved_name), type)
|
|
970
|
+
):
|
|
971
|
+
replacement = (
|
|
972
|
+
f"{resolved_name}({text[opening + 1:end]})"
|
|
973
|
+
)
|
|
974
|
+
text = text[:i] + replacement + text[end + 1:]
|
|
975
|
+
changed = True
|
|
976
|
+
break
|
|
838
977
|
else:
|
|
839
|
-
|
|
840
|
-
continue
|
|
978
|
+
raise ChemQLError(f"Unknown function `{name}`")
|
|
841
979
|
replacement = repr(value) if isinstance(value, str) else ("None" if value is None else str(value))
|
|
842
980
|
text = text[:i] + replacement + text[end + 1:]
|
|
843
981
|
changed = True
|
|
@@ -1391,10 +1529,17 @@ class ChemQLError(Exception):
|
|
|
1391
1529
|
self.length = length
|
|
1392
1530
|
|
|
1393
1531
|
|
|
1532
|
+
class _ChemQLUnknownCommand(ChemQLError):
|
|
1533
|
+
pass
|
|
1534
|
+
|
|
1535
|
+
|
|
1394
1536
|
_CHEMQL_RISK_DEPTH = 0
|
|
1395
1537
|
|
|
1396
1538
|
|
|
1397
1539
|
def format_error(message, text=None, position=None, length=1):
|
|
1540
|
+
if _CHEMQL_RISK_DEPTH:
|
|
1541
|
+
raise ChemQLError(message, position, length)
|
|
1542
|
+
|
|
1398
1543
|
output = ""
|
|
1399
1544
|
if (text is not None) and (position is not None) and (not NO_ANSI):
|
|
1400
1545
|
output += f"\033[1;31m{' ' * (position+4)}{'^' * max(1, length)}\n\033[0m"
|
|
@@ -4084,14 +4229,20 @@ def process_inline(text):
|
|
|
4084
4229
|
return evaluate_python(stripped)
|
|
4085
4230
|
|
|
4086
4231
|
# A standalone user-defined function call returns its value directly.
|
|
4087
|
-
function_call = re.
|
|
4232
|
+
function_call = re.match(
|
|
4233
|
+
r"([A-Za-z_][A-Za-z0-9_]*(?:::[A-Za-z_][A-Za-z0-9_]*)*)\s*\{",
|
|
4234
|
+
stripped,
|
|
4235
|
+
)
|
|
4088
4236
|
if function_call:
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
if
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4237
|
+
opening = function_call.end() - 1
|
|
4238
|
+
closing = find_matching(stripped, opening, "{", "}")
|
|
4239
|
+
if not stripped[closing + 1:].strip():
|
|
4240
|
+
function_name = _resolve_package_qualified_name(function_call.group(1))
|
|
4241
|
+
arguments = _split_function_arguments(stripped[opening + 1:closing])
|
|
4242
|
+
if function_name in FUNCTIONS:
|
|
4243
|
+
return _call_function(function_name, arguments)
|
|
4244
|
+
if "::" not in function_name and function_name in BUILTIN_FUNCTIONS:
|
|
4245
|
+
return _call_builtin(function_name, arguments)
|
|
4095
4246
|
|
|
4096
4247
|
# [ ... ] is Python.
|
|
4097
4248
|
if stripped.startswith("["):
|
|
@@ -4134,11 +4285,33 @@ def process_inline(text):
|
|
|
4134
4285
|
except (SyntaxError, NameError):
|
|
4135
4286
|
pass
|
|
4136
4287
|
|
|
4288
|
+
# Function calls can compose into ordinary Python expressions, e.g.
|
|
4289
|
+
# `int{1} + int{1}`. Expand them before deciding whether this is a query;
|
|
4290
|
+
# otherwise the query parser sees the resulting `1 + 1` as a command.
|
|
4291
|
+
if re.search(
|
|
4292
|
+
r"(?<![A-Za-z0-9_.:])[A-Za-z_][A-Za-z0-9_]*(?:::[A-Za-z_][A-Za-z0-9_]*)*(?:\.[A-Za-z_][A-Za-z0-9_]*)?\s*\{",
|
|
4293
|
+
stripped,
|
|
4294
|
+
):
|
|
4295
|
+
expanded = _expand_function_calls(stripped)
|
|
4296
|
+
try:
|
|
4297
|
+
ast.parse(expanded, mode="eval")
|
|
4298
|
+
except SyntaxError:
|
|
4299
|
+
stripped = expanded
|
|
4300
|
+
else:
|
|
4301
|
+
return evaluate_python(expanded)
|
|
4302
|
+
|
|
4137
4303
|
# Try ChemQL first. If the parser specifically reports an unknown command,
|
|
4138
4304
|
# treat the input as a possible Python expression. Only Python SyntaxError
|
|
4139
4305
|
# or NameError falls back to the original ChemQL error; other Python errors
|
|
4140
4306
|
# (e.g. TypeError) are meaningful and should remain visible.
|
|
4141
|
-
|
|
4307
|
+
try:
|
|
4308
|
+
result = execute_query_text(stripped)
|
|
4309
|
+
except _ChemQLUnknownCommand as error:
|
|
4310
|
+
try:
|
|
4311
|
+
return evaluate_python(stripped)
|
|
4312
|
+
except (SyntaxError, NameError):
|
|
4313
|
+
raise error
|
|
4314
|
+
|
|
4142
4315
|
plain_result = strip_ansi(result) if isinstance(result, str) else ""
|
|
4143
4316
|
# The query engine formats unknown-command failures as text (including a
|
|
4144
4317
|
# caret line), so detect the diagnostic independently of any leading ANSI
|
|
@@ -4504,11 +4677,11 @@ def process_lines(lines):
|
|
|
4504
4677
|
field_line = remove_comments(field_line).strip()
|
|
4505
4678
|
if not field_line:
|
|
4506
4679
|
continue
|
|
4507
|
-
required_match = re.fullmatch(r"required\s+([A-Za-z_]\w*)", field_line, re.IGNORECASE)
|
|
4680
|
+
required_match = re.fullmatch(r"(?:required\s+([A-Za-z_]\w*)|([A-Za-z_]\w*)!)", field_line, re.IGNORECASE)
|
|
4508
4681
|
if required_match:
|
|
4509
4682
|
if kind != "struct":
|
|
4510
4683
|
raise SyntaxError("`required` fields are supported only in structs")
|
|
4511
|
-
field = required_match.group(1)
|
|
4684
|
+
field = required_match.group(1) if required_match.group(1) is not None else required_match.group(2)
|
|
4512
4685
|
default = None
|
|
4513
4686
|
is_required = True
|
|
4514
4687
|
else:
|
|
@@ -4624,7 +4797,13 @@ def process_lines(lines):
|
|
|
4624
4797
|
process_lines(sections["risk"])
|
|
4625
4798
|
python_globals["error"] = None
|
|
4626
4799
|
except Exception as exc:
|
|
4627
|
-
python_globals["error"] =
|
|
4800
|
+
python_globals["error"] = (
|
|
4801
|
+
exc.error_type
|
|
4802
|
+
if isinstance(exc, ChemQLSignal)
|
|
4803
|
+
else "ChemQLError"
|
|
4804
|
+
if isinstance(exc, ChemQLError)
|
|
4805
|
+
else type(exc).__name__
|
|
4806
|
+
)
|
|
4628
4807
|
if sections["fix"]:
|
|
4629
4808
|
process_lines(sections["fix"])
|
|
4630
4809
|
elif not isinstance(exc, ChemQLSignal):
|
|
@@ -4858,15 +5037,7 @@ def process_lines(lines):
|
|
|
4858
5037
|
i += 1
|
|
4859
5038
|
continue
|
|
4860
5039
|
|
|
4861
|
-
# Python imports belong inside Python blocks, e.g. `[import math]`.
|
|
4862
5040
|
top_level_statement = remove_comments(_expand_definitions(raw_line)).strip()
|
|
4863
|
-
if top_level_statement.startswith("import ") or top_level_statement.startswith("from "):
|
|
4864
|
-
message = "Python imports must be inside a Python block, e.g. `[import math]`; use `load` for ChemQL packages."
|
|
4865
|
-
print(f"Error: {message}", file=sys.stderr)
|
|
4866
|
-
_record_execution(top_level_statement, message, False)
|
|
4867
|
-
i += 1
|
|
4868
|
-
continue
|
|
4869
|
-
|
|
4870
5041
|
# ChemQL package loading is separate from Python imports.
|
|
4871
5042
|
load_statement = top_level_statement
|
|
4872
5043
|
if load_statement == "load" or load_statement.startswith("load "):
|
|
@@ -4943,13 +5114,7 @@ def process_lines(lines):
|
|
|
4943
5114
|
result = _dump_history(command)
|
|
4944
5115
|
else:
|
|
4945
5116
|
result = process_inline(command)
|
|
4946
|
-
|
|
4947
|
-
# of raising. Inside `risk`, promote that result to an exception
|
|
4948
|
-
# so `fix` runs and the `error` variable is populated.
|
|
4949
|
-
if _CHEMQL_RISK_DEPTH and isinstance(result, str):
|
|
4950
|
-
plain_result = strip_ansi(result).lstrip()
|
|
4951
|
-
if plain_result.startswith("Error:"):
|
|
4952
|
-
raise ChemQLError(plain_result[6:].strip())
|
|
5117
|
+
|
|
4953
5118
|
rendered = "" if result is None else str(result)
|
|
4954
5119
|
if result is not None:
|
|
4955
5120
|
if NO_ANSI:
|
|
@@ -5568,14 +5733,20 @@ def execute(arguments, query_text=None, positions=None):
|
|
|
5568
5733
|
|
|
5569
5734
|
if query_text is not None and positions:
|
|
5570
5735
|
pos, length = error_position(positions, 0)
|
|
5736
|
+
message = f"Unknown command `{arguments[0]}`"
|
|
5737
|
+
if _CHEMQL_RISK_DEPTH:
|
|
5738
|
+
raise _ChemQLUnknownCommand(message, pos, length)
|
|
5571
5739
|
return format_error(
|
|
5572
|
-
(
|
|
5740
|
+
(message if NO_ANSI else f"Unknown command \033[1m{arguments[0]}\033[22m"),
|
|
5573
5741
|
query_text,
|
|
5574
5742
|
pos,
|
|
5575
5743
|
length
|
|
5576
5744
|
)
|
|
5577
5745
|
|
|
5578
|
-
|
|
5746
|
+
message = f"Unknown command `{arguments[0]}`"
|
|
5747
|
+
if _CHEMQL_RISK_DEPTH:
|
|
5748
|
+
raise _ChemQLUnknownCommand(message)
|
|
5749
|
+
return format_error(message if NO_ANSI else f"Unknown command \033[1m{arguments[0]}\033[22m")
|
|
5579
5750
|
|
|
5580
5751
|
|
|
5581
5752
|
HELP = (
|
|
@@ -5594,6 +5765,9 @@ HELP = (
|
|
|
5594
5765
|
" \x1b[92mbalance H2 + O2 -> H2O \x1b[0m \x1b[90m│\x1b[0m Balance a chemical equation\n"
|
|
5595
5766
|
" \x1b[92mhelp or ? \x1b[0m \x1b[90m│\x1b[0m Display this help message\n"
|
|
5596
5767
|
" \x1b[92mhelp{read_csv} \x1b[0m \x1b[90m│\x1b[0m Show detailed built-in documentation\n"
|
|
5768
|
+
" \x1b[92mhelp{stdlib} \x1b[0m \x1b[90m│\x1b[0m List standard-library packages\n"
|
|
5769
|
+
" \x1b[92mhelp{math} \x1b[0m \x1b[90m│\x1b[0m Show package documentation\n"
|
|
5770
|
+
" \x1b[92mhelp{math::sqrt} \x1b[0m \x1b[90m│\x1b[0m Show package function help\n"
|
|
5597
5771
|
" \x1b[92mversion \x1b[0m \x1b[90m│\x1b[0m Display the installed version\n"
|
|
5598
5772
|
" \x1b[92mexit \x1b[0m \x1b[90m│\x1b[0m Exit the interactive prompt\n"
|
|
5599
5773
|
"\n"
|
|
@@ -5765,7 +5939,7 @@ class ChemqlLexer(Lexer):
|
|
|
5765
5939
|
"view", "help", "dump", "version", "return", "union", "intersection",
|
|
5766
5940
|
"let", "unset", "variables", "macros", "definitions", "arguments",
|
|
5767
5941
|
"define", "func", "struct", "record", "risk", "fix", "cleanup",
|
|
5768
|
-
"signal", "import", "remove", "
|
|
5942
|
+
"signal", "import", "remove", "load"
|
|
5769
5943
|
}
|
|
5770
5944
|
OPERATORS = {
|
|
5771
5945
|
"and", "or", "has", "like", "like!", "likep", "sort", "limit",
|
|
@@ -5956,7 +6130,7 @@ class ChemqlCompleter(Completer):
|
|
|
5956
6130
|
"endfor", "endwhile", "endif", "endrisk", "endstruct", "endrecord", "endfunc", "enddefine",
|
|
5957
6131
|
)
|
|
5958
6132
|
QUERY_OPERATORS = ["=", "!=", ">", "<", ">=", "<=", "like", "like!", "likep", "has", "sort", "limit", "count", "asc", "desc", "return", "and", "or", "union", "intersection"]
|
|
5959
|
-
CONTROL_COMPLETIONS = ["if ", "for ", "while ", "endfor", "endwhile", "endif", "risk", "fix", "cleanup", "endrisk", "
|
|
6133
|
+
CONTROL_COMPLETIONS = ["if ", "for ", "while ", "endfor", "endwhile", "endif", "risk", "fix", "cleanup", "endrisk", "endstruct", "record ", "endrecord", "func ", "endfunc", "define ", "enddefine"]
|
|
5960
6134
|
|
|
5961
6135
|
@staticmethod
|
|
5962
6136
|
def _names():
|
|
@@ -6550,7 +6724,37 @@ def run_interactive():
|
|
|
6550
6724
|
break
|
|
6551
6725
|
if not lines:
|
|
6552
6726
|
continue
|
|
6727
|
+
# Collect risk/fix/cleanup blocks as complete REPL units.
|
|
6728
|
+
if lines and lines[0].strip().casefold() == "risk":
|
|
6729
|
+
depth = 0
|
|
6730
|
+
|
|
6731
|
+
for existing_line in lines:
|
|
6732
|
+
token = existing_line.strip().casefold()
|
|
6733
|
+
if token == "risk":
|
|
6734
|
+
depth += 1
|
|
6735
|
+
elif token == "endrisk":
|
|
6736
|
+
depth -= 1
|
|
6553
6737
|
|
|
6738
|
+
while depth > 0:
|
|
6739
|
+
try:
|
|
6740
|
+
next_line = session.prompt("... ")
|
|
6741
|
+
except (KeyboardInterrupt, EOFError):
|
|
6742
|
+
print("\nIncomplete risk block")
|
|
6743
|
+
lines = []
|
|
6744
|
+
break
|
|
6745
|
+
|
|
6746
|
+
added_lines = next_line.splitlines() or [next_line]
|
|
6747
|
+
lines.extend(added_lines)
|
|
6748
|
+
|
|
6749
|
+
for added_line in added_lines:
|
|
6750
|
+
token = added_line.strip().casefold()
|
|
6751
|
+
if token == "risk":
|
|
6752
|
+
depth += 1
|
|
6753
|
+
elif token == "endrisk":
|
|
6754
|
+
depth -= 1
|
|
6755
|
+
|
|
6756
|
+
if not lines:
|
|
6757
|
+
continue
|
|
6554
6758
|
# Collect new multiline Python, terminal, and comment blocks in the REPL.
|
|
6555
6759
|
if lines and lines[0].strip() in {"[[[", "```", "```bash", "```sh", "```cmd", "```terminal"}:
|
|
6556
6760
|
opener = lines[0].strip()
|
chemql/stdlib/README.md
CHANGED
|
@@ -11,6 +11,10 @@ directory next to the interpreter module and preserve the folder layout.
|
|
|
11
11
|
| `collections` | Uniqueness, flattening, pairing, frequency counts, product |
|
|
12
12
|
| `datetime` | Dates, timestamps, local/UTC time and date arithmetic |
|
|
13
13
|
| `json` | JSON encode/decode helpers |
|
|
14
|
+
| `random` | Pseudo-random numbers and sequence sampling |
|
|
15
|
+
| `decimal` | Base-10 decimal arithmetic and quantization |
|
|
16
|
+
| `fractions` | Exact rational numbers and fraction arithmetic |
|
|
17
|
+
| `pathlib` | Path inspection and UTF-8 text file operations |
|
|
14
18
|
|
|
15
19
|
Examples:
|
|
16
20
|
|
|
@@ -37,4 +41,28 @@ datetime::date_add_days{"2026-10-20", 7}
|
|
|
37
41
|
|
|
38
42
|
load json
|
|
39
43
|
json::loads{"{\"formula\": \"H2O\"}"}
|
|
44
|
+
|
|
45
|
+
load random
|
|
46
|
+
random::seed{42}
|
|
47
|
+
random::randint{1, 6}
|
|
48
|
+
|
|
49
|
+
load decimal
|
|
50
|
+
decimal::add{"0.1", "0.2"}
|
|
51
|
+
decimal::quantize{"3.14159", "0.01"}
|
|
52
|
+
|
|
53
|
+
load fractions
|
|
54
|
+
fractions::create{1, 3}
|
|
55
|
+
fractions::add{"1/3", "1/6"}
|
|
56
|
+
|
|
57
|
+
load pathlib
|
|
58
|
+
pathlib::name{"data/results.csv"}
|
|
59
|
+
pathlib::join{"data", "results.csv"}
|
|
40
60
|
```
|
|
61
|
+
|
|
62
|
+
Use `help{stdlib}` for the package index, `help{math}` for a module
|
|
63
|
+
overview, and `help{math::sqrt}` for function help. The help pages are
|
|
64
|
+
available whether or not a package has been loaded.
|
|
65
|
+
|
|
66
|
+
`pathlib::read_text` and `pathlib::write_text` access files using the
|
|
67
|
+
current process permissions. `random` uses Python's pseudo-random
|
|
68
|
+
generator; call `random::seed{value}` when repeatable results are needed.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# ChemQL standard library: decimal
|
|
2
|
+
[import decimal as _chemql_decimal]
|
|
3
|
+
public func create
|
|
4
|
+
return _chemql_decimal.Decimal(str(arguments[0]))
|
|
5
|
+
endfunc
|
|
6
|
+
public func add
|
|
7
|
+
return _chemql_decimal.Decimal(str(arguments[0])) + _chemql_decimal.Decimal(str(arguments[1]))
|
|
8
|
+
endfunc
|
|
9
|
+
public func subtract
|
|
10
|
+
return _chemql_decimal.Decimal(str(arguments[0])) - _chemql_decimal.Decimal(str(arguments[1]))
|
|
11
|
+
endfunc
|
|
12
|
+
public func multiply
|
|
13
|
+
return _chemql_decimal.Decimal(str(arguments[0])) * _chemql_decimal.Decimal(str(arguments[1]))
|
|
14
|
+
endfunc
|
|
15
|
+
public func divide
|
|
16
|
+
return _chemql_decimal.Decimal(str(arguments[0])) / _chemql_decimal.Decimal(str(arguments[1]))
|
|
17
|
+
endfunc
|
|
18
|
+
public func quantize
|
|
19
|
+
return _chemql_decimal.Decimal(str(arguments[0])).quantize(_chemql_decimal.Decimal(str(arguments[1])))
|
|
20
|
+
endfunc
|
|
21
|
+
public func to_string
|
|
22
|
+
return str(_chemql_decimal.Decimal(str(arguments[0])))
|
|
23
|
+
endfunc
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# ChemQL standard library: fractions
|
|
2
|
+
[import fractions as _chemql_fractions]
|
|
3
|
+
public func create
|
|
4
|
+
[if len(arguments) > 1]
|
|
5
|
+
return _chemql_fractions.Fraction(arguments[0], arguments[1])
|
|
6
|
+
[endblock]
|
|
7
|
+
return _chemql_fractions.Fraction(str(arguments[0]))
|
|
8
|
+
endfunc
|
|
9
|
+
public func add
|
|
10
|
+
return _chemql_fractions.Fraction(arguments[0]) + _chemql_fractions.Fraction(arguments[1])
|
|
11
|
+
endfunc
|
|
12
|
+
public func subtract
|
|
13
|
+
return _chemql_fractions.Fraction(arguments[0]) - _chemql_fractions.Fraction(arguments[1])
|
|
14
|
+
endfunc
|
|
15
|
+
public func multiply
|
|
16
|
+
return _chemql_fractions.Fraction(arguments[0]) * _chemql_fractions.Fraction(arguments[1])
|
|
17
|
+
endfunc
|
|
18
|
+
public func divide
|
|
19
|
+
return _chemql_fractions.Fraction(arguments[0]) / _chemql_fractions.Fraction(arguments[1])
|
|
20
|
+
endfunc
|
|
21
|
+
public func numerator
|
|
22
|
+
return _chemql_fractions.Fraction(arguments[0]).numerator
|
|
23
|
+
endfunc
|
|
24
|
+
public func denominator
|
|
25
|
+
return _chemql_fractions.Fraction(arguments[0]).denominator
|
|
26
|
+
endfunc
|
|
27
|
+
public func to_float
|
|
28
|
+
return float(_chemql_fractions.Fraction(arguments[0]))
|
|
29
|
+
endfunc
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# ChemQL standard library: pathlib
|
|
2
|
+
[import pathlib as _chemql_pathlib]
|
|
3
|
+
public func name
|
|
4
|
+
return _chemql_pathlib.Path(str(arguments[0])).name
|
|
5
|
+
endfunc
|
|
6
|
+
public func stem
|
|
7
|
+
return _chemql_pathlib.Path(str(arguments[0])).stem
|
|
8
|
+
endfunc
|
|
9
|
+
public func suffix
|
|
10
|
+
return _chemql_pathlib.Path(str(arguments[0])).suffix
|
|
11
|
+
endfunc
|
|
12
|
+
public func parent
|
|
13
|
+
return str(_chemql_pathlib.Path(str(arguments[0])).parent)
|
|
14
|
+
endfunc
|
|
15
|
+
public func join
|
|
16
|
+
return str(_chemql_pathlib.Path(str(arguments[0])).joinpath(*(str(part) for part in arguments[1:])))
|
|
17
|
+
endfunc
|
|
18
|
+
public func exists
|
|
19
|
+
return _chemql_pathlib.Path(str(arguments[0])).exists()
|
|
20
|
+
endfunc
|
|
21
|
+
public func is_file
|
|
22
|
+
return _chemql_pathlib.Path(str(arguments[0])).is_file()
|
|
23
|
+
endfunc
|
|
24
|
+
public func is_dir
|
|
25
|
+
return _chemql_pathlib.Path(str(arguments[0])).is_dir()
|
|
26
|
+
endfunc
|
|
27
|
+
public func read_text
|
|
28
|
+
[if len(arguments) > 1]
|
|
29
|
+
return _chemql_pathlib.Path(str(arguments[0])).read_text(encoding=str(arguments[1]))
|
|
30
|
+
[endblock]
|
|
31
|
+
return _chemql_pathlib.Path(str(arguments[0])).read_text(encoding="utf-8")
|
|
32
|
+
endfunc
|
|
33
|
+
public func write_text
|
|
34
|
+
[if len(arguments) > 2]
|
|
35
|
+
return _chemql_pathlib.Path(str(arguments[0])).write_text(str(arguments[1]), encoding=str(arguments[2]))
|
|
36
|
+
[endblock]
|
|
37
|
+
return _chemql_pathlib.Path(str(arguments[0])).write_text(str(arguments[1]), encoding="utf-8")
|
|
38
|
+
endfunc
|
chemql/stdlib/random.cql
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# ChemQL standard library: random
|
|
2
|
+
[import random as _chemql_random]
|
|
3
|
+
public func seed
|
|
4
|
+
_chemql_random.seed(arguments[0])
|
|
5
|
+
return None
|
|
6
|
+
endfunc
|
|
7
|
+
public func random
|
|
8
|
+
return _chemql_random.random()
|
|
9
|
+
endfunc
|
|
10
|
+
public func randint
|
|
11
|
+
return _chemql_random.randint(arguments[0], arguments[1])
|
|
12
|
+
endfunc
|
|
13
|
+
public func randrange
|
|
14
|
+
return _chemql_random.randrange(*arguments)
|
|
15
|
+
endfunc
|
|
16
|
+
public func choice
|
|
17
|
+
return _chemql_random.choice(arguments[0])
|
|
18
|
+
endfunc
|
|
19
|
+
public func sample
|
|
20
|
+
return _chemql_random.sample(arguments[0], arguments[1])
|
|
21
|
+
endfunc
|
|
22
|
+
public func shuffle
|
|
23
|
+
values = list(arguments[0])
|
|
24
|
+
_chemql_random.shuffle(values)
|
|
25
|
+
return values
|
|
26
|
+
endfunc
|
|
27
|
+
public func uniform
|
|
28
|
+
return _chemql_random.uniform(arguments[0], arguments[1])
|
|
29
|
+
endfunc
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chemql-python
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: A chemistry-focused query language and REPL
|
|
5
5
|
Author-email: Mohammad Rakibul Islam <rakibulofficial1017@gmail.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -19,7 +19,7 @@ SQL-inspired syntax, inspect elements and molecules, look up reactions,
|
|
|
19
19
|
balance equations, and combine chemistry queries with Python and shell
|
|
20
20
|
commands.
|
|
21
21
|
|
|
22
|
-
- **Version:** `1.4.
|
|
22
|
+
- **Version:** `1.4.1`
|
|
23
23
|
- **PyPI distribution:** `chemql-python`
|
|
24
24
|
- **Python import / CLI name:** `chemql`
|
|
25
25
|
- **Author:** Mohammad Rakibul Islam
|
|
@@ -522,49 +522,55 @@ ChemQL provides `risk`, `fix`, `cleanup`, and `signal` for handling errors, resp
|
|
|
522
522
|
|
|
523
523
|
### `risk`
|
|
524
524
|
|
|
525
|
-
|
|
525
|
+
`risk` executes its body. If a command raises an exception, ChemQL stores
|
|
526
|
+
the error type name in the Python variable `error` and runs the optional
|
|
527
|
+
`fix` body. A returned string that happens to contain error-looking text
|
|
528
|
+
does not trigger `fix`.
|
|
526
529
|
|
|
527
530
|
```text
|
|
528
531
|
risk
|
|
529
|
-
[result = 10 /
|
|
532
|
+
[result = 10 / 2]
|
|
533
|
+
print(result)
|
|
534
|
+
fix
|
|
535
|
+
print(error)
|
|
536
|
+
cleanup
|
|
537
|
+
print("Always runs")
|
|
530
538
|
endrisk
|
|
531
539
|
```
|
|
532
540
|
|
|
533
|
-
This illustrates the idea of protecting potentially failing code. The exact behaviour depends on ChemQL's implementation of `risk`.
|
|
534
|
-
|
|
535
541
|
### `fix`
|
|
536
542
|
|
|
537
|
-
|
|
543
|
+
The `fix` section runs only when the `risk` section raises an exception.
|
|
544
|
+
The `error` variable contains the exception type name, such as
|
|
545
|
+
`ChemQLError`, `ValueError`, or the error name carried by `signal`.
|
|
538
546
|
|
|
539
547
|
```text
|
|
540
548
|
risk
|
|
541
549
|
[result = 10 / 0]
|
|
542
550
|
fix
|
|
543
|
-
|
|
551
|
+
print("Handled:", error)
|
|
544
552
|
endrisk
|
|
545
553
|
```
|
|
546
554
|
|
|
547
|
-
This example illustrates the intended relationship between `risk` and `fix`. Check the implementation for the precise syntax and whether execution continues after an error.
|
|
548
|
-
|
|
549
555
|
### `cleanup`
|
|
550
556
|
|
|
551
|
-
|
|
557
|
+
The optional `cleanup` section runs whether the `risk` section succeeds
|
|
558
|
+
or fails, after the risk body and any fix body.
|
|
552
559
|
|
|
553
560
|
```text
|
|
554
561
|
risk
|
|
555
|
-
|
|
562
|
+
print("Performing operation")
|
|
556
563
|
fix
|
|
557
|
-
|
|
564
|
+
print("Handling an error")
|
|
558
565
|
cleanup
|
|
559
|
-
|
|
566
|
+
print("Cleaning up")
|
|
560
567
|
endrisk
|
|
561
568
|
```
|
|
562
569
|
|
|
563
|
-
Cleanup logic is useful for keeping resource-management steps separate from the main operation and its error-handling logic.
|
|
564
|
-
|
|
565
570
|
### `signal`
|
|
566
571
|
|
|
567
|
-
Use `signal` to
|
|
572
|
+
Use `signal` to raise a named ChemQL error that can be handled by
|
|
573
|
+
`fix`.
|
|
568
574
|
|
|
569
575
|
```text
|
|
570
576
|
if temperature < 0
|
|
@@ -572,19 +578,18 @@ if temperature < 0
|
|
|
572
578
|
endif
|
|
573
579
|
```
|
|
574
580
|
|
|
575
|
-
A signal can make an invalid state or failed operation explicit instead of allowing execution to proceed silently.
|
|
576
|
-
|
|
577
581
|
### Summary
|
|
578
582
|
|
|
579
583
|
| Keyword | Purpose |
|
|
580
584
|
|---|---|
|
|
581
|
-
| `risk` |
|
|
582
|
-
| `fix` |
|
|
583
|
-
| `cleanup` |
|
|
584
|
-
| `signal` | Raises
|
|
585
|
+
| `risk` | Executes the protected body |
|
|
586
|
+
| `fix` | Runs when the risk body raises an exception |
|
|
587
|
+
| `cleanup` | Runs after the risk and fix bodies |
|
|
588
|
+
| `signal` | Raises a named error |
|
|
585
589
|
| `endrisk` | Ends the risk handler |
|
|
586
590
|
|
|
587
|
-
|
|
591
|
+
Without a `fix` section, an ordinary exception propagates. `cleanup`
|
|
592
|
+
still runs during exception propagation.
|
|
588
593
|
|
|
589
594
|
## Functions, definitions, and macros
|
|
590
595
|
|
|
@@ -647,7 +652,8 @@ command lists definitions in the current session.
|
|
|
647
652
|
|
|
648
653
|
## Structs and records
|
|
649
654
|
|
|
650
|
-
ChemQL
|
|
655
|
+
ChemQL 1.4.1 supports required fields, marked with a trailing `!` or the
|
|
656
|
+
`required` keyword:
|
|
651
657
|
|
|
652
658
|
```text
|
|
653
659
|
struct Fruit
|
|
@@ -661,8 +667,8 @@ apple.name
|
|
|
661
667
|
apple.price
|
|
662
668
|
let orange = Fruit{"Orange", 20} // you can use position rather than just keyword arguments
|
|
663
669
|
let banana = Fruit{"Banana", price = 10} // or you can even mix
|
|
664
|
-
|
|
665
670
|
```
|
|
671
|
+
|
|
666
672
|
```text
|
|
667
673
|
struct Vegetable
|
|
668
674
|
name!
|
|
@@ -680,24 +686,24 @@ is fully implemented.
|
|
|
680
686
|
|
|
681
687
|
## Namespaces
|
|
682
688
|
|
|
683
|
-
Namespaces
|
|
684
|
-
|
|
685
|
-
To declare functions accessible in the global scope you must use the `public` keyword, everything is private by default, you may still add the `private` keyword for fun
|
|
686
|
-
Example
|
|
687
|
-
```text
|
|
688
|
-
namespace math
|
|
689
|
-
public let PI = 3.14159265358979
|
|
690
|
-
public let E = 2.718281828459045
|
|
691
|
-
public
|
|
692
|
-
|
|
689
|
+
Namespaces group functions, structs, records, and variables. Members are
|
|
690
|
+
private by default; use `public` to expose a member through the namespace:
|
|
693
691
|
|
|
692
|
+
```chemql
|
|
693
|
+
namespace geometry
|
|
694
|
+
public let PI = 3.141592653589793
|
|
695
|
+
public func circle_area
|
|
696
|
+
return [PI * \1 * \1]
|
|
697
|
+
endfunc
|
|
698
|
+
endnamespace
|
|
699
|
+
|
|
700
|
+
geometry::circle_area{2}
|
|
694
701
|
```
|
|
695
702
|
|
|
696
703
|
## Standard library
|
|
697
704
|
|
|
698
|
-
ChemQL loads reusable `.cql` packages with `load`. The loader searches the
|
|
699
|
-
directory and then
|
|
700
|
-
folder structure when extracting `chemql_stdlib.zip`.
|
|
705
|
+
ChemQL loads reusable `.cql` packages with `load`. The loader searches the
|
|
706
|
+
current directory first and then ChemQL's bundled `stdlib/` directory.
|
|
701
707
|
|
|
702
708
|
| Package | Purpose |
|
|
703
709
|
| --- | --- |
|
|
@@ -707,6 +713,10 @@ folder structure when extracting `chemql_stdlib.zip`.
|
|
|
707
713
|
| `collections` | `unique`, `flatten`, `zip`, `pairwise`, frequency counts, product |
|
|
708
714
|
| `datetime` | Current date/time, ISO dates, timestamps, weekday and date arithmetic |
|
|
709
715
|
| `json` | JSON serialization, pretty serialization, parsing and type inspection |
|
|
716
|
+
| `random` | Pseudo-random numbers and sequence sampling |
|
|
717
|
+
| `decimal` | Base-10 decimal arithmetic and quantization |
|
|
718
|
+
| `fractions` | Exact rational numbers and fraction arithmetic |
|
|
719
|
+
| `pathlib` | Path inspection and UTF-8 text file operations |
|
|
710
720
|
|
|
711
721
|
```chemql
|
|
712
722
|
load math
|
|
@@ -731,9 +741,30 @@ datetime::date_add_days{"2026-10-20", 7}
|
|
|
731
741
|
|
|
732
742
|
load json
|
|
733
743
|
json::loads{"{\"formula\": \"H2O\"}"}
|
|
744
|
+
|
|
745
|
+
load random
|
|
746
|
+
random::seed{42}
|
|
747
|
+
random::randint{1, 6}
|
|
748
|
+
|
|
749
|
+
load decimal
|
|
750
|
+
decimal::add{"0.1", "0.2"}
|
|
751
|
+
decimal::quantize{"3.14159", "0.01"}
|
|
752
|
+
|
|
753
|
+
load fractions
|
|
754
|
+
fractions::create{1, 3}
|
|
755
|
+
fractions::add{"1/3", "1/6"}
|
|
756
|
+
|
|
757
|
+
load pathlib
|
|
758
|
+
pathlib::name{"data/results.csv"}
|
|
759
|
+
pathlib::join{"data", "results.csv"}
|
|
734
760
|
```
|
|
735
761
|
|
|
736
762
|
These packages use Python's standard library only; they add no third-party dependencies.
|
|
763
|
+
Use `help{stdlib}` for the package index, `help{math}` for a module
|
|
764
|
+
overview, and `help{math::sqrt}` for function help. These help pages are
|
|
765
|
+
available before loading the package. `pathlib::read_text` and
|
|
766
|
+
`pathlib::write_text` access files with the permissions of the ChemQL
|
|
767
|
+
process.
|
|
737
768
|
|
|
738
769
|
## Built-in help
|
|
739
770
|
|
|
@@ -747,10 +778,14 @@ help{syntax}
|
|
|
747
778
|
help{errors}
|
|
748
779
|
help{files}
|
|
749
780
|
help{chemistry}
|
|
781
|
+
help{stdlib}
|
|
782
|
+
help{math}
|
|
783
|
+
help{math::sqrt}
|
|
750
784
|
```
|
|
751
785
|
|
|
752
|
-
Use `help{function_name}` for built-in documentation
|
|
753
|
-
|
|
786
|
+
Use `help{function_name}` for built-in documentation and
|
|
787
|
+
`help{module::function}` for standard-library function documentation. The
|
|
788
|
+
help index also lists the available standard-library packages.
|
|
754
789
|
|
|
755
790
|
## Shell integration and scripting
|
|
756
791
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
chemql/__init__.py,sha256
|
|
1
|
+
chemql/__init__.py,sha256=INIV28S5-HJkfc32Fpql9Cbn7tENAuHiPmmj69Zx6V0,272034
|
|
2
2
|
chemql/__main__.py,sha256=5eIIUjMeE90JcHhOamg7KczBDQVHjMHNNCSshBQ6buw,32
|
|
3
3
|
chemql/data/BondEnergies.json,sha256=QZN3iWZrD8125jV41EG1GiRohBtP0CzAP_EDQTfaEKc,8269
|
|
4
4
|
chemql/data/MoleculeBonds.json,sha256=Iw2shYxJScH_tI80IytBYoqVDSbgT7OSqPJUIgKZme4,84262
|
|
@@ -8,16 +8,20 @@ chemql/data/Reactions.json,sha256=BFpkKEWwLzG54ot96BVOWwU3btpimRJlOM57yGclGgc,15
|
|
|
8
8
|
chemql/data/THIRD_PARTY_LICENSES/PeriodicTableJSON.txt,sha256=9wih5q8s58qR3vJEGpooa4ESyAGZHcmGvzwMMkMHNKk,322
|
|
9
9
|
chemql/data/THIRD_PARTY_LICENSES/RHEA.txt,sha256=nXTeCP6vJBW5B_mtaVGFLr0w7vnjrSTBnv6_eOk3KKo,634
|
|
10
10
|
chemql/html/periodic_table.html,sha256=ftVgXWjRO2AsTAGEekTrvgE5ZvcwRfoby9UWoixc9Y8,361560
|
|
11
|
-
chemql/stdlib/README.md,sha256=
|
|
11
|
+
chemql/stdlib/README.md,sha256=mv-oXxTcFl1i-PiZehx9wDzY4zFwwg2UxdjPtWv14GI,2045
|
|
12
12
|
chemql/stdlib/collections.cql,sha256=MEiD1BJ11tW9Y7Fbo58xVn6VW_PmCZOZ8IZAICqPml0,804
|
|
13
13
|
chemql/stdlib/datetime.cql,sha256=_EOg9M6Gq8E8fCWrNVZZ50v5nMkOIB5_HqcLKo0Dl-Q,1252
|
|
14
|
+
chemql/stdlib/decimal.cql,sha256=m9gJdNe-D4C2qiPGzsazulPU1tc17JRFl5O7QhW5BI4,882
|
|
15
|
+
chemql/stdlib/fractions.cql,sha256=FzonkrbtCNhy2QT8wEVCyEiOh0pkIOGZNYpfHZZFrYs,1039
|
|
14
16
|
chemql/stdlib/json.cql,sha256=D-l8teKi-9F3Rk3z61WlOB4MWid7BAeR0FEMxDfH2Cs,397
|
|
15
17
|
chemql/stdlib/math.cql,sha256=2RjuZlkrgIaN74G5OkcNmtrosNI9s-eZANDjuGvPzCA,2036
|
|
18
|
+
chemql/stdlib/pathlib.cql,sha256=XkgWoPCtxW7tJKJokx52byb2RiWBKnVzG7X5uPfBFNQ,1332
|
|
19
|
+
chemql/stdlib/random.cql,sha256=Rvxbs-XP_gHUh0AmYmYl9WZXpJJJY3JpNNPn2HUobZU,742
|
|
16
20
|
chemql/stdlib/text.cql,sha256=vBHE9b5SUxTQutaqViMteZeAC580Wg39J1eExkIx4yw,2038
|
|
17
21
|
chemql/stdlib/math/statistics.cql,sha256=TVfazUbGGabUgAkLsg4LtPD8CaqZaj8mPnHbSuAmi40,1083
|
|
18
|
-
chemql_python-1.4.
|
|
19
|
-
chemql_python-1.4.
|
|
20
|
-
chemql_python-1.4.
|
|
21
|
-
chemql_python-1.4.
|
|
22
|
-
chemql_python-1.4.
|
|
23
|
-
chemql_python-1.4.
|
|
22
|
+
chemql_python-1.4.2.dist-info/licenses/LICENSE,sha256=o4ywHC0EwRy2zgIASiRD2sK3Vcsf7LBcTHjDvoJgRE0,1079
|
|
23
|
+
chemql_python-1.4.2.dist-info/METADATA,sha256=QeC1TFVbuH7-d6PHoSlXPvi52nLyza64rShUECCPjZc,27679
|
|
24
|
+
chemql_python-1.4.2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
25
|
+
chemql_python-1.4.2.dist-info/entry_points.txt,sha256=j98ObVWR743an524z24AiWeRsppag-py5cc7l0D0HkI,39
|
|
26
|
+
chemql_python-1.4.2.dist-info/top_level.txt,sha256=9UzHwnk_F6W19Q9nayaFgVyAELQXXwoYjeBgB6MWNr8,7
|
|
27
|
+
chemql_python-1.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|