zig-zon 0.0.3__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.
@@ -0,0 +1,195 @@
1
+ const std = @import("std");
2
+ const builtin = @import("builtin");
3
+ const Build = std.Build;
4
+ const fs = std.fs;
5
+ const mem = std.mem;
6
+
7
+ pub const CPython = @This();
8
+
9
+ exe: Build.LazyPath,
10
+
11
+ /// adds a `python` option to the build. if not provided, tries to find `python` in PATH
12
+ pub fn findAddOption(b: *Build) @This() {
13
+ return .{
14
+ .exe = b.option(
15
+ Build.LazyPath,
16
+ "python",
17
+ "path to the cpython interpreter executable",
18
+ ) orelse {
19
+ return find(b);
20
+ },
21
+ };
22
+ }
23
+
24
+ pub fn find(b: *Build) @This() {
25
+ return .{
26
+ .exe = .{
27
+ .cwd_relative = FindExeWindowsCompat.findProgram(b, &.{
28
+ "python",
29
+ "python3",
30
+ }, &.{}) catch {
31
+ const err_wf = b.addWriteFiles();
32
+ const err_step = b.addFail("finding python failed. please provide a path to the exe using -Dpython=");
33
+ err_wf.step.dependOn(&err_step.step);
34
+ return .{ .exe = err_wf.getDirectory() };
35
+ },
36
+ },
37
+ };
38
+ }
39
+
40
+ pub const LinkOptions = struct {
41
+ /// library to link against libpython
42
+ lib: *std.Build.Step.Compile,
43
+ macros: []const [2][]const u8 = &default_macros,
44
+ pub const default_macros = .{
45
+ .{ "Py_LIMITED_API", "0x03060500" }, // use the limited api from 3.6.5
46
+ };
47
+ };
48
+
49
+ pub fn generateIncludeDir(self: @This(), b: *Build) Build.LazyPath {
50
+ const extract_link_info = Build.Step.Run.create(b, "python header extraction run");
51
+ extract_link_info.addFileArg(self.exe);
52
+ extract_link_info.addFileArg(b.path("src/extract_link_info.py"));
53
+ return extract_link_info.addOutputDirectoryArg("include");
54
+ }
55
+
56
+ pub fn installExtLib(b: *Build, lib: *Build.Step.Compile) void {
57
+ const ext_lib_name = lib_ext_name(b, lib);
58
+ const lib_install = b.addInstallLibFile(lib.getEmittedBin(), ext_lib_name);
59
+ b.getInstallStep().dependOn(&lib_install.step);
60
+ }
61
+
62
+ pub fn link(self: @This(), b: *Build, opt: LinkOptions) void {
63
+ const target = Build.ResolvedTarget{
64
+ .query = .{},
65
+ .result = opt.lib.rootModuleTarget(),
66
+ };
67
+
68
+ const python_inc = self.generateIncludeDir(b);
69
+
70
+ const cpython = b.addTranslateC(.{
71
+ .root_source_file = python_inc.path(b, "Zig_Python_With_Hexver.h"),
72
+ .optimize = opt.lib.root_module.optimize orelse .ReleaseSafe,
73
+ .target = target,
74
+ .link_libc = true,
75
+ });
76
+ for (opt.macros) |macro| {
77
+ cpython.defineCMacro(macro[0], macro[1]);
78
+ }
79
+
80
+ cpython.addIncludePath(python_inc);
81
+
82
+ if (target.result.os.tag == .windows) {
83
+ opt.lib.root_module.addLibraryPath(python_inc);
84
+ opt.lib.linkSystemLibrary("python");
85
+ }
86
+ opt.lib.root_module.addImport(
87
+ "cpython",
88
+ cpython.createModule(),
89
+ );
90
+ }
91
+
92
+ pub fn lib_ext_name(b: *Build, lib: *Build.Step.Compile) []const u8 {
93
+ const target = lib.rootModuleTarget();
94
+ return if (target.os.tag == .windows)
95
+ b.fmt("{s}.pyd", .{lib.name})
96
+ else
97
+ b.fmt("{s}.so", .{lib.name});
98
+ }
99
+
100
+ pub fn run_test(
101
+ self: @This(),
102
+ b: *Build,
103
+ lib: *Build.Step.Compile,
104
+ test_py_file: Build.LazyPath,
105
+ ) *Build.Step.Run {
106
+ const runner = b.addExecutable(.{
107
+ .name = "python-runner",
108
+ .root_module = b.createModule(.{
109
+ .root_source_file = b.path("src/python-runner.zig"),
110
+ .target = b.graph.host,
111
+ .optimize = .ReleaseSmall,
112
+ }),
113
+ });
114
+ const run_step = b.addRunArtifact(runner);
115
+ const python_ext = writeExtFile(b, lib);
116
+ run_step.addFileArg(self.exe);
117
+ run_step.addDirectoryArg(python_ext.dirname());
118
+ run_step.addFileArg(test_py_file);
119
+ run_step.has_side_effects = true;
120
+ return run_step;
121
+ }
122
+
123
+ pub fn writeExtFile(b: *Build, lib: *Build.Step.Compile) Build.LazyPath {
124
+ const ext_lib_name = lib_ext_name(b, lib);
125
+ const wf = b.addWriteFiles();
126
+ return wf.addCopyFile(lib.getEmittedBin(), ext_lib_name);
127
+ }
128
+
129
+ const FindExeWindowsCompat = struct {
130
+ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 {
131
+ if (fs.realpathAlloc(b.allocator, full_path)) |p| {
132
+ return p;
133
+ } else |err| switch (err) {
134
+ error.OutOfMemory => @panic("OOM"),
135
+ else => {},
136
+ }
137
+
138
+ if (builtin.os.tag == .windows) {
139
+ if (b.graph.env_map.get("PATHEXT")) |PATHEXT| {
140
+ var it = mem.tokenizeScalar(u8, PATHEXT, fs.path.delimiter);
141
+
142
+ while (it.next()) |ext| {
143
+ if (!supportedWindowsProgramExtension(ext)) continue;
144
+ const exe_path = mem.join(b.allocator, "", &.{ full_path, ext }) catch @panic("OOM");
145
+ fs.accessAbsolute(exe_path, .{ .mode = .read_only }) catch {
146
+ b.allocator.free(exe_path);
147
+ continue;
148
+ };
149
+ return exe_path;
150
+ }
151
+ }
152
+ }
153
+
154
+ return null;
155
+ }
156
+
157
+ pub fn findProgram(b: *Build, names: []const []const u8, paths: []const []const u8) error{FileNotFound}![]const u8 {
158
+ // TODO report error for ambiguous situations
159
+ for (b.search_prefixes.items) |search_prefix| {
160
+ for (names) |name| {
161
+ if (fs.path.isAbsolute(name)) {
162
+ return name;
163
+ }
164
+ return tryFindProgram(b, b.pathJoin(&.{ search_prefix, "bin", name })) orelse continue;
165
+ }
166
+ }
167
+ if (b.graph.env_map.get("PATH")) |PATH| {
168
+ for (names) |name| {
169
+ if (fs.path.isAbsolute(name)) {
170
+ return name;
171
+ }
172
+ var it = mem.tokenizeScalar(u8, PATH, fs.path.delimiter);
173
+ while (it.next()) |p| {
174
+ return tryFindProgram(b, b.pathJoin(&.{ p, name })) orelse continue;
175
+ }
176
+ }
177
+ }
178
+ for (names) |name| {
179
+ if (fs.path.isAbsolute(name)) {
180
+ return name;
181
+ }
182
+ for (paths) |p| {
183
+ return tryFindProgram(b, b.pathJoin(&.{ p, name })) orelse continue;
184
+ }
185
+ }
186
+ return error.FileNotFound;
187
+ }
188
+
189
+ fn supportedWindowsProgramExtension(ext: []const u8) bool {
190
+ inline for (@typeInfo(std.process.Child.WindowsExtension).@"enum".fields) |field| {
191
+ if (std.ascii.eqlIgnoreCase(ext, "." ++ field.name)) return true;
192
+ }
193
+ return false;
194
+ }
195
+ };
@@ -0,0 +1,2 @@
1
+ exclude .gitlab-ci.yml
2
+ exclude .gitignore
zig_zon-0.0.3/PKG-INFO ADDED
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.4
2
+ Name: zig-zon
3
+ Version: 0.0.3
4
+ Summary: My package description
5
+ Author-email: Tobias Simetsreiter <dasimmet@gmail.com>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+
9
+ # Python Zig Zon Parser
10
+
11
+ A native python extension built with zig to parse zig-object-notation strings
12
+ into python objects.
13
+
14
+ # Usage (so far only linux)
15
+
16
+ install:
17
+
18
+ ```bash
19
+ python3 -m pip install git+https://gitlab.com/dasimmet/python-zig-zon.git
20
+ ```
21
+
22
+ running:
23
+
24
+ ```python
25
+ import zig_zon
26
+ parsed = zig_zon.parse('.{.allyourcode = .are_belong_to_us, .asd = 123}')
27
+ print(parsed)
28
+ ```
29
+
30
+ or, look at the [test](test.py)
@@ -0,0 +1,22 @@
1
+ # Python Zig Zon Parser
2
+
3
+ A native python extension built with zig to parse zig-object-notation strings
4
+ into python objects.
5
+
6
+ # Usage (so far only linux)
7
+
8
+ install:
9
+
10
+ ```bash
11
+ python3 -m pip install git+https://gitlab.com/dasimmet/python-zig-zon.git
12
+ ```
13
+
14
+ running:
15
+
16
+ ```python
17
+ import zig_zon
18
+ parsed = zig_zon.parse('.{.allyourcode = .are_belong_to_us, .asd = 123}')
19
+ print(parsed)
20
+ ```
21
+
22
+ or, look at the [test](test.py)
@@ -0,0 +1,48 @@
1
+ const std = @import("std");
2
+ const Build = std.Build;
3
+
4
+ pub const CPython = @import("CPython.zig");
5
+
6
+ pub fn build(b: *Build) void {
7
+ const target = b.standardTargetOptions(.{});
8
+ const optimize = b.standardOptimizeOption(.{});
9
+ const python = CPython.findAddOption(b);
10
+
11
+ const exe = b.addExecutable(.{
12
+ .name = "zigwheeler",
13
+ .root_module = b.addModule("zigwheeler", .{
14
+ .root_source_file = b.path("src/zigwheeler.zig"),
15
+ .target = target,
16
+ .optimize = optimize,
17
+ }),
18
+ });
19
+ b.installArtifact(exe);
20
+
21
+ const lib = b.addLibrary(.{
22
+ .name = "zig_zon",
23
+ .root_module = b.addModule("zig_zon", .{
24
+ .root_source_file = b.path("src/zig_zon.zig"),
25
+ .target = target,
26
+ .optimize = optimize,
27
+ }),
28
+ .linkage = .dynamic,
29
+ .use_llvm = true,
30
+ });
31
+ CPython.installExtLib(b, lib);
32
+
33
+ python.link(b, .{
34
+ .lib = lib,
35
+ });
36
+
37
+ const install_test = b.addInstallFile(b.path("src/test_zig_zon.py"), "lib/test_zig_zon.py");
38
+ b.default_step.dependOn(&install_test.step);
39
+ const run = b.addRunArtifact(exe);
40
+ if (b.args) |args| {
41
+ run.addArgs(args);
42
+ }
43
+ b.step("run", "run the wheeler").dependOn(&run.step);
44
+
45
+ const test_run = python.run_test(b, lib, b.path("src/test_zig_zon.py"));
46
+
47
+ b.step("test", "run the python test script").dependOn(&test_run.step);
48
+ }
@@ -0,0 +1,17 @@
1
+ .{
2
+ .name = .python_zig_zon,
3
+ .fingerprint = 0x3d332656a3644fc9,
4
+ .version = "0.0.3",
5
+ .minimum_zig_version = "0.15.1",
6
+ .dependencies = .{},
7
+ .paths = .{
8
+ "src",
9
+ "build.zig",
10
+ "build.zig.zon",
11
+ "CPython.zig",
12
+ "LICENSE",
13
+ "README.md",
14
+ "setup.py",
15
+ "pyproject.toml",
16
+ },
17
+ }
@@ -0,0 +1,13 @@
1
+ [project]
2
+ name = "zig-zon"
3
+ authors = [{ name = "Tobias Simetsreiter", email = "dasimmet@gmail.com" }]
4
+ description = "My package description"
5
+ readme = "README.md"
6
+ requires-python = ">=3.8"
7
+ dynamic = ["version"]
8
+
9
+ [build-system]
10
+ requires = ["setuptools"]
11
+ build-backend = "setuptools.build_meta"
12
+
13
+ [tool.setuptools_scm]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
zig_zon-0.0.3/setup.py ADDED
@@ -0,0 +1,28 @@
1
+ from setuptools import Extension
2
+ from setuptools import setup
3
+ import sys
4
+ import os
5
+
6
+ # In this package the setuptools_zig_build cannot be imported as a dependency.
7
+ # on another package zig_build should be passed to setup()
8
+ sys.path.insert(0, os.path.normpath(os.path.join(__file__, '..'))) # nopep8
9
+ from setuptools_zig_build import ZigBuildExtension # nopep8
10
+
11
+ zig_build = {
12
+ "use_zig_python_package": True,
13
+ "optimize": "ReleaseSmall",
14
+ 'test_step': 'test', # this is failing on windows for now ;-/
15
+ 'extra_args': [],
16
+ }
17
+
18
+ setup(
19
+ name='zig-zon',
20
+ cmdclass={
21
+ 'build_ext': ZigBuildExtension(zig_build),
22
+ },
23
+ ext_modules=[Extension('zig_zon', [])],
24
+ setup_requires=[
25
+ "setuptools-scm",
26
+ "ziglang==0.15.1",
27
+ ],
28
+ )
@@ -0,0 +1,108 @@
1
+ # coding: utf-8
2
+
3
+ import sys
4
+ import os
5
+ from subprocess import run
6
+ from pathlib import Path
7
+ import shlex
8
+
9
+ from setuptools import Extension
10
+ from distutils.dist import Distribution
11
+ from setuptools.command.build_ext import build_ext as SetupToolsBuildExt
12
+
13
+
14
+ class ZigCompilerError(Exception):
15
+ """Some compile/link operation failed."""
16
+
17
+
18
+ class BuildExt(SetupToolsBuildExt):
19
+ def __init__(self, dist, zig_value):
20
+ self._zig_value = zig_value
21
+ super().__init__(dist)
22
+
23
+ def build_extension(self, ext):
24
+ ext: Extension
25
+ if not self._zig_value:
26
+ return super().build_extension(ext)
27
+
28
+ build_zig_dir = "."
29
+ if len(ext.sources) == 1:
30
+ build_zig_dir = ext.sources[0]
31
+ elif len(ext.sources) > 1:
32
+ raise ZigCompilerError(
33
+ "sources should only point to build.zig directory")
34
+
35
+ target = Path(self.get_ext_fullpath(ext.name))
36
+
37
+ output_ext = os.path.splitext(self.get_ext_filename(ext.name))[1]
38
+
39
+ zig_out = Path(self.build_temp) / "zig-out"
40
+
41
+ bld_cmd = [os.environ.get('PY_ZIG', 'zig'), 'build', 'install',
42
+ '-Dpython={}'.format(sys.executable), '--prefix', str(zig_out.absolute())]
43
+
44
+ if isinstance(self._zig_value, dict):
45
+ if 'use_zig_python_package' in self._zig_value and self._zig_value['use_zig_python_package'] == True:
46
+ import ziglang
47
+ bld_cmd[0] = os.path.join(
48
+ os.path.dirname(ziglang.__file__), "zig")
49
+
50
+ if 'test_step' in self._zig_value:
51
+ bld_cmd.append(self._zig_value['test_step'])
52
+
53
+ if 'optimize' in self._zig_value:
54
+ assert isinstance(self._zig_value['optimize'], str)
55
+ assert self._zig_value['optimize'] in [
56
+ "Debug",
57
+ "ReleaseSafe",
58
+ "ReleaseFast",
59
+ "ReleaseSmall",
60
+ ]
61
+ bld_cmd.append(
62
+ '-Doptimize={}'.format(self._zig_value['optimize']))
63
+
64
+ if 'extra_args' in self._zig_value:
65
+ assert isinstance(self._zig_value['extra_args'], list)
66
+ for arg in self._zig_value['extra_args']:
67
+ assert isinstance(arg, str)
68
+ bld_cmd.append(arg)
69
+
70
+ os.makedirs(self.build_temp, exist_ok=True)
71
+ print('\ncmd', shlex.join(bld_cmd))
72
+ sys.stdout.flush()
73
+ run(bld_cmd, check=True, cwd=build_zig_dir)
74
+
75
+ output = None
76
+ for subpath in ['', 'lib', 'bin/', 'lib/', 'bin/lib', 'lib/lib']:
77
+ zig_output = zig_out / (subpath + ext.name + output_ext)
78
+ if zig_output.exists():
79
+ output = zig_output.absolute()
80
+ break
81
+ print('missing:', str(zig_output.absolute()), file=sys.stderr)
82
+
83
+ if output == None:
84
+ raise ZigCompilerError(f'expected output does not exist')
85
+
86
+ print('found output:', str(output.absolute()),
87
+ str(target), file=sys.stderr)
88
+
89
+ if target.exists():
90
+ target.unlink()
91
+ else:
92
+ target.parent.mkdir(exist_ok=True, parents=True)
93
+ output.rename(target)
94
+
95
+
96
+ class ZigBuildExtension:
97
+ def __init__(self, value):
98
+ self._value = value
99
+
100
+ def __call__(self, dist):
101
+ return BuildExt(dist, zig_value=self._value)
102
+
103
+
104
+ def setup_build_zig(dist, keyword, value):
105
+ assert isinstance(dist, Distribution)
106
+ assert keyword == 'build_zig'
107
+ be = dist.cmdclass.get('build_ext')
108
+ dist.cmdclass['build_ext'] = ZigBuildExtension(value)
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import sysconfig
4
+ import sys
5
+ import shutil
6
+ import os
7
+
8
+ output_dir = sys.argv[1]
9
+ syspath = sysconfig.get_paths()
10
+
11
+ lib_filename = 'python{}{}.lib'.format(
12
+ sys.version_info.major, sys.version_info.minor)
13
+
14
+ print("syspath:", syspath, file=sys.stderr)
15
+ print("include:", syspath['include'], file=sys.stderr)
16
+ print("data:", syspath['data'], file=sys.stderr)
17
+
18
+ os.makedirs(output_dir, exist_ok=True)
19
+ shutil.copytree(syspath['include'], output_dir, dirs_exist_ok=True)
20
+
21
+ found_lib = False
22
+ lib_paths = []
23
+ for k, v in sysconfig.get_config_vars().items():
24
+ if not isinstance(v, str):
25
+ continue
26
+ vf = os.path.join(v, lib_filename)
27
+ if os.path.exists(vf):
28
+ try:
29
+ shutil.copyfile(vf, os.path.join(output_dir, 'python.lib'))
30
+ found_lib = True
31
+ break
32
+ except:
33
+ lib_paths += vf
34
+ else:
35
+ lib_paths += vf
36
+
37
+ vfl = os.path.join(v, 'libs', lib_filename)
38
+ if os.path.exists(vfl):
39
+ try:
40
+ shutil.copyfile(vfl, os.path.join(output_dir, 'python.lib'))
41
+ found_lib = True
42
+ break
43
+ except:
44
+ lib_paths += vfl
45
+ else:
46
+ lib_paths += vfl
47
+
48
+ if sys.platform == 'windows' and not found_lib:
49
+ print("Did not find", lib_filename, "in:")
50
+ print(lib_paths)
51
+
52
+
53
+ with open(os.path.join(output_dir, 'Zig_Python_With_Hexver.h'), 'w') as fd:
54
+ fd.write('''
55
+ #ifndef PY_SSIZE_T_CLEAN
56
+ #define PY_SSIZE_T_CLEAN
57
+ #endif
58
+
59
+ #ifndef PYHEXVER
60
+ #define PYHEXVER {}
61
+ #endif
62
+
63
+ #include "Python.h"
64
+ '''.format(sys.hexversion))
65
+ print('OK:', os.path.join(output_dir, 'Zig_Python_With_Hexver.h'))
@@ -0,0 +1,49 @@
1
+ const std = @import("std");
2
+
3
+ pub fn main() !void {
4
+ var gpa_impl = std.heap.DebugAllocator(.{}).init;
5
+ defer _ = gpa_impl.deinit();
6
+ const gpa = gpa_impl.allocator();
7
+
8
+ const args = try std.process.argsAlloc(gpa);
9
+ defer std.process.argsFree(gpa, args);
10
+
11
+ if (args.len != 4) {
12
+ std.log.err("expected n args, got: {d}", .{args.len});
13
+ return error.Expected4Arguments;
14
+ }
15
+
16
+ const python = args[1];
17
+ const pythonpath = args[2];
18
+ const test_file = args[3];
19
+
20
+ const pythonpath_abs = if (std.fs.path.isAbsolute(pythonpath)) try gpa.dupe(u8, pythonpath) else blk: {
21
+ const cwd = try std.process.getCwdAlloc(gpa);
22
+ defer gpa.free(cwd);
23
+ break :blk try std.fs.path.join(gpa, &.{ cwd, pythonpath });
24
+ };
25
+ defer gpa.free(pythonpath_abs);
26
+
27
+ std.log.info("PYTHONPATH={s} {s} {s}", .{
28
+ pythonpath_abs,
29
+ python,
30
+ test_file,
31
+ });
32
+
33
+ const argv: []const []const u8 = &.{
34
+ python,
35
+ test_file,
36
+ };
37
+
38
+ var env = try std.process.getEnvMap(gpa);
39
+ defer env.deinit();
40
+ try env.put("PYTHONPATH", pythonpath_abs);
41
+
42
+ var proc: std.process.Child = .init(argv, gpa);
43
+ proc.env_map = &env;
44
+ proc.stderr_behavior = .Inherit;
45
+ proc.stdin_behavior = .Inherit;
46
+ proc.stdout_behavior = .Inherit;
47
+ const res = try proc.spawnAndWait();
48
+ std.process.exit(res.Exited);
49
+ }
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from pprint import pprint
4
+
5
+ import zig_zon
6
+ print('zig_zon module:', zig_zon)
7
+
8
+ teststr = """
9
+ .{
10
+ .asdf = "ccc",
11
+ .my_bool = true,
12
+ .my_float = 1.23,
13
+ .my_hex_int = 0xdeadbeef,
14
+ .my_negative_int = -432_123_111,
15
+ .my_negative_big_int = -4611686018427387904,
16
+ .my_optional = null,
17
+ }"""
18
+
19
+ testzon = zig_zon.parse(teststr)
20
+ print('teststr:')
21
+ pprint(testzon)
22
+
23
+ assert testzon['asdf'] == "ccc"
24
+ assert testzon['my_float'] == 1.23
25
+ assert testzon['my_hex_int'] == 0xdeadbeef
26
+ assert testzon['my_negative_int'] == -432_123_111
27
+ assert testzon['my_negative_big_int'] == -4611686018427387904
28
+ assert testzon['my_optional'] == None
29
+
30
+ print('build.zig.zon:')
31
+ with open('build.zig.zon') as fd:
32
+ pprint(zig_zon.parse(fd.read()))
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.4
2
+ Name: zig-zon
3
+ Version: 0.0.3
4
+ Summary: My package description
5
+ Author-email: Tobias Simetsreiter <dasimmet@gmail.com>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+
9
+ # Python Zig Zon Parser
10
+
11
+ A native python extension built with zig to parse zig-object-notation strings
12
+ into python objects.
13
+
14
+ # Usage (so far only linux)
15
+
16
+ install:
17
+
18
+ ```bash
19
+ python3 -m pip install git+https://gitlab.com/dasimmet/python-zig-zon.git
20
+ ```
21
+
22
+ running:
23
+
24
+ ```python
25
+ import zig_zon
26
+ parsed = zig_zon.parse('.{.allyourcode = .are_belong_to_us, .asd = 123}')
27
+ print(parsed)
28
+ ```
29
+
30
+ or, look at the [test](test.py)
@@ -0,0 +1,17 @@
1
+ CPython.zig
2
+ MANIFEST.in
3
+ README.md
4
+ build.zig
5
+ build.zig.zon
6
+ pyproject.toml
7
+ setup.py
8
+ setuptools_zig_build.py
9
+ src/extract_link_info.py
10
+ src/python-runner.zig
11
+ src/test_zig_zon.py
12
+ src/zig_zon.zig
13
+ src/zigwheeler.zig
14
+ src/zig_zon.egg-info/PKG-INFO
15
+ src/zig_zon.egg-info/SOURCES.txt
16
+ src/zig_zon.egg-info/dependency_links.txt
17
+ src/zig_zon.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ extract_link_info
2
+ test_zig_zon
3
+ zig_zon
@@ -0,0 +1,276 @@
1
+ const std = @import("std");
2
+ const Ast = std.zig.Ast;
3
+ const Allocator = std.mem.Allocator;
4
+ const assert = std.debug.assert;
5
+ const ZonGen = std.zig.ZonGen;
6
+
7
+ const c = @import("cpython");
8
+ const PyObject = c.PyObject;
9
+
10
+ const PyModuleDef_Base = extern struct {
11
+ ob_base: PyObject,
12
+ // m_init: ?fn () callconv(.C) [*c]PyObject = null,
13
+ m_init: ?*const fn () callconv(.c) [*c]PyObject = null,
14
+ m_index: c.Py_ssize_t = 0,
15
+ m_copy: [*c]PyObject = null,
16
+ };
17
+
18
+ const PyModuleDef_HEAD_INIT = PyModuleDef_Base{ .ob_base = PyObject{
19
+ .unnamed_0 = .{
20
+ .ob_refcnt = 1,
21
+ },
22
+ .ob_type = null,
23
+ } };
24
+
25
+ pub var methods = [_]c.PyMethodDef{
26
+ c.PyMethodDef{
27
+ .ml_name = "parse",
28
+ .ml_meth = @ptrCast(@alignCast(&parse)),
29
+ .ml_flags = @as(c_int, 1),
30
+ .ml_doc = "parse a zon literal to a python object",
31
+ },
32
+ c.PyMethodDef{},
33
+ };
34
+
35
+ pub var zigmodule = c.PyModuleDef{
36
+ .m_name = "zig_zon",
37
+ .m_methods = &methods,
38
+ };
39
+
40
+ pub export fn PyInit_zig_zon() [*c]c.PyObject {
41
+ return c.PyModule_Create(&zigmodule);
42
+ }
43
+
44
+ pub fn parse(self: [*]PyObject, args: [*]PyObject) callconv(.c) [*c]PyObject {
45
+ _ = self;
46
+ var zon_txt: [*c]u8 = undefined;
47
+ if (!(c.PyArg_ParseTuple(args, "s", &zon_txt) != 0)) return null;
48
+
49
+ var arena_instance = std.heap.ArenaAllocator.init(std.heap.c_allocator);
50
+ defer arena_instance.deinit();
51
+ const arena = arena_instance.allocator();
52
+
53
+ const ast = Ast.parse(arena, std.mem.sliceTo(zon_txt, 0), .zon) catch |err| {
54
+ const errstr = std.fmt.allocPrintSentinel(
55
+ arena,
56
+ "zig-zon ast error: {s}",
57
+ .{@errorName(err)},
58
+ 0,
59
+ ) catch "";
60
+ c.PyErr_SetString(c.PyExc_Exception, errstr.ptr);
61
+ return null;
62
+ };
63
+
64
+ // TODO: use zoir instead of AST maybe?
65
+ // this is unused for now and instead i walk the AST, but i keep it in as a sanity check for now.
66
+ var zoir = ZonGen.generate(arena, ast, .{ .parse_str_lits = false }) catch |err| {
67
+ const errstr = std.fmt.allocPrintSentinel(
68
+ arena,
69
+ "zig-zon zoir error: {s}",
70
+ .{@errorName(err)},
71
+ 0,
72
+ ) catch "";
73
+ c.PyErr_SetString(c.PyExc_Exception, errstr.ptr);
74
+ return null;
75
+ };
76
+ defer zoir.deinit(arena);
77
+
78
+ const main_node_index = ast.nodeData(.root).node;
79
+
80
+ return parseZon(arena, ast, main_node_index) catch |err| {
81
+ const errstr = std.fmt.allocPrintSentinel(
82
+ arena,
83
+ "zig-zon parse error: {s}",
84
+ .{@errorName(err)},
85
+ 0,
86
+ ) catch "";
87
+ c.PyErr_SetString(c.PyExc_Exception, errstr.ptr);
88
+ return null;
89
+ };
90
+ }
91
+
92
+ const ZonError = error{
93
+ StructExpected,
94
+ StructIdentifierExpected,
95
+ NotImplemented,
96
+ ZonArrayError,
97
+ } || IdentifierError || NumberLiteralError;
98
+
99
+ fn parseZon(arena: Allocator, tree: Ast, node: Ast.Node.Index) ZonError!*c.PyObject {
100
+ const node_tag = tree.nodeTag(node);
101
+ switch (node_tag) {
102
+ .struct_init_comma,
103
+ .struct_init_dot_comma,
104
+ .struct_init_dot_two_comma,
105
+ .struct_init_dot_two,
106
+ .struct_init_dot,
107
+ .struct_init_one_comma,
108
+ .struct_init_one,
109
+ .struct_init,
110
+ => return parseZonStruct(arena, tree, node),
111
+ .array_init_comma,
112
+ .array_init_dot_comma,
113
+ .array_init_dot_two_comma,
114
+ .array_init_dot_two,
115
+ .array_init_dot,
116
+ .array_init_one_comma,
117
+ .array_init_one,
118
+ .array_init,
119
+ => return parseZonArray(arena, tree, node),
120
+ .enum_literal => {
121
+ const name_token = tree.firstToken(node);
122
+ const enum_name = tree.tokenSlice(name_token + 1);
123
+ if (std.mem.startsWith(u8, enum_name, "@")) {
124
+ std.log.err("non bare enum literals not implemented: {s}", .{enum_name});
125
+ return error.NotImplemented;
126
+ }
127
+
128
+ return c.Py_BuildValue("s#", enum_name.ptr, enum_name.len);
129
+ },
130
+ .string_literal => {
131
+ const name_token = tree.firstToken(node);
132
+ const name = tree.tokenSlice(name_token);
133
+ var buf = std.ArrayListUnmanaged(u8).empty;
134
+ try parseStrLit(arena, &buf, name, 0);
135
+ return c.Py_BuildValue("s#", buf.items.ptr, buf.items.len);
136
+ },
137
+ .negation => return parseNumberLiteral(arena, tree, node, true),
138
+ .number_literal => return parseNumberLiteral(arena, tree, node, false),
139
+ .identifier => {
140
+ const token_slice = tree.tokenSlice(tree.firstToken(node));
141
+ if (std.mem.eql(u8, token_slice, "true")) {
142
+ return c.PyBool_FromLong(@as(c_long, 1));
143
+ } else if (std.mem.eql(u8, token_slice, "false")) {
144
+ return c.PyBool_FromLong(@as(c_long, 0));
145
+ } else if (std.mem.eql(u8, token_slice, "null")) {
146
+ return c.Py_BuildValue(""); // c.Py_None()
147
+ } else {
148
+ if (@errorReturnTrace()) |et| {
149
+ std.debug.dumpStackTrace(et.*);
150
+ std.debug.dumpCurrentStackTrace(null);
151
+ }
152
+ std.log.err("not implemented: {any} '{s}'", .{ node_tag, token_slice });
153
+ return error.NotImplemented;
154
+ }
155
+ },
156
+ else => {
157
+ if (@errorReturnTrace()) |et| {
158
+ std.debug.dumpStackTrace(et.*);
159
+ std.debug.dumpCurrentStackTrace(null);
160
+ }
161
+ const token_slice = tree.tokenSlice(tree.firstToken(node));
162
+ std.log.err("not implemented: {any} '{s}'", .{ node_tag, token_slice });
163
+ return error.NotImplemented;
164
+ },
165
+ }
166
+ }
167
+
168
+ const NumberLiteralError = error{
169
+ BigIntNotImplemented,
170
+ InvalidCharacter,
171
+ HexFloatNotImplemented,
172
+ NumberLiteralFail,
173
+ };
174
+
175
+ fn parseNumberLiteral(arena: Allocator, tree: Ast, node: Ast.Node.Index, negated: bool) NumberLiteralError!*c.PyObject {
176
+ _ = arena;
177
+ const num_token = tree.lastToken(node);
178
+ const token_bytes = tree.tokenSlice(num_token);
179
+ const parsed = std.zig.parseNumberLiteral(token_bytes);
180
+ switch (parsed) {
181
+ .int => |n| {
182
+ const factor: i64 = if (negated) -1 else 1;
183
+ return c.Py_BuildValue("n", factor * @as(i64, @bitCast(n)));
184
+ },
185
+ .big_int => return error.BigIntNotImplemented,
186
+ .float => |n| {
187
+ const float_payload = switch (n) {
188
+ .decimal => token_bytes,
189
+ .hex => return error.HexFloatNotImplemented,
190
+ };
191
+ const float = try std.fmt.parseFloat(f64, float_payload);
192
+ const factor: f64 = if (negated) -1 else 1;
193
+
194
+ return c.Py_BuildValue("f", factor * float);
195
+ },
196
+ .failure => |err| {
197
+ std.log.err("number literal: {} bytes: {s}", .{ err, token_bytes });
198
+ return error.NumberLiteralFail;
199
+ },
200
+ }
201
+ }
202
+
203
+ fn parseZonStruct(arena: Allocator, tree: Ast, node: Ast.Node.Index) ZonError!*c.PyObject {
204
+ var buf: [2]Ast.Node.Index = undefined;
205
+ const struct_init = tree.fullStructInit(&buf, node) orelse return error.StructExpected;
206
+
207
+ const ret = c.PyDict_New();
208
+
209
+ for (struct_init.ast.fields) |field_init| {
210
+ const name_token = tree.firstToken(field_init) - 2;
211
+ const field_name = try identifierTokenString(arena, tree, name_token);
212
+ const field_obj = try parseZon(arena, tree, field_init);
213
+ const set_result = c.PyDict_SetItemString(ret, field_name, field_obj);
214
+ assert(set_result >= 0);
215
+ }
216
+ return ret;
217
+ }
218
+
219
+ fn parseZonArray(arena: Allocator, tree: Ast, node: Ast.Node.Index) !*c.PyObject {
220
+ // ret.* = c.Py_BuildValue("l", @as(u64, 1));
221
+ var buf: [2]Ast.Node.Index = undefined;
222
+ const array_init = tree.fullArrayInit(&buf, node) orelse {
223
+ const tok = tree.nodeMainToken(node);
224
+ std.log.err("expected paths expression to be a list of strings, got: {d}", .{tok});
225
+ return error.ZonArrayError;
226
+ };
227
+
228
+ const ret = c.PyList_New(0);
229
+
230
+ for (array_init.ast.elements) |elem_node| {
231
+ const field_ret = try parseZon(arena, tree, elem_node);
232
+ assert(c.PyList_Append(ret, field_ret) >= 0);
233
+ }
234
+ return ret;
235
+ }
236
+
237
+ const IdentifierError = error{OutOfMemory} || StringLiteralError;
238
+
239
+ fn identifierTokenString(arena: Allocator, ast: Ast, token: Ast.TokenIndex) IdentifierError![:0]const u8 {
240
+ std.debug.assert(ast.tokenTag(token) == .identifier);
241
+ const ident_name = ast.tokenSlice(token);
242
+ if (!std.mem.startsWith(u8, ident_name, "@")) {
243
+ return try arena.dupeZ(u8, ident_name);
244
+ }
245
+
246
+ var buf = std.ArrayListUnmanaged(u8).empty;
247
+ try parseStrLit(arena, &buf, ident_name, 1);
248
+ try buf.append(arena, 0);
249
+ return @ptrCast(buf.items);
250
+ }
251
+
252
+ const StringLiteralError = error{
253
+ StringLiteralFail,
254
+ WriteFailed,
255
+ };
256
+
257
+ fn parseStrLit(
258
+ arena: Allocator,
259
+ buf: *std.ArrayListUnmanaged(u8),
260
+ bytes: []const u8,
261
+ offset: u32,
262
+ ) StringLiteralError!void {
263
+ const raw_string = bytes[offset..];
264
+ const result = r: {
265
+ var aw: std.Io.Writer.Allocating = .fromArrayList(arena, buf);
266
+ defer buf.* = aw.toArrayList();
267
+ break :r try std.zig.string_literal.parseWrite(&aw.writer, raw_string);
268
+ };
269
+ switch (result) {
270
+ .success => {},
271
+ .failure => |err| {
272
+ std.log.err("StringLiteralFail: {}", .{err});
273
+ return error.StringLiteralFail;
274
+ },
275
+ }
276
+ }
@@ -0,0 +1,5 @@
1
+ const std = @import("std");
2
+
3
+ pub fn main() !void {
4
+ std.log.info("Hello World", .{});
5
+ }