aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/main.c4
-rw-r--r--go/typdown.go65
-rw-r--r--include/typdown.h4
-rw-r--r--src/lexer/Lexer.zig25
-rw-r--r--src/parser.zig37
-rw-r--r--src/root.zig12
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;