Skip to content

Commit 98f3b8c

Browse files
committed
fixing more crashes!
1 parent 303969a commit 98f3b8c

9 files changed

Lines changed: 80 additions & 44 deletions

File tree

README.md

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# SuperHTML
2-
Actually functional HTML Language Server (and Templating Language Library).
2+
HTML Language Server and Templating Language Library
33

4-
**NOTE: SuperHTML is still incomplete, some features are missing and looking of somebody to implement them :^)**
5-
*And there's also bugs, waiting for people to report them as Issues :^)*
64

75
## HTML Language Server
8-
The Super CLI Tool offers syntax checking and autoformatting features for HTML files.
6+
The Super CLI Tool offers **syntax checking** and **autoformatting** features for HTML files.
7+
8+
The tool can be used either directly (for example by running it on save), or through a LSP client implementation.
99

1010
```
1111
$ super
@@ -36,7 +36,7 @@ As an example, HTML allows for closing some tags implicitly. For example the fol
3636
</ul>
3737
```
3838

39-
This will still be reported as an error by SuperHTML because otherwise the following snippet would have to be considered correct (while it's much probably a typo):
39+
This will still be reported as an error by SuperHTML because otherwise the following snippet would have to be considered correct (while it's most probably a typo):
4040

4141
```html
4242
<h1>Title<h1>
@@ -115,11 +115,27 @@ After:
115115

116116
### Editor support
117117
#### VSCode
118-
1. Download a prebuilt version of `super` from the Releases section (or build it yourself).
119-
2. Put `super` in your `PATH`.
118+
1. Download a prebuilt version of `superhtml` from the Releases section (or build it yourself).
119+
2. Put `superhtml` in your `PATH`.
120120
3. Install the [Super HTML VSCode extension](https://marketplace.visualstudio.com/items?itemName=LorisCro.super).
121121

122-
#### Flow
122+
#### Helix
123+
Add to your `.config/helix/languages.toml`:
124+
```toml
125+
[language-server.superhtml-lsp]
126+
command = "superhtml"
127+
args = ["lsp"]
128+
129+
[[language]]
130+
name = "html"
131+
scope = "source.html"
132+
roots = []
133+
file-types = ["html"]
134+
language-servers = [ "superhtml-lsp" ]
135+
```
136+
See https://helix-editor.com for more information on how to add new language servers.
137+
138+
#### [Flow](https://github.com/neurocyte/flow)
123139
Already defaults to using SuperHTML, just add the executable to your `PATH`.
124140

125141
#### Other editors
@@ -128,7 +144,7 @@ Follow your editor specific intructions on how to define a new Language Server f
128144
*(Also feel free to contribute more specific intructions to this readme / add files under the `editors/` subdirectory).*
129145

130146
## Templating Language Library
131-
SuperHTML is not only an LSP but also an HTML templating language. More on that soon.
147+
SuperHTML is also a HTML templating language. More on that soon.
132148

133149
## Contributing
134150
SuperHTML tracks the latest Zig release (0.13.0 at the moment of writing).

src/cli/lsp.zig

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const std = @import("std");
2+
const builtin = @import("builtin");
23
const assert = std.debug.assert;
34
const lsp = @import("lsp");
45
const types = lsp.types;
@@ -276,16 +277,13 @@ pub const Handler = struct {
276277
log.debug("format request!!", .{});
277278

278279
const doc = self.files.getPtr(request.textDocument.uri) orelse return null;
279-
280280
if (doc.html.errors.len != 0) {
281281
return null;
282282
}
283283

284284
log.debug("format!!", .{});
285285

286-
var buf = std.ArrayList(u8).init(self.gpa);
287-
errdefer buf.deinit();
288-
286+
var buf = std.ArrayList(u8).init(arena);
289287
try doc.html.render(doc.src, buf.writer());
290288

291289
const edits = try lsp.diff.edits(
@@ -295,9 +293,11 @@ pub const Handler = struct {
295293
.@"utf-8",
296294
);
297295

298-
self.gpa.free(doc.src);
299-
doc.src = try buf.toOwnedSlice();
300-
try doc.reparse(self.gpa);
296+
if (builtin.mode == .Debug) {
297+
if (std.mem.eql(u8, buf.items, doc.src)) {
298+
std.debug.assert(edits.items.len == 0);
299+
}
300+
}
301301

302302
return edits.items;
303303
}

src/fuzz.zig

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,33 @@ pub const std_options = .{ .log_level = .err };
55

66
pub fn main() !void {
77
var gpa_impl: std.heap.GeneralPurposeAllocator(.{}) = .{};
8+
89
// this will check for leaks and crash the program if it finds any
910
defer std.debug.assert(gpa_impl.deinit() == .ok);
1011
const gpa = gpa_impl.allocator();
1112

1213
// Read the data from stdin
1314
const stdin = std.io.getStdIn();
14-
const data = try stdin.readToEndAlloc(gpa, std.math.maxInt(usize));
15-
defer gpa.free(data);
15+
const src = try stdin.readToEndAlloc(gpa, std.math.maxInt(usize));
16+
defer gpa.free(src);
1617

17-
const ast = try super.html.Ast.init(gpa, data, .html);
18+
const ast = try super.html.Ast.init(gpa, src, .html);
1819
defer ast.deinit(gpa);
20+
21+
if (ast.errors.len == 0) {
22+
try ast.render(src, std.io.null_writer);
23+
}
1924
}
2025

2126
test "afl++ fuzz cases" {
2227
const cases: []const []const u8 = &.{
2328
@embedFile("fuzz/2.html"),
2429
@embedFile("fuzz/3.html"),
2530
@embedFile("fuzz/12.html"),
31+
@embedFile("fuzz/round2/2.html"),
32+
@embedFile("fuzz/round2/3.html"),
33+
@embedFile("fuzz/round3/2.html"),
34+
@embedFile("fuzz/77.html"),
2635
};
2736

2837
for (cases) |c| {

src/fuzz/77.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
tl></s</>
2+
;sphmlK
3+
<pYn.
4+
5+

src/fuzz/round2/2.html

90 Bytes
Binary file not shown.

src/fuzz/round2/3.html

120 Bytes
Binary file not shown.

src/fuzz/round3/2.html

155 Bytes
Binary file not shown.

src/html/Ast.zig

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -574,10 +574,10 @@ pub fn render(ast: Ast, src: []const u8, w: anytype) !void {
574574
current,
575575
});
576576

577-
switch (current.kind) {
577+
if (pre == 0) switch (current.kind) {
578578
else => {},
579579
.element => indentation -= 1,
580-
}
580+
};
581581

582582
const open_was_vertical = std.ascii.isWhitespace(src[current.open.end]);
583583

@@ -748,7 +748,9 @@ pub fn render(ast: Ast, src: []const u8, w: anytype) !void {
748748
current = ast.nodes[current.first_child_idx];
749749
}
750750
},
751-
.element_void => {
751+
.element_void,
752+
.element_self_closing,
753+
=> {
752754
if (current.next_idx != 0) {
753755
current = ast.nodes[current.next_idx];
754756
} else {
@@ -760,6 +762,7 @@ pub fn render(ast: Ast, src: []const u8, w: anytype) !void {
760762
},
761763
.exit => {
762764
std.debug.assert(current.kind != .element_void);
765+
std.debug.assert(current.kind != .element_self_closing);
763766
last_rbracket = current.close.end;
764767
if (current.close.start != 0) {
765768
const name = blk: {

src/html/Tokenizer.zig

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,7 +1702,9 @@ fn next2(self: *Tokenizer, src: []const u8) ?struct {
17021702
// Set the temporary buffer to the empty string. Emit a U+003C LESS-THAN SIGN character token. Reconsume in the script data double escape start state.
17031703
'a'...'z', 'A'...'Z' => {
17041704
self.idx -= 1;
1705-
@panic("TODO");
1705+
self.state = .{
1706+
.script_data_double_escape_start = state,
1707+
};
17061708
},
17071709
// Anything else
17081710
// Emit a U+003C LESS-THAN SIGN character token. Reconsume in the script data escaped state.
@@ -1722,7 +1724,11 @@ fn next2(self: *Tokenizer, src: []const u8) ?struct {
17221724
// Create a new end tag token, set its tag name to the empty string. Reconsume in the script data escaped end tag name state.
17231725
'a'...'z', 'A'...'Z' => {
17241726
self.idx -= 1;
1725-
@panic("TODO");
1727+
var new = state;
1728+
new.name_start = self.idx;
1729+
self.state = .{
1730+
.script_data_escaped_end_tag_name = new,
1731+
};
17261732
},
17271733
// Anything else
17281734
// Emit a U+003C LESS-THAN SIGN character token and a U+002F SOLIDUS character token. Reconsume in the script data escaped state.
@@ -2348,12 +2354,14 @@ fn next2(self: *Tokenizer, src: []const u8) ?struct {
23482354
},
23492355
// U+003D EQUALS SIGN (=)
23502356
// Switch to the before attribute value state.
2351-
'=' => self.state = .{
2352-
.before_attribute_value = .{
2353-
.tag = state.tag,
2354-
.name = state.name,
2355-
.equal_sign = self.idx - 1,
2356-
},
2357+
'=' => {
2358+
self.state = .{
2359+
.before_attribute_value = .{
2360+
.tag = state.tag,
2361+
.name = state.name,
2362+
.equal_sign = self.idx - 1,
2363+
},
2364+
};
23572365
},
23582366
// U+003E GREATER-THAN SIGN (>)
23592367
// Switch to the data state. Emit the current tag token.
@@ -2383,8 +2391,9 @@ fn next2(self: *Tokenizer, src: []const u8) ?struct {
23832391
// Anything else
23842392
// Start a new attribute in the current tag token. Set that attribute name and value to the empty string. Reconsume in the attribute name state.
23852393
else => {
2386-
self.idx -= 1;
2394+
std.debug.assert(state.name.len() != 0);
23872395

2396+
self.idx -= 1;
23882397
var tag = state.tag;
23892398
tag.attr_count += 1;
23902399

@@ -3513,28 +3522,22 @@ fn next2(self: *Tokenizer, src: []const u8) ?struct {
35133522
// https://html.spec.whatwg.org/multipage/parsing.html#comment-less-than-sign-bang-dash-state
35143523
.comment_less_than_sign_bang_dash => |comment_start| {
35153524
if (!self.consume(src)) {
3525+
self.idx -= 1;
35163526
self.state = .{ .comment_end_dash = comment_start };
35173527
}
35183528
switch (self.current) {
35193529
// U+002D HYPHEN-MINUS (-)
35203530
// Switch to the comment less-than sign bang dash dash state.
3521-
'-' => switch (self.state) {
3522-
else => unreachable,
3523-
.comment_less_than_sign_bang => {
3524-
self.state = .{
3525-
.comment_less_than_sign_bang_dash = comment_start,
3526-
};
3527-
},
3528-
.comment_less_than_sign_bang_dash => {
3529-
self.state = .{
3530-
.comment_less_than_sign_bang_dash_dash = comment_start,
3531-
};
3532-
},
3531+
'-' => self.state = .{
3532+
.comment_less_than_sign_bang_dash_dash = comment_start,
35333533
},
35343534

35353535
// Anything else
35363536
// Reconsume in the comment end dash state.
3537-
else => self.state = .{ .comment_end_dash = comment_start },
3537+
else => {
3538+
self.idx -= 1;
3539+
self.state = .{ .comment_end_dash = comment_start };
3540+
},
35383541
}
35393542
},
35403543
// https://html.spec.whatwg.org/multipage/parsing.html#comment-less-than-sign-bang-dash-dash-state

0 commit comments

Comments
 (0)