Skip to content

Commit aa0ac09

Browse files
committed
feat: C API
1 parent f9a20fd commit aa0ac09

3 files changed

Lines changed: 384 additions & 0 deletions

File tree

build.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ pub fn build(b: *std.Build) void {
1414
.root_module = lib_mod,
1515
});
1616

17+
const c_api = b.addLibrary(.{
18+
.name = "fehler",
19+
.root_module = b.createModule(.{
20+
.root_source_file = b.path("src/c_api.zig"),
21+
.target = target,
22+
.optimize = optimize,
23+
}),
24+
.linkage = .dynamic,
25+
});
26+
27+
c_api.installHeader(b.path("include/fehler.h"), "fehler.h");
28+
29+
b.installArtifact(c_api);
30+
1731
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
1832

1933
const test_step = b.step("test", "Run unit tests");

include/fehler.h

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/**
2+
* Fehler - Diagnostic Reporting Library
3+
*
4+
* A rich, colorful compiler-style diagnostic reporting library
5+
* with support for source ranges, multiple output formats, and SARIF export.
6+
*
7+
* Version: 0.6.1
8+
* License: MIT
9+
*/
10+
11+
#ifndef FEHLER_H
12+
#define FEHLER_H
13+
14+
#ifdef __cplusplus
15+
extern "C" {
16+
#endif
17+
18+
#include <stddef.h>
19+
#include <stdint.h>
20+
21+
/**
22+
* Opaque handle to an ErrorReporter instance.
23+
* Manages source files and formats diagnostics for output.
24+
*/
25+
typedef struct FehlerReporter FehlerReporter;
26+
27+
/**
28+
* Opaque handle to a Diagnostic instance.
29+
* Represents a single error, warning, or note with optional source location.
30+
*/
31+
typedef struct FehlerDiagnostic FehlerDiagnostic;
32+
33+
/**
34+
* Severity levels for diagnostics.
35+
* Determines the color and label used when displaying the diagnostic.
36+
*/
37+
typedef enum FehlerSeverity {
38+
FEHLER_SEVERITY_FATAL = 0,
39+
FEHLER_SEVERITY_ERROR = 1,
40+
FEHLER_SEVERITY_WARN = 2,
41+
FEHLER_SEVERITY_NOTE = 3,
42+
FEHLER_SEVERITY_TODO = 4,
43+
FEHLER_SEVERITY_UNIMPLEMENTED = 5,
44+
} FehlerSeverity;
45+
46+
/**
47+
* Output format styles for diagnostics.
48+
*/
49+
typedef enum FehlerFormat {
50+
FEHLER_FORMAT_FEHLER = 0,
51+
FEHLER_FORMAT_GCC = 1,
52+
FEHLER_FORMAT_MSVC = 2,
53+
} FehlerFormat;
54+
55+
/**
56+
* Represents a position in source code.
57+
*/
58+
typedef struct FehlerPosition {
59+
size_t line;
60+
size_t column;
61+
} FehlerPosition;
62+
63+
/**
64+
* Represents a range in source code.
65+
*/
66+
typedef struct FehlerSourceRange {
67+
const char* file;
68+
FehlerPosition start;
69+
FehlerPosition end;
70+
} FehlerSourceRange;
71+
72+
/**
73+
* Creates a new ErrorReporter instance.
74+
*/
75+
FehlerReporter* fehler_reporter_create(void);
76+
77+
/**
78+
* Destroys an ErrorReporter instance and frees all associated memory.
79+
*/
80+
void fehler_reporter_destroy(FehlerReporter* reporter);
81+
82+
/**
83+
* Sets the output format for the reporter.
84+
*/
85+
void fehler_reporter_set_format(FehlerReporter* reporter, FehlerFormat format);
86+
87+
/**
88+
* Adds a source file to the reporter for reference in diagnostics.
89+
* The content is copied and owned by the reporter.
90+
*/
91+
int fehler_reporter_add_source(FehlerReporter* reporter, const char* filename, const char* content);
92+
93+
/**
94+
* Reports a diagnostic to stdout with formatting.
95+
*/
96+
void fehler_reporter_report(FehlerReporter* reporter, FehlerDiagnostic* diagnostic);
97+
98+
/**
99+
* Creates a new Diagnostic instance.
100+
* The message string is copied and owned by the diagnostic.
101+
*/
102+
FehlerDiagnostic* fehler_diagnostic_create(FehlerSeverity severity, const char* message);
103+
104+
/**
105+
* Destroys a Diagnostic instance and frees all associated memory.
106+
*/
107+
void fehler_diagnostic_destroy(FehlerDiagnostic* diagnostic);
108+
109+
/**
110+
* Sets a single-character location for the diagnostic.
111+
*/
112+
int fehler_diagnostic_set_location(FehlerDiagnostic* diagnostic, const char* file, size_t line, size_t column);
113+
114+
/**
115+
* Sets a source range for the diagnostic.
116+
*/
117+
int fehler_diagnostic_set_range(FehlerDiagnostic* diagnostic, const char* file, size_t start_line, size_t start_col, size_t end_line, size_t end_col);
118+
119+
/**
120+
* Sets help text for the diagnostic.
121+
* The help string is copied and owned by the diagnostic.
122+
*/
123+
int fehler_diagnostic_set_help(FehlerDiagnostic* diagnostic, const char* help);
124+
125+
/**
126+
* Sets an error code for the diagnostic.
127+
* The code string is copied and owned by the diagnostic.
128+
*/
129+
int fehler_diagnostic_set_code(FehlerDiagnostic* diagnostic, const char* code);
130+
131+
/**
132+
* Sets a documentation URL for the diagnostic.
133+
* The URL string is copied and owned by the diagnostic.
134+
*/
135+
int fehler_diagnostic_set_url(FehlerDiagnostic* diagnostic, const char* url);
136+
137+
/**
138+
* Quick helper to create and report a simple diagnostic with location.
139+
* Handles creation, configuration, reporting, and cleanup automatically.
140+
*/
141+
void fehler_report_simple(FehlerReporter* reporter, FehlerSeverity severity, const char* message, const char* file, size_t line, size_t column);
142+
143+
/**
144+
* Quick helper to create and report a diagnostic with a range.
145+
* Handles creation, configuration, reporting, and cleanup automatically.
146+
*/
147+
void fehler_report_range(FehlerReporter* reporter, FehlerSeverity severity, const char* message, const char* file, size_t start_line, size_t start_col, size_t end_line, size_t end_col);
148+
149+
// ============================================================================
150+
// Version and Info
151+
// ============================================================================
152+
153+
/**
154+
* Returns the version string of the Fehler library.
155+
*/
156+
const char* fehler_version(void);
157+
158+
/**
159+
* Checks if color output is supported.
160+
*/
161+
int fehler_has_color_support(void);
162+
163+
#ifdef __cplusplus
164+
}
165+
#endif
166+
167+
#endif // FEHLER_H

src/c_api.zig

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
const std = @import("std");
2+
const fehler = @import("root.zig");
3+
4+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
5+
const c_allocator = gpa.allocator();
6+
7+
pub const FehlerReporter = opaque {};
8+
9+
pub const FehlerDiagnostic = opaque {};
10+
11+
pub const FehlerSeverity = enum(c_int) {
12+
fatal = 0,
13+
err = 1,
14+
warn = 2,
15+
note = 3,
16+
todo = 4,
17+
unimplemented = 5,
18+
};
19+
20+
pub const FehlerFormat = enum(c_int) {
21+
fehler = 0,
22+
gcc = 1,
23+
msvc = 2,
24+
};
25+
26+
pub const FehlerPosition = extern struct {
27+
line: usize,
28+
column: usize,
29+
};
30+
31+
pub const FehlerSourceRange = extern struct {
32+
file: [*:0]const u8,
33+
start: FehlerPosition,
34+
end: FehlerPosition,
35+
};
36+
37+
export fn fehler_reporter_create() ?*FehlerReporter {
38+
const reporter = c_allocator.create(fehler.ErrorReporter) catch return null;
39+
reporter.* = fehler.ErrorReporter.init(c_allocator);
40+
return @ptrCast(reporter);
41+
}
42+
43+
export fn fehler_reporter_destroy(reporter: *FehlerReporter) void {
44+
const r: *fehler.ErrorReporter = @ptrCast(@alignCast(reporter));
45+
r.deinit();
46+
c_allocator.destroy(r);
47+
}
48+
49+
export fn fehler_reporter_set_format(reporter: *FehlerReporter, format: FehlerFormat) void {
50+
const r: *fehler.ErrorReporter = @ptrCast(@alignCast(reporter));
51+
r.format = switch (format) {
52+
.fehler => .fehler,
53+
.gcc => .gcc,
54+
.msvc => .msvc,
55+
};
56+
}
57+
58+
export fn fehler_reporter_add_source(
59+
reporter: *FehlerReporter,
60+
filename: [*:0]const u8,
61+
content: [*:0]const u8,
62+
) c_int {
63+
const r: *fehler.ErrorReporter = @ptrCast(@alignCast(reporter));
64+
const filename_slice = std.mem.span(filename);
65+
const content_slice = std.mem.span(content);
66+
r.addSource(filename_slice, content_slice) catch return -1;
67+
return 0;
68+
}
69+
70+
export fn fehler_reporter_report(reporter: *FehlerReporter, diagnostic: *FehlerDiagnostic) void {
71+
const r: *fehler.ErrorReporter = @ptrCast(@alignCast(reporter));
72+
const d: *fehler.Diagnostic = @ptrCast(@alignCast(diagnostic));
73+
r.report(d.*);
74+
}
75+
76+
export fn fehler_diagnostic_create(
77+
severity: FehlerSeverity,
78+
message: [*:0]const u8,
79+
) ?*FehlerDiagnostic {
80+
const diag = c_allocator.create(fehler.Diagnostic) catch return null;
81+
const message_slice = std.mem.span(message);
82+
const owned_message = c_allocator.dupe(u8, message_slice) catch {
83+
c_allocator.destroy(diag);
84+
return null;
85+
};
86+
87+
const sev = switch (severity) {
88+
.fatal => fehler.Severity.fatal,
89+
.err => fehler.Severity.err,
90+
.warn => fehler.Severity.warn,
91+
.note => fehler.Severity.note,
92+
.todo => fehler.Severity.todo,
93+
.unimplemented => fehler.Severity.unimplemented,
94+
};
95+
96+
diag.* = fehler.Diagnostic.init(sev, owned_message);
97+
return @ptrCast(diag);
98+
}
99+
100+
export fn fehler_diagnostic_destroy(diagnostic: *FehlerDiagnostic) void {
101+
const d: *fehler.Diagnostic = @ptrCast(@alignCast(diagnostic));
102+
c_allocator.free(d.message);
103+
if (d.help) |help| c_allocator.free(help);
104+
if (d.code) |code| c_allocator.free(code);
105+
if (d.url) |url| c_allocator.free(url);
106+
c_allocator.destroy(d);
107+
}
108+
109+
export fn fehler_diagnostic_set_location(
110+
diagnostic: *FehlerDiagnostic,
111+
file: [*:0]const u8,
112+
line: usize,
113+
column: usize,
114+
) c_int {
115+
const d: *fehler.Diagnostic = @ptrCast(@alignCast(diagnostic));
116+
const file_slice = std.mem.span(file);
117+
d.range = fehler.SourceRange.single(file_slice, line, column);
118+
return 0;
119+
}
120+
121+
export fn fehler_diagnostic_set_range(
122+
diagnostic: *FehlerDiagnostic,
123+
file: [*:0]const u8,
124+
start_line: usize,
125+
start_col: usize,
126+
end_line: usize,
127+
end_col: usize,
128+
) c_int {
129+
const d: *fehler.Diagnostic = @ptrCast(@alignCast(diagnostic));
130+
const file_slice = std.mem.span(file);
131+
d.range = fehler.SourceRange.span(file_slice, start_line, start_col, end_line, end_col);
132+
return 0;
133+
}
134+
135+
export fn fehler_diagnostic_set_help(
136+
diagnostic: *FehlerDiagnostic,
137+
help: [*:0]const u8,
138+
) c_int {
139+
const d: *fehler.Diagnostic = @ptrCast(@alignCast(diagnostic));
140+
const help_slice = std.mem.span(help);
141+
const owned_help = c_allocator.dupe(u8, help_slice) catch return -1;
142+
if (d.help) |old_help| c_allocator.free(old_help);
143+
d.help = owned_help;
144+
return 0;
145+
}
146+
147+
export fn fehler_diagnostic_set_code(
148+
diagnostic: *FehlerDiagnostic,
149+
code: [*:0]const u8,
150+
) c_int {
151+
const d: *fehler.Diagnostic = @ptrCast(@alignCast(diagnostic));
152+
const code_slice = std.mem.span(code);
153+
const owned_code = c_allocator.dupe(u8, code_slice) catch return -1;
154+
if (d.code) |old_code| c_allocator.free(old_code);
155+
d.code = owned_code;
156+
return 0;
157+
}
158+
159+
export fn fehler_diagnostic_set_url(
160+
diagnostic: *FehlerDiagnostic,
161+
url: [*:0]const u8,
162+
) c_int {
163+
const d: *fehler.Diagnostic = @ptrCast(@alignCast(diagnostic));
164+
const url_slice = std.mem.span(url);
165+
const owned_url = c_allocator.dupe(u8, url_slice) catch return -1;
166+
if (d.url) |old_url| c_allocator.free(old_url);
167+
d.url = owned_url;
168+
return 0;
169+
}
170+
171+
export fn fehler_report_simple(
172+
reporter: *FehlerReporter,
173+
severity: FehlerSeverity,
174+
message: [*:0]const u8,
175+
file: [*:0]const u8,
176+
line: usize,
177+
column: usize,
178+
) void {
179+
const diag = fehler_diagnostic_create(severity, message) orelse return;
180+
defer fehler_diagnostic_destroy(diag);
181+
_ = fehler_diagnostic_set_location(diag, file, line, column);
182+
fehler_reporter_report(reporter, diag);
183+
}
184+
185+
export fn fehler_report_range(
186+
reporter: *FehlerReporter,
187+
severity: FehlerSeverity,
188+
message: [*:0]const u8,
189+
file: [*:0]const u8,
190+
start_line: usize,
191+
start_col: usize,
192+
end_line: usize,
193+
end_col: usize,
194+
) void {
195+
const diag = fehler_diagnostic_create(severity, message) orelse return;
196+
defer fehler_diagnostic_destroy(diag);
197+
_ = fehler_diagnostic_set_range(diag, file, start_line, start_col, end_line, end_col);
198+
fehler_reporter_report(reporter, diag);
199+
}
200+
201+
export fn fehler_version() [*:0]const u8 {
202+
return "0.6.1";
203+
}

0 commit comments

Comments
 (0)