diff options
| -rw-r--r-- | examples/main.c | 4 | ||||
| -rw-r--r-- | go/typdown.go | 65 | ||||
| -rw-r--r-- | include/typdown.h | 4 | ||||
| -rw-r--r-- | src/lexer/Lexer.zig | 25 | ||||
| -rw-r--r-- | src/parser.zig | 37 | ||||
| -rw-r--r-- | src/root.zig | 12 |
6 files changed, 106 insertions, 41 deletions
diff --git a/examples/main.c b/examples/main.c index d28af79..6ab775f 100644 --- a/examples/main.c +++ b/examples/main.c @@ -9,7 +9,7 @@ void foo(char *v) { for (int i = 0; i < doc.errors_len; i++) { struct typdown_Error error = doc.errors[i]; printf("cannot parse '%s', error: %s (%d)\n", v, typdown_getErrorString(error.code), error.code); - printf("%d to %d ", error.location.beg, error.location.end); + printf("line %d: ", error.location.line); for (int j = error.location.beg; j < error.location.end; j++) printf("%c", v[j]); printf("\n"); @@ -18,7 +18,7 @@ void foo(char *v) { return; } uint8_t code; - char *res = typdown_renderHTML(doc, &code); + char *res = typdown_renderHTML(doc.root, &code); if (code != 0) { printf("cannot render '%s', error: %s (%d)\n", v, typdown_getErrorString(code), code); typdown_free(doc); diff --git a/go/typdown.go b/go/typdown.go index d93d4ca..5e784b4 100644 --- a/go/typdown.go +++ b/go/typdown.go @@ -1,11 +1,16 @@ package typdown -// #cgo LDFLAGS: -L${SRCDIR}/zig-out/lib -ltypdown -// #include <stdlib.h> -// #include "typdown.h" +/* +#cgo LDFLAGS: -L${SRCDIR}/zig-out/lib -ltypdown +#include <stdlib.h> +#include "typdown.h" + +typedef struct typdown_Document Document; +*/ import "C" import ( "errors" + "fmt" "html/template" "unsafe" ) @@ -36,32 +41,62 @@ var ( ErrInvalidMathBlock = errors.New("invalid math block") ) +type Error struct { + code uint8 + source *string + location ErrorLocation +} + +func (e Error) Unwrap() error { + return codeErrors[uint8(e.code)] +} + +func (e Error) Error() string { + loc := e.location + return fmt.Sprintf("%s: %s (line %d)", e.Unwrap().Error(), loc.source, loc.line) +} + +func (e Error) Is(err error) bool { + return errors.Is(e.Unwrap(), err) +} + +type ErrorLocation struct { + source string + line uint +} + type Document struct { - ptr unsafe.Pointer + Errors []Error + embed C.Document } -func Parse(content string) (*Document, error) { - code := C.uchar(0) +func Parse(content string) Document { conv := C.CString(content) - raw := C.typdown_parse(conv, &code) defer C.free(unsafe.Pointer(conv)) - if code > 0 { - err := codeErrors[uint8(code)] - if code == 1 { - panic(err) + rawDoc := C.typdown_parse(conv) + doc := Document{embed: rawDoc} + if rawDoc.errors_len > 0 { + doc.Errors = make([]Error, rawDoc.errors_len) + errs := unsafe.Slice(rawDoc.errors, rawDoc.errors_len) + for i := range rawDoc.errors_len { + raw := errs[i] + doc.Errors[i] = Error{ + code: uint8(raw.code), + source: &content, + location: ErrorLocation{source: content[raw.location.beg:raw.location.end], line: uint(raw.location.line)}, + } } - return nil, err } - return &Document{raw}, nil + return doc } func (d *Document) Close() { - C.typdown_free(d.ptr) + C.typdown_free(d.embed) } func (d *Document) RenderHTML() (template.HTML, error) { code := C.uchar(0) - raw := C.typdown_renderHTML(d.ptr, &code) + raw := C.typdown_renderHTML(d.embed.root, &code) defer C.free(unsafe.Pointer(raw)) if code > 0 { err := codeErrors[uint8(code)] diff --git a/include/typdown.h b/include/typdown.h index abf395d..0e954d4 100644 --- a/include/typdown.h +++ b/include/typdown.h @@ -4,7 +4,7 @@ struct typdown_Error { uint8_t code; - struct {size_t beg; size_t end;} location; + struct {size_t beg; size_t end; size_t line;} location; }; struct typdown_Document { @@ -17,4 +17,4 @@ char * typdown_getErrorString(uint8_t); struct typdown_Document typdown_parse(char *); void typdown_free(struct typdown_Document); -char * typdown_renderHTML(struct typdown_Document, uint8_t *); +char * typdown_renderHTML(void *, uint8_t *); diff --git a/src/lexer/Lexer.zig b/src/lexer/Lexer.zig index c14ce01..b72fe3b 100644 --- a/src/lexer/Lexer.zig +++ b/src/lexer/Lexer.zig @@ -9,6 +9,7 @@ iter: unicode.Utf8Iterator, force_lit: bool = false, before: ?Token = null, new_line: bool = true, +current_line: usize = 1, const Self = @This(); @@ -38,6 +39,7 @@ pub fn next(self: *Self) ?Token { var current_kind: ?Token.Kind = null; while (self.iter.nextCodepointSlice()) |rune| { if (eql(u8, rune, "\r")) continue; + if (eql(u8, rune, "\n")) self.current_line += 1; var override_if: ?[]const u8 = null; // escape chars if (eql(u8, rune, "\\")) { @@ -243,6 +245,8 @@ test "lexer image" { } test "lexer multiline" { + const expect = std.testing.expect; + var l = try init( \\# Title \\ @@ -255,20 +259,35 @@ test "lexer multiline" { ); try doTest(&l, .title, "#"); + try expect(l.current_line == 1); try doTest(&l, .literal, "Title"); + try expect(l.current_line == 1); try doTest(&l, .strong_delimiter, "\n\n"); + try expect(l.current_line == 3); try doTest(&l, .literal, "paragraph"); + try expect(l.current_line == 3); try doTest(&l, .weak_delimiter, "\n"); + try expect(l.current_line == 4); try doTest(&l, .title, "#"); + try expect(l.current_line == 4); try doTest(&l, .literal, "a title"); + try expect(l.current_line == 4); try doTest(&l, .weak_delimiter, "\n"); + try expect(l.current_line == 5); try doTest(&l, .literal, "a # in sentence"); + try expect(l.current_line == 5); try doTest(&l, .strong_delimiter, "\n\n"); + try expect(l.current_line == 7); try doTest(&l, .tag, "#"); + try expect(l.current_line == 7); try doTest(&l, .literal, "tag"); + try expect(l.current_line == 7); try doTest(&l, .weak_delimiter, "\n"); + try expect(l.current_line == 8); try doTest(&l, .tag, "#"); + try expect(l.current_line == 8); try doTest(&l, .literal, "tag2"); + try expect(l.current_line == 8); try std.testing.expect(l.next() == null); } @@ -299,8 +318,14 @@ test "trim space" { ); try expect(l.next().?.equals(.quote, ">")); + try expect(l.current_line == 1); try expect(l.next().?.equals(.literal, "hello")); + try expect(l.current_line == 1); try expect(l.next().?.equals(.weak_delimiter, "\n")); + try expect(l.current_line == 2); try expect(l.next().?.equals(.quote, ">")); + try expect(l.current_line == 2); try expect(l.next().?.equals(.literal, "world")); + try expect(l.current_line == 2); + try expect(l.next() == null); } diff --git a/src/parser.zig b/src/parser.zig index e3eb5d5..f49bdc4 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -25,12 +25,27 @@ pub const Error = error{FeatureNotSupported} || math.Error || Allocator.Error; +const Self = @This(); + /// Document represents a parsed typdown document. pub const Document = struct { /// Root of the document: used to render the document is other languages. root: *Element.Root, /// Errors got while parsing the document. - errors: ?[]DocError = null, + errors: ?[]Document.Error = null, + + /// DocError contains information about the error. + pub const Error = struct { + /// Error returned. + err: Self.Error, + /// Location of the error in the source. + location: struct { beg: usize, end: usize, line: usize }, + + /// Extract the invalid content from the source. + pub fn extract(self: @This(), source: []const u8) []const u8 { + return source[self.location.beg..self.location.end]; + } + }; pub fn deinit(self: @This(), alloc: Allocator) void { self.root.deinit(); @@ -38,19 +53,6 @@ pub const Document = struct { } }; -/// DocError contains information about the error. -pub const DocError = struct { - /// Error returned. - err: Error, - /// Location of the error in the source. - location: struct { beg: usize, end: usize }, - - /// Extract the invalid content from the source. - pub fn extract(self: @This(), source: []const u8) []const u8 { - return source[self.location.beg..self.location.end]; - } -}; - pub fn parseReader(parent: Allocator, r: *std.io.Reader) !Document { var l = try Lexer.initReader(parent, r); defer parent.free(l.iter.bytes); @@ -66,7 +68,7 @@ fn gen(parent: Allocator, l: *Lexer) Allocator.Error!Document { var root = try Element.Root.init(parent); errdefer root.deinit(); const alloc = root.allocator(); - var doc_errors = std.ArrayList(DocError).empty; + var doc_errors = std.ArrayList(Document.Error).empty; errdefer doc_errors.deinit(parent); base: while (l.peek()) |it| { const beg = l.iter.i; @@ -93,7 +95,10 @@ fn gen(parent: Allocator, l: *Lexer) Allocator.Error!Document { if (err == Error.OutOfMemory) return Error.OutOfMemory; var end = l.iter.i; if (beg == end) end += 1; - try doc_errors.append(parent, .{ .err = err, .location = .{ .beg = beg, .end = end } }); + try doc_errors.append(parent, .{ + .err = err, + .location = .{ .beg = beg, .end = end, .line = l.current_line }, + }); _ = l.next(); // consume until next delimiter while (l.next()) |next| if (next.kind.isDelimiter()) continue :base; diff --git a/src/root.zig b/src/root.zig index 30386e9..5d8125f 100644 --- a/src/root.zig +++ b/src/root.zig @@ -53,7 +53,7 @@ const typdown_Document = extern struct { const typdown_Error = extern struct { code: u8, - location: extern struct { beg: usize, end: usize }, + location: extern struct { beg: usize, end: usize, line: usize }, }; }; @@ -65,7 +65,7 @@ fn from(alloc: Allocator, doc: Document) typdown_Document { for (errors) |err| { res.errors.?[0] = .{ .code = getErrorCode(err.err), - .location = .{ .beg = err.location.beg, .end = err.location.end }, + .location = .{ .beg = err.location.beg, .end = err.location.end, .line = err.location.line }, }; } res.errors_len = errors.len; @@ -75,7 +75,7 @@ fn from(alloc: Allocator, doc: Document) typdown_Document { fn fromError(alloc: Allocator, err: Error) typdown_Document { var v = alloc.alloc(typdown_Document.typdown_Error, 1) catch |e| @panic(@errorName(e)); - v[0] = .{ .code = getErrorCode(err), .location = .{ .beg = 0, .end = 0 } }; + v[0] = .{ .code = getErrorCode(err), .location = .{ .beg = 0, .end = 0, .line = 0 } }; return .{ .errors = v.ptr, .errors_len = 1 }; } @@ -108,8 +108,8 @@ export fn typdown_free(self: typdown_Document) void { } /// Render an HTML from the document. -export fn typdown_renderHTML(document: typdown_Document, code: *u8) ?[*:0]const u8 { - const root: *Element.Root = @ptrCast(@alignCast(document.root)); +export fn typdown_renderHTML(context: *anyopaque, code: *u8) ?[*:0]const u8 { + const root: *Element.Root = @ptrCast(@alignCast(context)); const res = root.renderHTML(default_alloc) catch |err| { code.* = getErrorCode(err); return null; @@ -136,7 +136,7 @@ fn doTest(content: [*:0]const u8, exp: []const u8, comptime exp_code: u8) !void return try expect(false); } var code: u8 = undefined; - const raw = typdown_renderHTML(doc, &code) orelse { + const raw = typdown_renderHTML(doc.root, &code) orelse { expect(code == exp_code) catch |err| { std.debug.print("{}\n", .{code}); return err; |
