spy-ast 0.1.0__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.
- spy_ast-0.1.0/LICENSE +22 -0
- spy_ast-0.1.0/PKG-INFO +5 -0
- spy_ast-0.1.0/build_backend.py +315 -0
- spy_ast-0.1.0/cmd/pyast_parser/main.v +36 -0
- spy_ast-0.1.0/ext.v +40 -0
- spy_ast-0.1.0/parser.v +1633 -0
- spy_ast-0.1.0/pyproject.toml +16 -0
- spy_ast-0.1.0/test.py +523 -0
- spy_ast-0.1.0/test_nested_match.py +31 -0
- spy_ast-0.1.0/tests_vlang_parser/test_builtin_ast_compat.py +101 -0
- spy_ast-0.1.0/tests_vlang_parser/test_parser.py +109 -0
- spy_ast-0.1.0/typed_api_test.v +31 -0
- spy_ast-0.1.0/v.mod +6 -0
- spy_ast-0.1.0/v_ast/__init__.py +4 -0
- spy_ast-0.1.0/v_ast/_vext.c +124 -0
- spy_ast-0.1.0/v_ast/ast.py +11 -0
- spy_ast-0.1.0/v_ast/wrapper.py +270 -0
spy_ast-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arun Sharma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
spy_ast-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""PEP 517 backend for spy-ast.
|
|
2
|
+
|
|
3
|
+
Build outputs include:
|
|
4
|
+
- Python package `v_ast`
|
|
5
|
+
- Native V shared library
|
|
6
|
+
- CPython extension that calls into the V shared library
|
|
7
|
+
- Native parser executable fallback
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import base64
|
|
13
|
+
import hashlib
|
|
14
|
+
import io
|
|
15
|
+
import os
|
|
16
|
+
import pathlib
|
|
17
|
+
import shutil
|
|
18
|
+
import shlex
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import sysconfig
|
|
22
|
+
import tarfile
|
|
23
|
+
import tempfile
|
|
24
|
+
import zipfile
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
NAME = "spy-ast"
|
|
28
|
+
VERSION = "0.1.0"
|
|
29
|
+
ROOT = pathlib.Path(__file__).resolve().parent
|
|
30
|
+
PACKAGE_DIR = ROOT / "v_ast"
|
|
31
|
+
VSRC_DIR = ROOT
|
|
32
|
+
VCLI_DIR = ROOT / "cmd" / "pyast_parser"
|
|
33
|
+
EXT_SOURCE = PACKAGE_DIR / "_vext.c"
|
|
34
|
+
BINARY_BASENAME = "pyast_parser"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _dist_info_dir() -> str:
|
|
38
|
+
return f"{NAME.replace('-', '_')}-{VERSION}.dist-info"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _wheel_tag() -> str:
|
|
42
|
+
platform = sysconfig.get_platform().replace("-", "_").replace(".", "_")
|
|
43
|
+
return f"py3-none-{platform}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _wheel_name() -> str:
|
|
47
|
+
return f"{NAME.replace('-', '_')}-{VERSION}-{_wheel_tag()}.whl"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _metadata() -> str:
|
|
51
|
+
return "\n".join(
|
|
52
|
+
[
|
|
53
|
+
"Metadata-Version: 2.1",
|
|
54
|
+
f"Name: {NAME}",
|
|
55
|
+
f"Version: {VERSION}",
|
|
56
|
+
"Summary: Static Python AST parser for a modified Python dialect with match as an expression.",
|
|
57
|
+
"Requires-Python: >=3.11",
|
|
58
|
+
"",
|
|
59
|
+
]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _wheel_file() -> str:
|
|
64
|
+
return "\n".join(
|
|
65
|
+
[
|
|
66
|
+
"Wheel-Version: 1.0",
|
|
67
|
+
"Generator: spy-ast-local-backend",
|
|
68
|
+
"Root-Is-Purelib: false",
|
|
69
|
+
f"Tag: {_wheel_tag()}",
|
|
70
|
+
"",
|
|
71
|
+
]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _record_line(path: str, data: bytes) -> tuple[str, str, str]:
|
|
76
|
+
digest = hashlib.sha256(data).digest()
|
|
77
|
+
b64 = base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=")
|
|
78
|
+
return path, f"sha256={b64}", str(len(data))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _run_checked(cmd: list[str], cwd: pathlib.Path | None = None) -> None:
|
|
82
|
+
proc = subprocess.run(cmd, cwd=cwd, check=False, text=True, capture_output=True)
|
|
83
|
+
if proc.returncode != 0:
|
|
84
|
+
message = proc.stderr.strip() or proc.stdout.strip() or "command failed"
|
|
85
|
+
rendered = " ".join(shlex.quote(x) for x in cmd)
|
|
86
|
+
raise RuntimeError(f"{rendered}: {message}")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _with_module_path(cmd: list[str], temp_dir: pathlib.Path) -> list[str]:
|
|
90
|
+
module_dir = temp_dir / "pyast"
|
|
91
|
+
temp_dir.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
try:
|
|
93
|
+
module_dir.symlink_to(ROOT, target_is_directory=True)
|
|
94
|
+
except OSError:
|
|
95
|
+
shutil.copytree(
|
|
96
|
+
ROOT,
|
|
97
|
+
module_dir,
|
|
98
|
+
ignore=shutil.ignore_patterns(
|
|
99
|
+
".git",
|
|
100
|
+
".venv",
|
|
101
|
+
"__pycache__",
|
|
102
|
+
"dist",
|
|
103
|
+
"dist-*",
|
|
104
|
+
"wheelhouse",
|
|
105
|
+
"vlang",
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
return [cmd[0], "-path", f"{temp_dir}|@vlib|@vmodules", *cmd[1:]]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _shared_lib_name() -> str:
|
|
112
|
+
if os.name == "nt":
|
|
113
|
+
return "pyast_parser.dll"
|
|
114
|
+
if sys.platform == "darwin":
|
|
115
|
+
return "libpyast_parser.dylib"
|
|
116
|
+
return "libpyast_parser.so"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _build_shared_library() -> tuple[str, bytes]:
|
|
120
|
+
libname = _shared_lib_name()
|
|
121
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
122
|
+
temp_dir = pathlib.Path(tmpdir)
|
|
123
|
+
out = temp_dir / libname
|
|
124
|
+
target = temp_dir / "pyast"
|
|
125
|
+
cmd = _with_module_path(["v", "-enable-globals", "-shared", "-o", str(out), str(target)], temp_dir)
|
|
126
|
+
_run_checked(cmd, cwd=ROOT)
|
|
127
|
+
return libname, out.read_bytes()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _build_binary() -> tuple[str, bytes]:
|
|
131
|
+
suffix = ".exe" if os.name == "nt" else ""
|
|
132
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
133
|
+
temp_dir = pathlib.Path(tmpdir)
|
|
134
|
+
out = temp_dir / f"{BINARY_BASENAME}{suffix}"
|
|
135
|
+
target = temp_dir / "pyast" / "cmd" / "pyast_parser"
|
|
136
|
+
cmd = _with_module_path(["v", "-enable-globals", "-o", str(out), str(target)], temp_dir)
|
|
137
|
+
_run_checked(cmd, cwd=ROOT)
|
|
138
|
+
return out.name, out.read_bytes()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _build_python_extension() -> tuple[str, bytes]:
|
|
142
|
+
ext_suffix = sysconfig.get_config_var("EXT_SUFFIX") or ".so"
|
|
143
|
+
include_dir = sysconfig.get_paths().get("include")
|
|
144
|
+
if not include_dir:
|
|
145
|
+
raise RuntimeError("unable to locate Python include directory")
|
|
146
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
147
|
+
out = pathlib.Path(tmpdir) / f"_vext{ext_suffix}"
|
|
148
|
+
cmd = ["cc", "-O2"]
|
|
149
|
+
if os.name != "nt":
|
|
150
|
+
cmd.append("-fPIC")
|
|
151
|
+
cmd.extend(["-I", include_dir, str(EXT_SOURCE), "-shared", "-o", str(out)])
|
|
152
|
+
if sys.platform == "darwin":
|
|
153
|
+
cmd.extend(["-undefined", "dynamic_lookup"])
|
|
154
|
+
elif os.name == "nt":
|
|
155
|
+
lib_dir = sysconfig.get_config_var("LIBDIR") or str(pathlib.Path(sys.base_prefix) / "libs")
|
|
156
|
+
py_lib = f"python{sys.version_info.major}{sys.version_info.minor}"
|
|
157
|
+
cmd.extend(["-L", lib_dir, f"-l{py_lib}"])
|
|
158
|
+
elif os.name != "nt":
|
|
159
|
+
cmd.append("-ldl")
|
|
160
|
+
_run_checked(cmd, cwd=ROOT)
|
|
161
|
+
return out.name, out.read_bytes()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _write_entry(zf: zipfile.ZipFile, arcname: str, data: bytes, mode: int) -> tuple[str, str, str]:
|
|
165
|
+
info = zipfile.ZipInfo(arcname)
|
|
166
|
+
info.compress_type = zipfile.ZIP_DEFLATED
|
|
167
|
+
info.create_system = 3 # Unix mode bits
|
|
168
|
+
info.external_attr = (mode & 0xFFFF) << 16
|
|
169
|
+
zf.writestr(info, data)
|
|
170
|
+
return _record_line(arcname, data)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _sdist_name() -> str:
|
|
174
|
+
return f"{NAME.replace('-', '_')}-{VERSION}"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _sdist_filename() -> str:
|
|
178
|
+
return f"{_sdist_name()}.tar.gz"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _sdist_paths() -> list[pathlib.Path]:
|
|
182
|
+
paths = [
|
|
183
|
+
ROOT / "build_backend.py",
|
|
184
|
+
ROOT / "LICENSE",
|
|
185
|
+
ROOT / "pyproject.toml",
|
|
186
|
+
ROOT / "v.mod",
|
|
187
|
+
]
|
|
188
|
+
paths.extend(sorted(PACKAGE_DIR.rglob("*.py")))
|
|
189
|
+
paths.extend(sorted(PACKAGE_DIR.rglob("*.c")))
|
|
190
|
+
paths.extend(sorted((ROOT / "tests_vlang_parser").rglob("*.py")))
|
|
191
|
+
paths.extend(sorted(VSRC_DIR.glob("*.v")))
|
|
192
|
+
paths.extend(sorted((ROOT / "cmd").rglob("*.v")))
|
|
193
|
+
for sample in [ROOT / "test.py", ROOT / "test_nested_match.py"]:
|
|
194
|
+
if sample.exists():
|
|
195
|
+
paths.append(sample)
|
|
196
|
+
return sorted({path for path in paths if path.exists()})
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _write_sdist_entry(tf: tarfile.TarFile, path: pathlib.Path, arcname: str) -> None:
|
|
200
|
+
info = tf.gettarinfo(str(path), arcname)
|
|
201
|
+
info.uid = 0
|
|
202
|
+
info.gid = 0
|
|
203
|
+
info.uname = ""
|
|
204
|
+
info.gname = ""
|
|
205
|
+
with path.open("rb") as handle:
|
|
206
|
+
tf.addfile(info, handle)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _write_sdist_bytes(tf: tarfile.TarFile, arcname: str, data: bytes, mode: int = 0o644) -> None:
|
|
210
|
+
info = tarfile.TarInfo(arcname)
|
|
211
|
+
info.size = len(data)
|
|
212
|
+
info.mode = mode
|
|
213
|
+
info.uid = 0
|
|
214
|
+
info.gid = 0
|
|
215
|
+
info.uname = ""
|
|
216
|
+
info.gname = ""
|
|
217
|
+
tf.addfile(info, io.BytesIO(data))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def get_requires_for_build_wheel(config_settings=None): # noqa: D401
|
|
221
|
+
return []
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def get_requires_for_build_sdist(config_settings=None):
|
|
225
|
+
return []
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def get_requires_for_build_editable(config_settings=None):
|
|
229
|
+
return []
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def build_sdist(sdist_directory, config_settings=None):
|
|
233
|
+
sdist_dir = pathlib.Path(sdist_directory)
|
|
234
|
+
sdist_dir.mkdir(parents=True, exist_ok=True)
|
|
235
|
+
sdist_path = sdist_dir / _sdist_filename()
|
|
236
|
+
prefix = _sdist_name()
|
|
237
|
+
|
|
238
|
+
with tarfile.open(sdist_path, "w:gz", format=tarfile.PAX_FORMAT) as tf:
|
|
239
|
+
_write_sdist_bytes(tf, f"{prefix}/PKG-INFO", _metadata().encode("utf-8"))
|
|
240
|
+
for path in _sdist_paths():
|
|
241
|
+
arcname = f"{prefix}/{path.relative_to(ROOT).as_posix()}"
|
|
242
|
+
_write_sdist_entry(tf, path, arcname)
|
|
243
|
+
|
|
244
|
+
return sdist_path.name
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
|
|
248
|
+
wheel_dir = pathlib.Path(wheel_directory)
|
|
249
|
+
wheel_dir.mkdir(parents=True, exist_ok=True)
|
|
250
|
+
wheel_path = wheel_dir / _wheel_name()
|
|
251
|
+
dist_info = _dist_info_dir()
|
|
252
|
+
|
|
253
|
+
ext_name, ext_data = _build_python_extension()
|
|
254
|
+
lib_name, lib_data = _build_shared_library()
|
|
255
|
+
bin_name, bin_data = _build_binary()
|
|
256
|
+
|
|
257
|
+
rows: list[tuple[str, str, str]] = []
|
|
258
|
+
with zipfile.ZipFile(wheel_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
|
259
|
+
for py in sorted(PACKAGE_DIR.rglob("*.py")):
|
|
260
|
+
arcname = py.relative_to(ROOT).as_posix()
|
|
261
|
+
rows.append(_write_entry(zf, arcname, py.read_bytes(), 0o644))
|
|
262
|
+
|
|
263
|
+
rows.append(_write_entry(zf, f"{PACKAGE_DIR.name}/{ext_name}", ext_data, 0o755))
|
|
264
|
+
rows.append(_write_entry(zf, f"{PACKAGE_DIR.name}/{lib_name}", lib_data, 0o755))
|
|
265
|
+
rows.append(_write_entry(zf, f"{PACKAGE_DIR.name}/_bin/{bin_name}", bin_data, 0o755))
|
|
266
|
+
|
|
267
|
+
for src in sorted(VSRC_DIR.glob("*.v")):
|
|
268
|
+
rows.append(
|
|
269
|
+
_write_entry(
|
|
270
|
+
zf,
|
|
271
|
+
f"{PACKAGE_DIR.name}/_vsrc/{src.name}",
|
|
272
|
+
src.read_bytes(),
|
|
273
|
+
0o644,
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
for src in sorted(VCLI_DIR.glob("*.v")):
|
|
277
|
+
rows.append(
|
|
278
|
+
_write_entry(
|
|
279
|
+
zf,
|
|
280
|
+
f"{PACKAGE_DIR.name}/_vsrc/cmd/pyast_parser/{src.name}",
|
|
281
|
+
src.read_bytes(),
|
|
282
|
+
0o644,
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
metadata_path = f"{dist_info}/METADATA"
|
|
287
|
+
rows.append(_write_entry(zf, metadata_path, _metadata().encode("utf-8"), 0o644))
|
|
288
|
+
|
|
289
|
+
wheel_meta_path = f"{dist_info}/WHEEL"
|
|
290
|
+
rows.append(_write_entry(zf, wheel_meta_path, _wheel_file().encode("utf-8"), 0o644))
|
|
291
|
+
|
|
292
|
+
record_path = f"{dist_info}/RECORD"
|
|
293
|
+
record_lines = [",".join(row) for row in rows]
|
|
294
|
+
record_lines.append(f"{record_path},,")
|
|
295
|
+
record_data = ("\n".join(record_lines) + "\n").encode("utf-8")
|
|
296
|
+
_write_entry(zf, record_path, record_data, 0o644)
|
|
297
|
+
|
|
298
|
+
return wheel_path.name
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def build_editable(wheel_directory, config_settings=None, metadata_directory=None):
|
|
302
|
+
return build_wheel(wheel_directory, config_settings, metadata_directory)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
|
|
306
|
+
meta_dir = pathlib.Path(metadata_directory) / _dist_info_dir()
|
|
307
|
+
meta_dir.mkdir(parents=True, exist_ok=True)
|
|
308
|
+
(meta_dir / "METADATA").write_text(_metadata(), encoding="utf-8")
|
|
309
|
+
(meta_dir / "WHEEL").write_text(_wheel_file(), encoding="utf-8")
|
|
310
|
+
(meta_dir / "RECORD").write_text("", encoding="utf-8")
|
|
311
|
+
return meta_dir.name
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def prepare_metadata_for_build_editable(metadata_directory, config_settings=None):
|
|
315
|
+
return prepare_metadata_for_build_wheel(metadata_directory, config_settings)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module main
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import pyast
|
|
5
|
+
|
|
6
|
+
fn main() {
|
|
7
|
+
args := os.args[1..]
|
|
8
|
+
if args.len < 2 {
|
|
9
|
+
eprintln('usage: v run cmd/pyast_parser --json <source-file> | --json-expr <source-file>')
|
|
10
|
+
exit(2)
|
|
11
|
+
}
|
|
12
|
+
mode := args[0]
|
|
13
|
+
source_path := args[1]
|
|
14
|
+
source := os.read_file(source_path) or {
|
|
15
|
+
eprintln('failed to read ${source_path}: ${err}')
|
|
16
|
+
exit(1)
|
|
17
|
+
}
|
|
18
|
+
if mode == '--json' {
|
|
19
|
+
payload := pyast.parse_module_json(source) or {
|
|
20
|
+
eprintln('parse error: ${err}')
|
|
21
|
+
exit(1)
|
|
22
|
+
}
|
|
23
|
+
println(payload.trim_space())
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
if mode == '--json-expr' {
|
|
27
|
+
payload := pyast.parse_expression_json(source) or {
|
|
28
|
+
eprintln('parse error: ${err}')
|
|
29
|
+
exit(1)
|
|
30
|
+
}
|
|
31
|
+
println(payload.trim_space())
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
eprintln('unknown mode: ${mode}')
|
|
35
|
+
exit(2)
|
|
36
|
+
}
|
spy_ast-0.1.0/ext.v
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module pyast
|
|
2
|
+
|
|
3
|
+
pub fn parse_module(source string) !Module {
|
|
4
|
+
mut parser := new_parser(source)!
|
|
5
|
+
return parser.parse_module()!
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
pub fn parse_expression(source string) !Expr {
|
|
9
|
+
mut parser := new_parser(source)!
|
|
10
|
+
return parser.parse_expr_root()!
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
pub fn parse_module_json(source string) !string {
|
|
14
|
+
mod := parse_module(source)!
|
|
15
|
+
return module_json(mod)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub fn parse_expression_json(source string) !string {
|
|
19
|
+
expr := parse_expression(source)!
|
|
20
|
+
return expr_json(expr)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@[export: 'pyast_parse_module_json']
|
|
24
|
+
pub fn pyast_parse_module_json(source &char) &char {
|
|
25
|
+
src := unsafe { cstring_to_vstring(source) }
|
|
26
|
+
payload := parse_module_json(src) or { return unsafe { nil } }
|
|
27
|
+
return payload.str
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@[export: 'pyast_parse_expression_json']
|
|
31
|
+
pub fn pyast_parse_expression_json(source &char) &char {
|
|
32
|
+
src := unsafe { cstring_to_vstring(source) }
|
|
33
|
+
payload := parse_expression_json(src) or { return unsafe { nil } }
|
|
34
|
+
return payload.str
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@[export: 'pyast_last_error']
|
|
38
|
+
pub fn pyast_last_error() &char {
|
|
39
|
+
return c'parse error'
|
|
40
|
+
}
|