Compare commits

...

3 Commits
0.1.0 ... main

Author SHA1 Message Date
Bastian (BaM) 83dbdd2d93 Bump version to 0.1.2 7 months ago
Bastian (BaM) 60cc17f66d feat: implement automatic memory management and custom JSON serialization 7 months ago
Bastian (BaM) 04bbb717c2 Update build.zig.zon 7 months ago
  1. 4
      .github/copilot-instructions.md
  2. 81
      README.md
  3. 84
      build.zig.zon
  4. 16
      examples/birth-date-finder/src/main.zig
  5. 8
      examples/birth-date-finder/src/plain_text_example.zig
  6. 8
      examples/birth-date-finder/src/test_multiple.zig
  7. 48
      src/pi_finder.zig

4
.github/copilot-instructions.md

@ -33,3 +33,7 @@
- The Pi-Finder library is focused on simplicity and performance
- JSON is the standard output format
- Any new features should maintain backward compatibility
## Git Usage
- Always use "git --no-pager" with commands that produce output, such as "git log", "git diff", etc.

81
README.md

@ -5,11 +5,13 @@ A Zig library for searching numeric sequences in the decimal places of Pi.
## Features
- Efficient searching of numeric sequences in Pi decimal places
- JSON output as standard
- Optional plain text output format
- **Automatic memory management** with `result.deinit()`
- **Custom JSON serialization** using `std.json.stringify`
- JSON output as standard format
- Configurable context display with delimiters
- Performance metrics included in results
- Reusable module design
- Performance metrics
- Compatible with Zig 0.14.1
## Installation
@ -59,27 +61,74 @@ pub fn main() !void {
.position = 25,
.delimiter = "-",
.delimiter_interval = 2,
.prefix = "[",
.suffix = "]",
};
// Search for sequence
const result = try pi_finder.searchSequence("123456", context_config, allocator);
defer result.deinit(); // Automatically frees allocated memory
// Output as JSON (default format)
const json_output = try PiFinder.formatSearchResultAsJson(allocator, result);
defer allocator.free(json_output);
// Output as JSON using std.json.stringify
var json_string = std.ArrayList(u8).init(allocator);
defer json_string.deinit();
std.debug.print("{s}\n", .{json_output});
try std.json.stringify(result, .{}, json_string.writer());
std.debug.print("{s}\n", .{json_string.items});
}
```
### Using Plain Text Output Format
### Plain Text Output Example
```zig
// After getting the search result
const plain_text_output = try PiFinder.formatSearchResultAsPlainText(allocator, result);
defer allocator.free(plain_text_output);
if (result.success) {
std.debug.print("Success: {}\n", .{result.success});
std.debug.print("Sequence: {s}\n", .{result.sequence});
if (result.position) |pos| {
std.debug.print("Position: {}\n", .{pos});
}
if (result.context) |context| {
std.debug.print("Context: {s}\n", .{context});
}
} else {
std.debug.print("Search failed: {s}\n", .{result.error_message orelse "Unknown error"});
}
```
## Key Features
### Automatic Memory Management
The library uses RAII (Resource Acquisition Is Initialization) pattern:
```zig
const result = try pi_finder.searchSequence("123456", context_config, allocator);
defer result.deinit(); // Automatically frees allocated context memory
```
### Custom JSON Serialization
std.debug.print("{s}\n", .{plain_text_output});
SearchResult implements custom JSON serialization that excludes internal fields:
```zig
// The allocator field is automatically excluded from JSON output
try std.json.stringify(result, .{}, writer);
```
### Performance Metrics
Every search result includes detailed performance metrics:
```json
{
"performance": {
"file_load_time_ms": 327.988,
"search_time_ms": 10.766,
"file_size_mb": 953.674,
"load_speed_mbs": 2948.052
}
}
```
## API
@ -92,11 +141,6 @@ std.debug.print("{s}\n", .{plain_text_output});
- `getContext(position, config, sequence, allocator)` - Generates context
- `searchSequence(sequence, config, allocator)` - Comprehensive search with structured result
### Output Format Functions
- `formatSearchResultAsJson(allocator, result)` - Formats search result as JSON
- `formatSearchResultAsPlainText(allocator, result)` - Formats search result as plain text
### ContextConfig
Configuration for context display:
@ -120,6 +164,11 @@ Structured result containing:
- `performance` - Performance metrics
- `context_config` - Used context configuration
**Methods:**
- `deinit()` - Frees any dynamically allocated memory (like context)
- `jsonStringify(writer)` - Custom JSON serialization (automatically used by `std.json.stringify`)
## Examples
See the `examples/` directory for complete examples:

84
build.zig.zon

@ -1,23 +1,85 @@
.{
// Basic package information
.name = "pi-finder",
.version = "0.1.0",
// This is the default name used by packages depending on this one. For
// example, when a user runs `zig fetch --save <url>`, this field is used
// as the key in the `dependencies` table. Although the user can choose a
// different name, most users will stick with this provided value.
//
// It is redundant to include "zig" in this name because it is already
// within the Zig package namespace.
.name = .pi_finder,
// Minimum Zig version required for this package
// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.1.2",
// Together with name, this represents a globally unique package
// identifier. This field is generated by the Zig toolchain when the
// package is first created, and then *never changes*. This allows
// unambiguous detection of one package being an updated version of
// another.
//
// When forking a Zig project, this id should be regenerated (delete the
// field and run `zig build`) if the upstream project is still maintained.
// Otherwise, the fork is *hostile*, attempting to take control over the
// original project's identity. Thus it is recommended to leave the comment
// on the following line intact, so that it shows up in code reviews that
// modify the field.
.fingerprint = 0x74ba60f053049abd, // Changing this has security and trust implications.
// Tracks the earliest Zig version that the package considers to be a
// supported use case.
.minimum_zig_version = "0.14.1",
// Package dependencies (currently none, but could be added later)
.dependencies = .{},
// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
//.example = .{
// // When updating this field to a new URL, be sure to delete the corresponding
// // `hash`, otherwise you are communicating that you expect to find the old hash at
// // the new URL. If the contents of a URL change this will result in a hash mismatch
// // which will prevent zig from using it.
// .url = "https://example.com/foo.tar.gz",
//
// // This is computed from the file contents of the directory of files that is
// // obtained after fetching `url` and applying the inclusion rules given by
// // `paths`.
// //
// // This field is the source of truth; packages do not come from a `url`; they
// // come from a `hash`. `url` is just one of many possible mirrors for how to
// // obtain a package matching this `hash`.
// //
// // Uses the [multihash](https://multiformats.io/multihash/) format.
// .hash = "...",
//
// // When this is provided, the package is found in a directory relative to the
// // build root. In this case the package's hash is irrelevant and therefore not
// // computed. This field and `url` are mutually exclusive.
// .path = "foo",
//
// // When this is set to `true`, a package is declared to be lazily
// // fetched. This makes the dependency only get fetched if it is
// // actually used.
// .lazy = false,
//},
},
// Package metadata
// Specifies the set of files and directories that are included in this package.
// Only files and directories listed here are included in the `hash` that
// is computed for this package. Only files listed here will remain on disk
// when using the zig package manager. As a rule of thumb, one should list
// files required for compilation plus any license(s).
// Paths are relative to the build root. Use the empty string (`""`) to refer to
// the build root itself.
// A directory listed here means that all files within, recursively, are included.
.paths = .{
// Include all important project files
"build.zig",
"build.zig.zon",
"src",
"README.md",
"LICENSE",
// Note: we don't include the data directory directly in the package
// as it contains very large files
"examples",
},
}

16
examples/birth-date-finder/src/main.zig

@ -1,5 +1,5 @@
const std = @import("std");
const PiFinder = @import("pi-finder");
const pi = @import("pi-finder");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
@ -10,7 +10,7 @@ pub fn main() !void {
const pi_file_path = "../../data/pi-decimal-10_000_000_000.txt";
// Initialize Pi Finder (JSON output is standard)
var pi_finder = PiFinder.PiFinder.init(allocator, pi_file_path) catch |err| {
var pi_finder = pi.PiFinder.init(allocator, pi_file_path) catch |err| {
std.debug.print("Error initializing Pi finder: {}\n", .{err});
return;
};
@ -20,7 +20,7 @@ pub fn main() !void {
const birth_date = "010102"; // 01.01.02
// Context configuration with delimiter '-'
const context_config = PiFinder.ContextConfig{
const context_config = pi.ContextConfig{
.size = 50,
.position = 25,
.delimiter = "-",
@ -31,18 +31,12 @@ pub fn main() !void {
// Perform search
const result = try pi_finder.searchSequence(birth_date, context_config, allocator);
defer result.deinit();
// Output result as JSON
const json_output = try PiFinder.formatSearchResultAsJson(allocator, result);
const json_output = try pi.formatSearchResultAsJson(allocator, result);
defer allocator.free(json_output);
const stdout = std.io.getStdOut().writer();
try stdout.print("{s}\n", .{json_output});
// Clean up if context was allocated
if (result.context) |context| {
if (context_config.delimiter != null) {
allocator.free(context);
}
}
}

8
examples/birth-date-finder/src/plain_text_example.zig

@ -33,6 +33,7 @@ pub fn main() !void {
// Perform search
const result = try pi_finder.searchSequence(birth_date, context_config, allocator);
defer result.deinit();
// Output result as plain text
const plain_text_output = try PiFinder.formatSearchResultAsPlainText(allocator, result);
@ -40,11 +41,4 @@ pub fn main() !void {
const stdout = std.io.getStdOut().writer();
try stdout.print("{s}\n", .{plain_text_output});
// Clean up if context was allocated
if (result.context) |context| {
if (context_config.delimiter != null) {
allocator.free(context);
}
}
}

8
examples/birth-date-finder/src/test_multiple.zig

@ -41,6 +41,7 @@ pub fn main() !void {
// Perform search
const result = try pi_finder.searchSequence(birth_date, context_config, allocator);
defer result.deinit();
// Output result as JSON
const json_output = try PiFinder.formatSearchResultAsJson(allocator, result);
@ -48,12 +49,5 @@ pub fn main() !void {
const stdout = std.io.getStdOut().writer();
try stdout.print("Searching for {s}:\n{s}\n\n", .{ birth_date, json_output });
// Clean up if context was allocated
if (result.context) |context| {
if (context_config.delimiter != null) {
allocator.free(context);
}
}
}
}

48
src/pi_finder.zig

@ -22,6 +22,7 @@ pub const SearchResult = struct {
error_message: ?[]const u8 = null,
performance: ?PerformanceMetrics = null,
context_config: ?ContextConfig = null,
allocator: ?std.mem.Allocator = null, // privates Feld, wird nicht serialisiert
pub const PerformanceMetrics = struct {
file_load_time_ms: f64,
@ -29,6 +30,49 @@ pub const SearchResult = struct {
file_size_mb: f64,
load_speed_mbs: f64,
};
/// Frees any dynamically allocated memory in the SearchResult
pub fn deinit(self: *const SearchResult) void {
if (self.allocator) |allocator| {
if (self.context) |context| {
allocator.free(context);
}
}
}
/// Custom JSON Serialisierung - ignoriert das allocator Feld
pub fn jsonStringify(self: @This(), writer: anytype) !void {
try writer.beginObject();
try writer.objectField("success");
try writer.write(self.success);
try writer.objectField("sequence");
try writer.write(self.sequence);
try writer.objectField("position");
try writer.write(self.position);
try writer.objectField("context");
try writer.write(self.context);
if (self.error_message != null) {
try writer.objectField("error_message");
try writer.write(self.error_message);
}
if (self.performance != null) {
try writer.objectField("performance");
try writer.write(self.performance);
}
if (self.context_config != null) {
try writer.objectField("context_config");
try writer.write(self.context_config);
}
try writer.endObject();
}
};
/// PiFinder structure with core functionality for searching and formatting sequences
@ -187,6 +231,7 @@ pub const PiFinder = struct {
.position = null,
.context = null,
.error_message = @errorName(err),
.allocator = allocator,
};
};
@ -202,6 +247,7 @@ pub const PiFinder = struct {
.position = pos,
.context = null,
.error_message = @errorName(err),
.allocator = allocator,
};
};
@ -221,6 +267,7 @@ pub const PiFinder = struct {
.load_speed_mbs = load_speed_mbs,
},
.context_config = context_cfg,
.allocator = allocator,
};
} else {
return SearchResult{
@ -229,6 +276,7 @@ pub const PiFinder = struct {
.position = null,
.context = null,
.error_message = "Sequence not found in Pi",
.allocator = allocator,
};
}
}

Loading…
Cancel
Save