snail-lang 0.5.0__tar.gz → 0.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {snail_lang-0.5.0 → snail_lang-0.5.2}/Cargo.lock +7 -7
- {snail_lang-0.5.0 → snail_lang-0.5.2}/PKG-INFO +1 -1
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-ast/Cargo.toml +1 -1
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-core/Cargo.toml +1 -1
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-error/Cargo.toml +1 -1
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-lower/Cargo.toml +1 -1
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/Cargo.toml +1 -1
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-python/Cargo.toml +1 -1
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-python/src/lib.rs +74 -8
- {snail_lang-0.5.0 → snail_lang-0.5.2}/pyproject.toml +1 -1
- snail_lang-0.5.2/python/snail/__init__.py +25 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/python/snail/cli.py +103 -33
- snail_lang-0.5.2/python/snail/runtime/__init__.py +167 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/python/snail/runtime/structured_accessor.py +3 -4
- snail_lang-0.5.0/python/snail/__init__.py +0 -10
- snail_lang-0.5.0/python/snail/runtime/__init__.py +0 -75
- {snail_lang-0.5.0 → snail_lang-0.5.2}/Cargo.toml +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/LICENSE +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/README.md +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-ast/README.md +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-ast/src/ast.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-ast/src/awk.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-ast/src/lib.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-core/README.md +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-core/src/lib.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-error/README.md +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-error/src/lib.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-lower/README.md +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-lower/src/awk.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-lower/src/constants.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-lower/src/expr.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-lower/src/helpers.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-lower/src/lib.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-lower/src/operators.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-lower/src/program.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-lower/src/py_ast.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-lower/src/stmt.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/README.md +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/src/awk.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/src/expr.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/src/lib.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/src/literal.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/src/snail.pest +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/src/stmt.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/src/string.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/src/util.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/tests/common.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/tests/errors.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/tests/parser.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/tests/statements.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/tests/syntax_expressions.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-parser/tests/syntax_strings.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/crates/snail-python/build.rs +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/python/snail/runtime/compact_try.py +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/python/snail/runtime/regex.py +0 -0
- {snail_lang-0.5.0 → snail_lang-0.5.2}/python/snail/runtime/subprocess.py +0 -0
|
@@ -485,11 +485,11 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
|
|
485
485
|
|
|
486
486
|
[[package]]
|
|
487
487
|
name = "snail-ast"
|
|
488
|
-
version = "0.5.
|
|
488
|
+
version = "0.5.2"
|
|
489
489
|
|
|
490
490
|
[[package]]
|
|
491
491
|
name = "snail-core"
|
|
492
|
-
version = "0.5.
|
|
492
|
+
version = "0.5.2"
|
|
493
493
|
dependencies = [
|
|
494
494
|
"pyo3",
|
|
495
495
|
"snail-ast",
|
|
@@ -500,14 +500,14 @@ dependencies = [
|
|
|
500
500
|
|
|
501
501
|
[[package]]
|
|
502
502
|
name = "snail-error"
|
|
503
|
-
version = "0.5.
|
|
503
|
+
version = "0.5.2"
|
|
504
504
|
dependencies = [
|
|
505
505
|
"snail-ast",
|
|
506
506
|
]
|
|
507
507
|
|
|
508
508
|
[[package]]
|
|
509
509
|
name = "snail-lower"
|
|
510
|
-
version = "0.5.
|
|
510
|
+
version = "0.5.2"
|
|
511
511
|
dependencies = [
|
|
512
512
|
"pyo3",
|
|
513
513
|
"snail-ast",
|
|
@@ -516,7 +516,7 @@ dependencies = [
|
|
|
516
516
|
|
|
517
517
|
[[package]]
|
|
518
518
|
name = "snail-parser"
|
|
519
|
-
version = "0.5.
|
|
519
|
+
version = "0.5.2"
|
|
520
520
|
dependencies = [
|
|
521
521
|
"pest",
|
|
522
522
|
"pest_derive",
|
|
@@ -526,7 +526,7 @@ dependencies = [
|
|
|
526
526
|
|
|
527
527
|
[[package]]
|
|
528
528
|
name = "snail-proptest"
|
|
529
|
-
version = "0.5.
|
|
529
|
+
version = "0.5.2"
|
|
530
530
|
dependencies = [
|
|
531
531
|
"proptest",
|
|
532
532
|
"pyo3",
|
|
@@ -540,7 +540,7 @@ dependencies = [
|
|
|
540
540
|
|
|
541
541
|
[[package]]
|
|
542
542
|
name = "snail-python"
|
|
543
|
-
version = "0.5.
|
|
543
|
+
version = "0.5.2"
|
|
544
544
|
dependencies = [
|
|
545
545
|
"pyo3",
|
|
546
546
|
"snail-core",
|
|
@@ -8,9 +8,25 @@ use snail_core::{
|
|
|
8
8
|
CompileMode, compile_snail_source_with_auto_print, format_snail_error, parse_awk_program,
|
|
9
9
|
parse_program,
|
|
10
10
|
};
|
|
11
|
+
use std::sync::OnceLock;
|
|
12
|
+
use std::time::Instant;
|
|
11
13
|
|
|
12
14
|
const SNAIL_TRACE_PREFIX: &str = "snail:";
|
|
13
15
|
|
|
16
|
+
fn profile_enabled() -> bool {
|
|
17
|
+
static ENABLED: OnceLock<bool> = OnceLock::new();
|
|
18
|
+
*ENABLED.get_or_init(|| std::env::var_os("SNAIL_PROFILE_NATIVE").is_some())
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fn log_profile(label: &str, elapsed: std::time::Duration) {
|
|
22
|
+
if profile_enabled() {
|
|
23
|
+
eprintln!(
|
|
24
|
+
"[snail][native] {label}: {:.3} ms",
|
|
25
|
+
elapsed.as_secs_f64() * 1000.0
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
fn parse_mode(mode: &str) -> PyResult<CompileMode> {
|
|
15
31
|
match mode {
|
|
16
32
|
"snail" => Ok(CompileMode::Snail),
|
|
@@ -89,12 +105,23 @@ fn compile_source(
|
|
|
89
105
|
auto_print: bool,
|
|
90
106
|
filename: &str,
|
|
91
107
|
) -> Result<PyObject, PyErr> {
|
|
108
|
+
let profile = profile_enabled();
|
|
109
|
+
let total_start = Instant::now();
|
|
110
|
+
let compile_start = Instant::now();
|
|
92
111
|
let module = compile_snail_source_with_auto_print(py, source, mode, auto_print)
|
|
93
112
|
.map_err(|err| PySyntaxError::new_err(format_snail_error(&err, filename)))?;
|
|
113
|
+
if profile {
|
|
114
|
+
log_profile("compile_snail_source", compile_start.elapsed());
|
|
115
|
+
}
|
|
116
|
+
let ast_start = Instant::now();
|
|
94
117
|
let ast = py.import_bound("ast")?;
|
|
95
118
|
let fixed = ast
|
|
96
119
|
.getattr("fix_missing_locations")?
|
|
97
120
|
.call1((module.clone_ref(py),))?;
|
|
121
|
+
if profile {
|
|
122
|
+
log_profile("fix_missing_locations", ast_start.elapsed());
|
|
123
|
+
log_profile("compile_source_total", total_start.elapsed());
|
|
124
|
+
}
|
|
98
125
|
Ok(fixed.into_py(py))
|
|
99
126
|
}
|
|
100
127
|
|
|
@@ -135,14 +162,25 @@ fn compile_py(
|
|
|
135
162
|
auto_print: bool,
|
|
136
163
|
filename: &str,
|
|
137
164
|
) -> PyResult<PyObject> {
|
|
165
|
+
let profile = profile_enabled();
|
|
166
|
+
let total_start = Instant::now();
|
|
138
167
|
let mode = parse_mode(mode)?;
|
|
139
168
|
let python_ast = compile_source(py, source, mode, auto_print, filename)?;
|
|
140
169
|
let display = display_filename(filename);
|
|
170
|
+
let linecache_start = Instant::now();
|
|
141
171
|
register_linecache(py, &display, source)?;
|
|
172
|
+
if profile {
|
|
173
|
+
log_profile("register_linecache", linecache_start.elapsed());
|
|
174
|
+
}
|
|
175
|
+
let compile_start = Instant::now();
|
|
142
176
|
let builtins = py.import_bound("builtins")?;
|
|
143
177
|
let code = builtins
|
|
144
178
|
.getattr("compile")?
|
|
145
179
|
.call1((python_ast, display, "exec"))?;
|
|
180
|
+
if profile {
|
|
181
|
+
log_profile("py_compile", compile_start.elapsed());
|
|
182
|
+
log_profile("compile_py_total", total_start.elapsed());
|
|
183
|
+
}
|
|
146
184
|
Ok(code.unbind())
|
|
147
185
|
}
|
|
148
186
|
|
|
@@ -171,33 +209,56 @@ fn exec_py(
|
|
|
171
209
|
auto_import: bool,
|
|
172
210
|
filename: &str,
|
|
173
211
|
) -> PyResult<i32> {
|
|
212
|
+
let profile = profile_enabled();
|
|
213
|
+
let total_start = Instant::now();
|
|
174
214
|
let mode = parse_mode(mode)?;
|
|
175
215
|
let python_ast = compile_source(py, source, mode, auto_print, filename)?;
|
|
176
216
|
let display = display_filename(filename);
|
|
217
|
+
let linecache_start = Instant::now();
|
|
177
218
|
register_linecache(py, &display, source)?;
|
|
219
|
+
if profile {
|
|
220
|
+
log_profile("register_linecache", linecache_start.elapsed());
|
|
221
|
+
}
|
|
222
|
+
let compile_start = Instant::now();
|
|
178
223
|
let builtins = py.import_bound("builtins")?;
|
|
179
224
|
let code = builtins
|
|
180
225
|
.getattr("compile")?
|
|
181
226
|
.call1((python_ast, display, "exec"))?;
|
|
227
|
+
if profile {
|
|
228
|
+
log_profile("py_compile", compile_start.elapsed());
|
|
229
|
+
}
|
|
230
|
+
let globals_start = Instant::now();
|
|
182
231
|
let globals = prepare_globals(py, strip_display_prefix(filename), &argv, auto_import)?;
|
|
232
|
+
if profile {
|
|
233
|
+
log_profile("prepare_globals", globals_start.elapsed());
|
|
234
|
+
}
|
|
183
235
|
|
|
184
|
-
|
|
236
|
+
let exec_start = Instant::now();
|
|
237
|
+
let exec_result = builtins.getattr("exec")?.call1((code.as_any(), &globals));
|
|
238
|
+
if profile {
|
|
239
|
+
log_profile("py_exec", exec_start.elapsed());
|
|
240
|
+
}
|
|
241
|
+
let result = match exec_result {
|
|
185
242
|
Ok(_) => Ok(0),
|
|
186
243
|
Err(err) => {
|
|
187
244
|
if err.is_instance_of::<PySystemExit>(py) {
|
|
188
245
|
let code = err.value_bound(py).getattr("code")?;
|
|
189
246
|
if code.is_none() {
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
247
|
+
Ok(0)
|
|
248
|
+
} else if let Ok(exit_code) = code.extract::<i32>() {
|
|
249
|
+
Ok(exit_code)
|
|
250
|
+
} else {
|
|
251
|
+
Ok(1)
|
|
194
252
|
}
|
|
195
|
-
Ok(1)
|
|
196
253
|
} else {
|
|
197
254
|
Err(err)
|
|
198
255
|
}
|
|
199
256
|
}
|
|
257
|
+
};
|
|
258
|
+
if profile {
|
|
259
|
+
log_profile("exec_py_total", total_start.elapsed());
|
|
200
260
|
}
|
|
261
|
+
result
|
|
201
262
|
}
|
|
202
263
|
|
|
203
264
|
#[pyfunction(name = "parse")]
|
|
@@ -214,15 +275,20 @@ fn parse_py(source: &str, mode: &str, filename: &str) -> PyResult<()> {
|
|
|
214
275
|
}
|
|
215
276
|
|
|
216
277
|
#[pymodule]
|
|
217
|
-
fn _native(
|
|
278
|
+
fn _native(py: Python<'_>, module: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
279
|
+
let profile = profile_enabled();
|
|
280
|
+
let total_start = Instant::now();
|
|
218
281
|
module.add_function(wrap_pyfunction!(compile_py, module)?)?;
|
|
219
282
|
module.add_function(wrap_pyfunction!(compile_ast_py, module)?)?;
|
|
220
283
|
module.add_function(wrap_pyfunction!(exec_py, module)?)?;
|
|
221
284
|
module.add_function(wrap_pyfunction!(parse_py, module)?)?;
|
|
222
|
-
module.add("__build_info__", build_info_dict(
|
|
285
|
+
module.add("__build_info__", build_info_dict(py)?)?;
|
|
223
286
|
module.add(
|
|
224
287
|
"__all__",
|
|
225
288
|
vec!["compile", "compile_ast", "exec", "parse", "__build_info__"],
|
|
226
289
|
)?;
|
|
290
|
+
if profile {
|
|
291
|
+
log_profile("module_init", total_start.elapsed());
|
|
292
|
+
}
|
|
227
293
|
Ok(())
|
|
228
294
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from ._native import __build_info__, compile, compile_ast, exec, parse
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _resolve_version() -> str:
|
|
5
|
+
try:
|
|
6
|
+
from importlib.metadata import version
|
|
7
|
+
|
|
8
|
+
return version("snail-lang")
|
|
9
|
+
except Exception: # pragma: no cover - during development
|
|
10
|
+
return "0.0.0"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def __getattr__(name: str):
|
|
14
|
+
if name == "__version__":
|
|
15
|
+
value = _resolve_version()
|
|
16
|
+
globals()["__version__"] = value
|
|
17
|
+
return value
|
|
18
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def __dir__() -> list[str]:
|
|
22
|
+
return sorted(list(globals().keys()) + ["__version__"])
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = ["compile", "compile_ast", "exec", "parse", "__version__", "__build_info__"]
|
|
@@ -1,23 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import argparse
|
|
4
|
-
import ast
|
|
5
|
-
import builtins
|
|
6
3
|
import os
|
|
7
4
|
import sys
|
|
8
|
-
import traceback
|
|
9
|
-
from pathlib import Path
|
|
10
5
|
|
|
11
|
-
from . import __build_info__,
|
|
6
|
+
from . import __build_info__, compile_ast, exec
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
usage="snail [options] -f <file> [args]...\n snail [options] <code> [args]...",
|
|
19
|
-
add_help=True,
|
|
20
|
-
)
|
|
8
|
+
_USAGE = (
|
|
9
|
+
"snail [options] -f <file> [args]...\n"
|
|
10
|
+
" snail [options] <code> [args]..."
|
|
11
|
+
)
|
|
12
|
+
_DESCRIPTION = "Snail programming language interpreter"
|
|
21
13
|
|
|
22
14
|
|
|
23
15
|
def _display_filename(filename: str) -> str:
|
|
@@ -26,10 +18,7 @@ def _display_filename(filename: str) -> str:
|
|
|
26
18
|
return f"snail:{filename}"
|
|
27
19
|
|
|
28
20
|
|
|
29
|
-
def _trim_internal_prefix(
|
|
30
|
-
stack: traceback.StackSummary,
|
|
31
|
-
internal_files: set[str],
|
|
32
|
-
) -> None:
|
|
21
|
+
def _trim_internal_prefix(stack, internal_files: set[str]) -> None:
|
|
33
22
|
if not stack:
|
|
34
23
|
return
|
|
35
24
|
trim_count = 0
|
|
@@ -48,10 +37,7 @@ def _trim_internal_prefix(
|
|
|
48
37
|
del stack[:trim_count]
|
|
49
38
|
|
|
50
39
|
|
|
51
|
-
def _trim_traceback_exception(
|
|
52
|
-
tb_exc: traceback.TracebackException,
|
|
53
|
-
internal_files: set[str],
|
|
54
|
-
) -> None:
|
|
40
|
+
def _trim_traceback_exception(tb_exc, internal_files: set[str]) -> None:
|
|
55
41
|
_trim_internal_prefix(tb_exc.stack, internal_files)
|
|
56
42
|
cause = getattr(tb_exc, "__cause__", None)
|
|
57
43
|
if cause is not None:
|
|
@@ -76,6 +62,8 @@ def _install_trimmed_excepthook() -> None:
|
|
|
76
62
|
) -> None:
|
|
77
63
|
if exc_type is KeyboardInterrupt:
|
|
78
64
|
return original_excepthook(exc_type, exc, tb)
|
|
65
|
+
import traceback
|
|
66
|
+
|
|
79
67
|
tb_exc = traceback.TracebackException(
|
|
80
68
|
exc_type,
|
|
81
69
|
exc,
|
|
@@ -95,6 +83,77 @@ def _install_trimmed_excepthook() -> None:
|
|
|
95
83
|
sys.excepthook = _snail_excepthook
|
|
96
84
|
|
|
97
85
|
|
|
86
|
+
class _Args:
|
|
87
|
+
def __init__(self) -> None:
|
|
88
|
+
self.file: str | None = None
|
|
89
|
+
self.awk = False
|
|
90
|
+
self.no_print = False
|
|
91
|
+
self.no_auto_import = False
|
|
92
|
+
self.debug = False
|
|
93
|
+
self.version = False
|
|
94
|
+
self.help = False
|
|
95
|
+
self.args: list[str] = []
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _print_help(file=sys.stdout) -> None:
|
|
99
|
+
print(f"usage: {_USAGE}", file=file)
|
|
100
|
+
print("", file=file)
|
|
101
|
+
print(_DESCRIPTION, file=file)
|
|
102
|
+
print("", file=file)
|
|
103
|
+
print("options:", file=file)
|
|
104
|
+
print(" -f <file> read Snail source from file", file=file)
|
|
105
|
+
print(" -a, --awk awk mode", file=file)
|
|
106
|
+
print(" -P, --no-print disable auto-print of last expression", file=file)
|
|
107
|
+
print(" -I, --no-auto-import disable auto-imports", file=file)
|
|
108
|
+
print(" --debug parse and compile, then print, do not run", file=file)
|
|
109
|
+
print(" -v, --version show version and exit", file=file)
|
|
110
|
+
print(" -h, --help show this help message and exit", file=file)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _parse_args(argv: list[str]) -> _Args:
|
|
114
|
+
args = _Args()
|
|
115
|
+
idx = 0
|
|
116
|
+
while idx < len(argv):
|
|
117
|
+
token = argv[idx]
|
|
118
|
+
if token == "--":
|
|
119
|
+
args.args = argv[idx + 1 :]
|
|
120
|
+
return args
|
|
121
|
+
if token == "-" or not token.startswith("-"):
|
|
122
|
+
args.args = argv[idx:]
|
|
123
|
+
return args
|
|
124
|
+
if token in ("-h", "--help"):
|
|
125
|
+
args.help = True
|
|
126
|
+
return args
|
|
127
|
+
if token in ("-v", "--version"):
|
|
128
|
+
args.version = True
|
|
129
|
+
idx += 1
|
|
130
|
+
continue
|
|
131
|
+
if token in ("-a", "--awk"):
|
|
132
|
+
args.awk = True
|
|
133
|
+
idx += 1
|
|
134
|
+
continue
|
|
135
|
+
if token in ("-P", "--no-print"):
|
|
136
|
+
args.no_print = True
|
|
137
|
+
idx += 1
|
|
138
|
+
continue
|
|
139
|
+
if token in ("-I", "--no-auto-import"):
|
|
140
|
+
args.no_auto_import = True
|
|
141
|
+
idx += 1
|
|
142
|
+
continue
|
|
143
|
+
if token == "--debug":
|
|
144
|
+
args.debug = True
|
|
145
|
+
idx += 1
|
|
146
|
+
continue
|
|
147
|
+
if token == "-f":
|
|
148
|
+
if idx + 1 >= len(argv):
|
|
149
|
+
raise ValueError("option -f requires an argument")
|
|
150
|
+
args.file = argv[idx + 1]
|
|
151
|
+
idx += 2
|
|
152
|
+
continue
|
|
153
|
+
raise ValueError(f"unknown option: {token}")
|
|
154
|
+
return args
|
|
155
|
+
|
|
156
|
+
|
|
98
157
|
def _format_version(version: str, build_info: dict[str, object] | None) -> str:
|
|
99
158
|
display_version = version if version.startswith("v") else f"v{version}"
|
|
100
159
|
if not build_info:
|
|
@@ -114,28 +173,36 @@ def _format_version(version: str, build_info: dict[str, object] | None) -> str:
|
|
|
114
173
|
return f"{display_version} ({git_rev})"
|
|
115
174
|
|
|
116
175
|
|
|
176
|
+
def _get_version() -> str:
|
|
177
|
+
from . import __version__ as version
|
|
178
|
+
|
|
179
|
+
return version
|
|
180
|
+
|
|
181
|
+
|
|
117
182
|
def main(argv: list[str] | None = None) -> int:
|
|
118
183
|
if argv is None:
|
|
119
184
|
_install_trimmed_excepthook()
|
|
185
|
+
argv = sys.argv[1:]
|
|
120
186
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
parser.add_argument("-v", "--version", action="store_true")
|
|
128
|
-
parser.add_argument("args", nargs=argparse.REMAINDER)
|
|
129
|
-
|
|
130
|
-
namespace = parser.parse_args(argv)
|
|
187
|
+
try:
|
|
188
|
+
namespace = _parse_args(argv)
|
|
189
|
+
except ValueError as exc:
|
|
190
|
+
_print_help(file=sys.stderr)
|
|
191
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
192
|
+
return 2
|
|
131
193
|
|
|
194
|
+
if namespace.help:
|
|
195
|
+
_print_help()
|
|
196
|
+
return 0
|
|
132
197
|
if namespace.version:
|
|
133
|
-
print(_format_version(
|
|
198
|
+
print(_format_version(_get_version(), __build_info__))
|
|
134
199
|
return 0
|
|
135
200
|
|
|
136
201
|
mode = "awk" if namespace.awk else "snail"
|
|
137
202
|
|
|
138
203
|
if namespace.file:
|
|
204
|
+
from pathlib import Path
|
|
205
|
+
|
|
139
206
|
path = Path(namespace.file)
|
|
140
207
|
try:
|
|
141
208
|
source = path.read_text()
|
|
@@ -153,6 +220,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
153
220
|
args = ["--", *namespace.args[1:]]
|
|
154
221
|
|
|
155
222
|
if namespace.debug:
|
|
223
|
+
import ast
|
|
224
|
+
import builtins
|
|
225
|
+
|
|
156
226
|
python_ast = compile_ast(
|
|
157
227
|
source,
|
|
158
228
|
mode=mode,
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
|
|
5
|
+
__all__ = ["install_helpers", "AutoImportDict", "AUTO_IMPORT_NAMES"]
|
|
6
|
+
|
|
7
|
+
# Names that can be auto-imported when first referenced.
|
|
8
|
+
# Maps name -> (module, attribute) where attribute is None for whole-module imports.
|
|
9
|
+
AUTO_IMPORT_NAMES: dict[str, tuple[str, str | None]] = {
|
|
10
|
+
# Whole module imports: import X
|
|
11
|
+
"sys": ("sys", None),
|
|
12
|
+
"os": ("os", None),
|
|
13
|
+
# Attribute imports: from X import Y
|
|
14
|
+
"Path": ("pathlib", "Path"),
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AutoImportDict(dict):
|
|
19
|
+
"""A dict subclass that lazily imports allowed names on first access.
|
|
20
|
+
|
|
21
|
+
When a key lookup fails, if the key is in AUTO_IMPORT_NAMES,
|
|
22
|
+
the corresponding module/attribute is imported and stored in the dict.
|
|
23
|
+
Supports both whole-module imports (import sys) and attribute imports
|
|
24
|
+
(from pathlib import Path).
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __missing__(self, key: str) -> object:
|
|
28
|
+
if key in AUTO_IMPORT_NAMES:
|
|
29
|
+
module_name, attr_name = AUTO_IMPORT_NAMES[key]
|
|
30
|
+
module = importlib.import_module(module_name)
|
|
31
|
+
value = getattr(module, attr_name) if attr_name else module
|
|
32
|
+
self[key] = value
|
|
33
|
+
return value
|
|
34
|
+
raise KeyError(key)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_compact_try = None
|
|
38
|
+
_regex_search = None
|
|
39
|
+
_regex_compile = None
|
|
40
|
+
_subprocess_capture = None
|
|
41
|
+
_subprocess_status = None
|
|
42
|
+
_jmespath_query = None
|
|
43
|
+
_js = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _get_compact_try():
|
|
47
|
+
global _compact_try
|
|
48
|
+
if _compact_try is None:
|
|
49
|
+
from .compact_try import compact_try
|
|
50
|
+
|
|
51
|
+
_compact_try = compact_try
|
|
52
|
+
return _compact_try
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _get_regex_search():
|
|
56
|
+
global _regex_search
|
|
57
|
+
if _regex_search is None:
|
|
58
|
+
from .regex import regex_search
|
|
59
|
+
|
|
60
|
+
_regex_search = regex_search
|
|
61
|
+
return _regex_search
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _get_regex_compile():
|
|
65
|
+
global _regex_compile
|
|
66
|
+
if _regex_compile is None:
|
|
67
|
+
from .regex import regex_compile
|
|
68
|
+
|
|
69
|
+
_regex_compile = regex_compile
|
|
70
|
+
return _regex_compile
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _get_subprocess_capture():
|
|
74
|
+
global _subprocess_capture
|
|
75
|
+
if _subprocess_capture is None:
|
|
76
|
+
from .subprocess import SubprocessCapture
|
|
77
|
+
|
|
78
|
+
_subprocess_capture = SubprocessCapture
|
|
79
|
+
return _subprocess_capture
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _get_subprocess_status():
|
|
83
|
+
global _subprocess_status
|
|
84
|
+
if _subprocess_status is None:
|
|
85
|
+
from .subprocess import SubprocessStatus
|
|
86
|
+
|
|
87
|
+
_subprocess_status = SubprocessStatus
|
|
88
|
+
return _subprocess_status
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _get_jmespath_query():
|
|
92
|
+
global _jmespath_query
|
|
93
|
+
if _jmespath_query is None:
|
|
94
|
+
from .structured_accessor import __snail_jmespath_query
|
|
95
|
+
|
|
96
|
+
_jmespath_query = __snail_jmespath_query
|
|
97
|
+
return _jmespath_query
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _get_js():
|
|
101
|
+
global _js
|
|
102
|
+
if _js is None:
|
|
103
|
+
from .structured_accessor import js
|
|
104
|
+
|
|
105
|
+
_js = js
|
|
106
|
+
return _js
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _lazy_compact_try(expr_fn, fallback_fn=None):
|
|
110
|
+
return _get_compact_try()(expr_fn, fallback_fn)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _lazy_regex_search(value, pattern):
|
|
114
|
+
return _get_regex_search()(value, pattern)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _lazy_regex_compile(pattern):
|
|
118
|
+
return _get_regex_compile()(pattern)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _lazy_subprocess_capture(cmd: str):
|
|
122
|
+
return _get_subprocess_capture()(cmd)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _lazy_subprocess_status(cmd: str):
|
|
126
|
+
return _get_subprocess_status()(cmd)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _lazy_jmespath_query(query: str):
|
|
130
|
+
return _get_jmespath_query()(query)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _lazy_js(input_data=None):
|
|
134
|
+
return _get_js()(input_data)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def __snail_partial(func, /, *args, **kwargs):
|
|
138
|
+
import functools
|
|
139
|
+
|
|
140
|
+
return functools.partial(func, *args, **kwargs)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def __snail_contains__(left, right):
|
|
144
|
+
method = getattr(right, "__snail_contains__", None)
|
|
145
|
+
if method is not None:
|
|
146
|
+
return method(left)
|
|
147
|
+
return left in right
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def __snail_contains_not__(left, right):
|
|
151
|
+
method = getattr(right, "__snail_contains__", None)
|
|
152
|
+
if method is not None:
|
|
153
|
+
return not bool(method(left))
|
|
154
|
+
return left not in right
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def install_helpers(globals_dict: dict) -> None:
|
|
158
|
+
globals_dict["__snail_compact_try"] = _lazy_compact_try
|
|
159
|
+
globals_dict["__snail_regex_search"] = _lazy_regex_search
|
|
160
|
+
globals_dict["__snail_regex_compile"] = _lazy_regex_compile
|
|
161
|
+
globals_dict["__SnailSubprocessCapture"] = _lazy_subprocess_capture
|
|
162
|
+
globals_dict["__SnailSubprocessStatus"] = _lazy_subprocess_status
|
|
163
|
+
globals_dict["__snail_jmespath_query"] = _lazy_jmespath_query
|
|
164
|
+
globals_dict["__snail_partial"] = __snail_partial
|
|
165
|
+
globals_dict["__snail_contains__"] = __snail_contains__
|
|
166
|
+
globals_dict["__snail_contains_not__"] = __snail_contains_not__
|
|
167
|
+
globals_dict["js"] = _lazy_js
|
|
@@ -4,17 +4,16 @@ import json as _json
|
|
|
4
4
|
import os as _os
|
|
5
5
|
import sys as _sys
|
|
6
6
|
|
|
7
|
-
import jmespath
|
|
8
|
-
|
|
9
|
-
|
|
10
7
|
def __snail_jmespath_query(query: str):
|
|
11
8
|
"""Create a callable that applies JMESPath query.
|
|
12
9
|
|
|
13
10
|
Used by the $[query] syntax which lowers to __snail_jmespath_query(query).
|
|
14
11
|
"""
|
|
15
12
|
|
|
13
|
+
import jmespath as _jmespath
|
|
14
|
+
|
|
16
15
|
def apply(data):
|
|
17
|
-
return
|
|
16
|
+
return _jmespath.search(query, data)
|
|
18
17
|
|
|
19
18
|
return apply
|
|
20
19
|
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
from ._native import __build_info__, compile, compile_ast, exec, parse
|
|
2
|
-
|
|
3
|
-
try:
|
|
4
|
-
from importlib.metadata import version
|
|
5
|
-
|
|
6
|
-
__version__ = version("snail-lang")
|
|
7
|
-
except Exception: # pragma: no cover - during development
|
|
8
|
-
__version__ = "0.0.0"
|
|
9
|
-
|
|
10
|
-
__all__ = ["compile", "compile_ast", "exec", "parse", "__version__", "__build_info__"]
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import functools
|
|
4
|
-
import importlib
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from .compact_try import compact_try
|
|
8
|
-
from .regex import regex_compile, regex_search
|
|
9
|
-
from .structured_accessor import (
|
|
10
|
-
__snail_jmespath_query,
|
|
11
|
-
js,
|
|
12
|
-
)
|
|
13
|
-
from .subprocess import SubprocessCapture, SubprocessStatus
|
|
14
|
-
|
|
15
|
-
__all__ = ["install_helpers", "AutoImportDict", "AUTO_IMPORT_NAMES"]
|
|
16
|
-
|
|
17
|
-
# Names that can be auto-imported when first referenced.
|
|
18
|
-
# Maps name -> (module, attribute) where attribute is None for whole-module imports.
|
|
19
|
-
AUTO_IMPORT_NAMES: dict[str, tuple[str, str | None]] = {
|
|
20
|
-
# Whole module imports: import X
|
|
21
|
-
"sys": ("sys", None),
|
|
22
|
-
"os": ("os", None),
|
|
23
|
-
# Attribute imports: from X import Y
|
|
24
|
-
"Path": ("pathlib", "Path"),
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class AutoImportDict(dict):
|
|
29
|
-
"""A dict subclass that lazily imports allowed names on first access.
|
|
30
|
-
|
|
31
|
-
When a key lookup fails, if the key is in AUTO_IMPORT_NAMES,
|
|
32
|
-
the corresponding module/attribute is imported and stored in the dict.
|
|
33
|
-
Supports both whole-module imports (import sys) and attribute imports
|
|
34
|
-
(from pathlib import Path).
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
def __missing__(self, key: str) -> Any:
|
|
38
|
-
if key in AUTO_IMPORT_NAMES:
|
|
39
|
-
module_name, attr_name = AUTO_IMPORT_NAMES[key]
|
|
40
|
-
module = importlib.import_module(module_name)
|
|
41
|
-
value = getattr(module, attr_name) if attr_name else module
|
|
42
|
-
self[key] = value
|
|
43
|
-
return value
|
|
44
|
-
raise KeyError(key)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def __snail_partial(func, /, *args, **kwargs):
|
|
48
|
-
return functools.partial(func, *args, **kwargs)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def __snail_contains__(left, right):
|
|
52
|
-
method = getattr(right, "__snail_contains__", None)
|
|
53
|
-
if method is not None:
|
|
54
|
-
return method(left)
|
|
55
|
-
return left in right
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def __snail_contains_not__(left, right):
|
|
59
|
-
method = getattr(right, "__snail_contains__", None)
|
|
60
|
-
if method is not None:
|
|
61
|
-
return not bool(method(left))
|
|
62
|
-
return left not in right
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def install_helpers(globals_dict: dict) -> None:
|
|
66
|
-
globals_dict["__snail_compact_try"] = compact_try
|
|
67
|
-
globals_dict["__snail_regex_search"] = regex_search
|
|
68
|
-
globals_dict["__snail_regex_compile"] = regex_compile
|
|
69
|
-
globals_dict["__SnailSubprocessCapture"] = SubprocessCapture
|
|
70
|
-
globals_dict["__SnailSubprocessStatus"] = SubprocessStatus
|
|
71
|
-
globals_dict["__snail_jmespath_query"] = __snail_jmespath_query
|
|
72
|
-
globals_dict["__snail_partial"] = __snail_partial
|
|
73
|
-
globals_dict["__snail_contains__"] = __snail_contains__
|
|
74
|
-
globals_dict["__snail_contains_not__"] = __snail_contains_not__
|
|
75
|
-
globals_dict["js"] = js
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|