zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

html_render.zig (13394B) - Raw


      1 const std = @import("std");
      2 const Ast = std.zig.Ast;
      3 const assert = std.debug.assert;
      4 
      5 const Walk = @import("Walk");
      6 const Decl = Walk.Decl;
      7 
      8 const gpa = std.heap.wasm_allocator;
      9 const Oom = error{OutOfMemory};
     10 
     11 /// Delete this to find out where URL escaping needs to be added.
     12 pub const missing_feature_url_escape = true;
     13 
     14 pub const RenderSourceOptions = struct {
     15     skip_doc_comments: bool = false,
     16     skip_comments: bool = false,
     17     collapse_whitespace: bool = false,
     18     fn_link: Decl.Index = .none,
     19     /// Assumed to be sorted ascending.
     20     source_location_annotations: []const Annotation = &.{},
     21     /// Concatenated with dom_id.
     22     annotation_prefix: []const u8 = "l",
     23 };
     24 
     25 pub const Annotation = struct {
     26     file_byte_offset: u32,
     27     /// Concatenated with annotation_prefix.
     28     dom_id: u32,
     29 };
     30 
     31 pub fn fileSourceHtml(
     32     file_index: Walk.File.Index,
     33     out: *std.ArrayListUnmanaged(u8),
     34     root_node: Ast.Node.Index,
     35     options: RenderSourceOptions,
     36 ) !void {
     37     const ast = file_index.get_ast();
     38     const file = file_index.get();
     39 
     40     const g = struct {
     41         var field_access_buffer: std.ArrayListUnmanaged(u8) = .empty;
     42     };
     43 
     44     const start_token = ast.firstToken(root_node);
     45     const end_token = ast.lastToken(root_node) + 1;
     46 
     47     var cursor: usize = ast.tokenStart(start_token);
     48 
     49     var indent: usize = 0;
     50     if (std.mem.lastIndexOf(u8, ast.source[0..cursor], "\n")) |newline_index| {
     51         for (ast.source[newline_index + 1 .. cursor]) |c| {
     52             if (c == ' ') {
     53                 indent += 1;
     54             } else {
     55                 break;
     56             }
     57         }
     58     }
     59 
     60     var next_annotate_index: usize = 0;
     61 
     62     for (
     63         ast.tokens.items(.tag)[start_token..end_token],
     64         ast.tokens.items(.start)[start_token..end_token],
     65         start_token..,
     66     ) |tag, start, token_index| {
     67         const between = ast.source[cursor..start];
     68         if (std.mem.trim(u8, between, " \t\r\n").len > 0) {
     69             if (!options.skip_comments) {
     70                 try out.appendSlice(gpa, "<span class=\"tok-comment\">");
     71                 try appendUnindented(out, between, indent);
     72                 try out.appendSlice(gpa, "</span>");
     73             }
     74         } else if (between.len > 0) {
     75             if (options.collapse_whitespace) {
     76                 if (out.items.len > 0 and out.items[out.items.len - 1] != ' ')
     77                     try out.append(gpa, ' ');
     78             } else {
     79                 try appendUnindented(out, between, indent);
     80             }
     81         }
     82         if (tag == .eof) break;
     83         const slice = ast.tokenSlice(token_index);
     84         cursor = start + slice.len;
     85 
     86         // Insert annotations.
     87         while (true) {
     88             if (next_annotate_index >= options.source_location_annotations.len) break;
     89             const next_annotation = options.source_location_annotations[next_annotate_index];
     90             if (cursor <= next_annotation.file_byte_offset) break;
     91             try out.writer(gpa).print("<span id=\"{s}{d}\"></span>", .{
     92                 options.annotation_prefix, next_annotation.dom_id,
     93             });
     94             next_annotate_index += 1;
     95         }
     96 
     97         switch (tag) {
     98             .eof => unreachable,
     99 
    100             .keyword_addrspace,
    101             .keyword_align,
    102             .keyword_and,
    103             .keyword_asm,
    104             .keyword_break,
    105             .keyword_catch,
    106             .keyword_comptime,
    107             .keyword_const,
    108             .keyword_continue,
    109             .keyword_defer,
    110             .keyword_else,
    111             .keyword_enum,
    112             .keyword_errdefer,
    113             .keyword_error,
    114             .keyword_export,
    115             .keyword_extern,
    116             .keyword_for,
    117             .keyword_if,
    118             .keyword_inline,
    119             .keyword_noalias,
    120             .keyword_noinline,
    121             .keyword_nosuspend,
    122             .keyword_opaque,
    123             .keyword_or,
    124             .keyword_orelse,
    125             .keyword_packed,
    126             .keyword_anyframe,
    127             .keyword_pub,
    128             .keyword_resume,
    129             .keyword_return,
    130             .keyword_linksection,
    131             .keyword_callconv,
    132             .keyword_struct,
    133             .keyword_suspend,
    134             .keyword_switch,
    135             .keyword_test,
    136             .keyword_threadlocal,
    137             .keyword_try,
    138             .keyword_union,
    139             .keyword_unreachable,
    140             .keyword_var,
    141             .keyword_volatile,
    142             .keyword_allowzero,
    143             .keyword_while,
    144             .keyword_anytype,
    145             .keyword_fn,
    146             => {
    147                 try out.appendSlice(gpa, "<span class=\"tok-kw\">");
    148                 try appendEscaped(out, slice);
    149                 try out.appendSlice(gpa, "</span>");
    150             },
    151 
    152             .string_literal,
    153             .char_literal,
    154             .multiline_string_literal_line,
    155             => {
    156                 try out.appendSlice(gpa, "<span class=\"tok-str\">");
    157                 try appendEscaped(out, slice);
    158                 try out.appendSlice(gpa, "</span>");
    159             },
    160 
    161             .builtin => {
    162                 try out.appendSlice(gpa, "<span class=\"tok-builtin\">");
    163                 try appendEscaped(out, slice);
    164                 try out.appendSlice(gpa, "</span>");
    165             },
    166 
    167             .doc_comment,
    168             .container_doc_comment,
    169             => {
    170                 if (!options.skip_doc_comments) {
    171                     try out.appendSlice(gpa, "<span class=\"tok-comment\">");
    172                     try appendEscaped(out, slice);
    173                     try out.appendSlice(gpa, "</span>");
    174                 }
    175             },
    176 
    177             .identifier => i: {
    178                 if (options.fn_link != .none) {
    179                     const fn_link = options.fn_link.get();
    180                     const fn_token = ast.nodeMainToken(fn_link.ast_node);
    181                     if (token_index == fn_token + 1) {
    182                         try out.appendSlice(gpa, "<a class=\"tok-fn\" href=\"#");
    183                         _ = missing_feature_url_escape;
    184                         try fn_link.fqn(out);
    185                         try out.appendSlice(gpa, "\">");
    186                         try appendEscaped(out, slice);
    187                         try out.appendSlice(gpa, "</a>");
    188                         break :i;
    189                     }
    190                 }
    191 
    192                 if (token_index > 0 and ast.tokenTag(token_index - 1) == .keyword_fn) {
    193                     try out.appendSlice(gpa, "<span class=\"tok-fn\">");
    194                     try appendEscaped(out, slice);
    195                     try out.appendSlice(gpa, "</span>");
    196                     break :i;
    197                 }
    198 
    199                 if (Walk.isPrimitiveNonType(slice)) {
    200                     try out.appendSlice(gpa, "<span class=\"tok-null\">");
    201                     try appendEscaped(out, slice);
    202                     try out.appendSlice(gpa, "</span>");
    203                     break :i;
    204                 }
    205 
    206                 if (std.zig.primitives.isPrimitive(slice)) {
    207                     try out.appendSlice(gpa, "<span class=\"tok-type\">");
    208                     try appendEscaped(out, slice);
    209                     try out.appendSlice(gpa, "</span>");
    210                     break :i;
    211                 }
    212 
    213                 if (file.token_parents.get(token_index)) |field_access_node| {
    214                     g.field_access_buffer.clearRetainingCapacity();
    215                     try walkFieldAccesses(file_index, &g.field_access_buffer, field_access_node);
    216                     if (g.field_access_buffer.items.len > 0) {
    217                         try out.appendSlice(gpa, "<a href=\"#");
    218                         _ = missing_feature_url_escape;
    219                         try out.appendSlice(gpa, g.field_access_buffer.items);
    220                         try out.appendSlice(gpa, "\">");
    221                         try appendEscaped(out, slice);
    222                         try out.appendSlice(gpa, "</a>");
    223                     } else {
    224                         try appendEscaped(out, slice);
    225                     }
    226                     break :i;
    227                 }
    228 
    229                 {
    230                     g.field_access_buffer.clearRetainingCapacity();
    231                     try resolveIdentLink(file_index, &g.field_access_buffer, token_index);
    232                     if (g.field_access_buffer.items.len > 0) {
    233                         try out.appendSlice(gpa, "<a href=\"#");
    234                         _ = missing_feature_url_escape;
    235                         try out.appendSlice(gpa, g.field_access_buffer.items);
    236                         try out.appendSlice(gpa, "\">");
    237                         try appendEscaped(out, slice);
    238                         try out.appendSlice(gpa, "</a>");
    239                         break :i;
    240                     }
    241                 }
    242 
    243                 try appendEscaped(out, slice);
    244             },
    245 
    246             .number_literal => {
    247                 try out.appendSlice(gpa, "<span class=\"tok-number\">");
    248                 try appendEscaped(out, slice);
    249                 try out.appendSlice(gpa, "</span>");
    250             },
    251 
    252             .bang,
    253             .pipe,
    254             .pipe_pipe,
    255             .pipe_equal,
    256             .equal,
    257             .equal_equal,
    258             .equal_angle_bracket_right,
    259             .bang_equal,
    260             .l_paren,
    261             .r_paren,
    262             .semicolon,
    263             .percent,
    264             .percent_equal,
    265             .l_brace,
    266             .r_brace,
    267             .l_bracket,
    268             .r_bracket,
    269             .period,
    270             .period_asterisk,
    271             .ellipsis2,
    272             .ellipsis3,
    273             .caret,
    274             .caret_equal,
    275             .plus,
    276             .plus_plus,
    277             .plus_equal,
    278             .plus_percent,
    279             .plus_percent_equal,
    280             .plus_pipe,
    281             .plus_pipe_equal,
    282             .minus,
    283             .minus_equal,
    284             .minus_percent,
    285             .minus_percent_equal,
    286             .minus_pipe,
    287             .minus_pipe_equal,
    288             .asterisk,
    289             .asterisk_equal,
    290             .asterisk_asterisk,
    291             .asterisk_percent,
    292             .asterisk_percent_equal,
    293             .asterisk_pipe,
    294             .asterisk_pipe_equal,
    295             .arrow,
    296             .colon,
    297             .slash,
    298             .slash_equal,
    299             .comma,
    300             .ampersand,
    301             .ampersand_equal,
    302             .question_mark,
    303             .angle_bracket_left,
    304             .angle_bracket_left_equal,
    305             .angle_bracket_angle_bracket_left,
    306             .angle_bracket_angle_bracket_left_equal,
    307             .angle_bracket_angle_bracket_left_pipe,
    308             .angle_bracket_angle_bracket_left_pipe_equal,
    309             .angle_bracket_right,
    310             .angle_bracket_right_equal,
    311             .angle_bracket_angle_bracket_right,
    312             .angle_bracket_angle_bracket_right_equal,
    313             .tilde,
    314             => try appendEscaped(out, slice),
    315 
    316             .invalid, .invalid_periodasterisks => return error.InvalidToken,
    317         }
    318     }
    319 }
    320 
    321 fn appendUnindented(out: *std.ArrayListUnmanaged(u8), s: []const u8, indent: usize) !void {
    322     var it = std.mem.splitScalar(u8, s, '\n');
    323     var is_first_line = true;
    324     while (it.next()) |line| {
    325         if (is_first_line) {
    326             try appendEscaped(out, line);
    327             is_first_line = false;
    328         } else {
    329             try out.appendSlice(gpa, "\n");
    330             try appendEscaped(out, unindent(line, indent));
    331         }
    332     }
    333 }
    334 
    335 pub fn appendEscaped(out: *std.ArrayListUnmanaged(u8), s: []const u8) !void {
    336     for (s) |c| {
    337         try out.ensureUnusedCapacity(gpa, 6);
    338         switch (c) {
    339             '&' => out.appendSliceAssumeCapacity("&amp;"),
    340             '<' => out.appendSliceAssumeCapacity("&lt;"),
    341             '>' => out.appendSliceAssumeCapacity("&gt;"),
    342             '"' => out.appendSliceAssumeCapacity("&quot;"),
    343             else => out.appendAssumeCapacity(c),
    344         }
    345     }
    346 }
    347 
    348 fn walkFieldAccesses(
    349     file_index: Walk.File.Index,
    350     out: *std.ArrayListUnmanaged(u8),
    351     node: Ast.Node.Index,
    352 ) Oom!void {
    353     const ast = file_index.get_ast();
    354     assert(ast.nodeTag(node) == .field_access);
    355     const object_node, const field_ident = ast.nodeData(node).node_and_token;
    356     switch (ast.nodeTag(object_node)) {
    357         .identifier => {
    358             const lhs_ident = ast.nodeMainToken(object_node);
    359             try resolveIdentLink(file_index, out, lhs_ident);
    360         },
    361         .field_access => {
    362             try walkFieldAccesses(file_index, out, object_node);
    363         },
    364         else => {},
    365     }
    366     if (out.items.len > 0) {
    367         try out.append(gpa, '.');
    368         try out.appendSlice(gpa, ast.tokenSlice(field_ident));
    369     }
    370 }
    371 
    372 fn resolveIdentLink(
    373     file_index: Walk.File.Index,
    374     out: *std.ArrayListUnmanaged(u8),
    375     ident_token: Ast.TokenIndex,
    376 ) Oom!void {
    377     const decl_index = file_index.get().lookup_token(ident_token);
    378     if (decl_index == .none) return;
    379     try resolveDeclLink(decl_index, out);
    380 }
    381 
    382 fn unindent(s: []const u8, indent: usize) []const u8 {
    383     var indent_idx: usize = 0;
    384     for (s) |c| {
    385         if (c == ' ' and indent_idx < indent) {
    386             indent_idx += 1;
    387         } else {
    388             break;
    389         }
    390     }
    391     return s[indent_idx..];
    392 }
    393 
    394 pub fn resolveDeclLink(decl_index: Decl.Index, out: *std.ArrayListUnmanaged(u8)) Oom!void {
    395     const decl = decl_index.get();
    396     switch (decl.categorize()) {
    397         .alias => |alias_decl| try alias_decl.get().fqn(out),
    398         else => try decl.fqn(out),
    399     }
    400 }