1//! This is the JavaScript backend's linker. It takes in a set of `Js.Tree`s and
2//! produces a JavaScript bundle out of them.
3//!
4//! This approach is flawed because it combining too much work in one unit.
5//! Printing and Linking should be separate stages, so that more work can be
6//! done in parallel. This approach is used for now to make identifier name
7//! collisions easier to deal with.
8comp: *Comp,
9backend: *Js,
10
11/// Value is an index into `link_strings`
12decls: std.AutoHashMapUnmanaged(InternPool.Value, LinkString.Index) = .{},
13decl_strings: std.ArrayHashMapUnmanaged(
14 LinkString,
15 GlobalOwner,
16 LinkString.Context,
17 true,
18) = .{},
19string_bytes: std.ArrayListUnmanaged(u8) = .{},
20
21pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void {
22 var t = tracer.trace(@src());
23 defer t.end();
24
25 // 1. Resolve all global identifiers. Function names must not shadow external
26 // names. We also need this list prepared so that local identifier names do not
27 // shadow functions or externals.
28 const root_decl = link.comp.intern_pool.declPtrConst(entry_decl);
29
30 const root_function = link.treeForFunction(root_decl.value());
31
32 for (root_function.extern_symbols) |extern_symbol| {
33 try link.reserveExternSymbol(extern_symbol);
34 }
35
36 var dependency_queue: DependencyQueue = .{};
37 defer dependency_queue.list.deinit(link.comp.gpa);
38 try dependency_queue.list.appendSlice(link.comp.gpa, root_function.top_level_value_deps);
39
40 while (dependency_queue.next()) |value| {
41 const gop_decl = try link.decls.getOrPut(link.comp.gpa, value);
42 if (gop_decl.found_existing) {
43 dependency_queue.swapRemovePrevious();
44 continue;
45 }
46
47 const tree = link.treeForFunction(value);
48
49 for (tree.extern_symbols) |extern_symbol| {
50 try link.reserveExternSymbol(extern_symbol);
51 }
52
53 try dependency_queue.list.appendSlice(link.comp.gpa, tree.top_level_value_deps);
54
55 const decl = link.comp.intern_pool.declPtrConst(
56 value.valueData(&link.comp.intern_pool, .func)
57 .get(&link.comp.intern_pool)
58 .owner_decl,
59 );
60 const name = decl.name.toSlice(&link.comp.intern_pool);
61 assert(name.len > 0);
62
63 const gop_string = try link.decl_strings.getOrPutContextAdapted(
64 link.comp.gpa,
65 name,
66 LinkString.SliceContext{ .link = link },
67 .{ .link = link },
68 );
69 if (gop_string.found_existing) {
70 std.debug.panic("TODO: handle this name collision case: {s}", .{name});
71 }
72 const name_str = try link.appendLinkString(name);
73 gop_string.key_ptr.* = name_str;
74 gop_string.value_ptr.* = .decl;
75 gop_decl.value_ptr.* = @enumFromInt(gop_string.index);
76 }
77
78 try writer.writeAll(std.fmt.comptimePrint(
79 "// ix compiler v{}. alpha quality software.\n",
80 .{@import("config").version},
81 ));
82
83 if (root_function.nodes.len > 1) {
84 // 2. Print every function decl
85 dependency_queue.i = 0;
86 while (dependency_queue.next()) |value| {
87 try link.printTopLevelValue(value, writer);
88 }
89
90 // 3. Print the top-level code
91 try link.printDeclContents(root_function, writer);
92 } else {
93 try writer.print("// (program has no observable effects, thus an empty file)\n", .{});
94 }
95}
96
97fn treeForFunction(link: *const Link, value: InternPool.Value) *Tree {
98 assert(value.valueTag(&link.comp.intern_pool) == .func); // must be a function
99 return link.backend.codegen_map.getPtr(value) orelse
100 unreachable; // did not codegen function
101}
102
103const DependencyQueue = struct {
104 list: std.ArrayListUnmanaged(InternPool.Value) = .{},
105 i: usize = 0,
106
107 pub fn next(queue: *DependencyQueue) ?InternPool.Value {
108 if (queue.i >= queue.list.items.len) {
109 return null;
110 }
111
112 defer queue.i += 1;
113 return queue.list.items[queue.i];
114 }
115
116 pub fn swapRemovePrevious(queue: *DependencyQueue) void {
117 _ = queue.list.swapRemove(queue.i - 1);
118 }
119};
120
121pub fn deinit(link: *Link) void {
122 var t = tracer.trace(@src());
123 defer t.end();
124
125 defer link.* = undefined;
126 link.decls.deinit(link.comp.gpa);
127 link.decl_strings.deinit(link.comp.gpa);
128 link.string_bytes.deinit(link.comp.gpa);
129}
130
131fn reserveExternSymbol(link: *Link, extern_symbol: String.WithLen) !void {
132 var t = tracer.trace(@src());
133 defer t.end();
134
135 const name = extern_symbol.toSlice(&link.comp.intern_pool);
136 const gop = try link.decl_strings.getOrPutContextAdapted(
137 link.comp.gpa,
138 name,
139 LinkString.SliceContext{ .link = link },
140 .{ .link = link },
141 );
142
143 if (gop.found_existing) {
144 if (gop.value_ptr.* != .external) {
145 @panic("TODO: Rename Decl");
146 }
147 }
148 gop.key_ptr.* = try link.appendLinkString(name);
149 gop.value_ptr.* = .external;
150}
151
152fn appendLinkString(link: *Link, string: []const u8) !LinkString {
153 var t = tracer.trace(@src());
154 defer t.end();
155
156 const start = link.string_bytes.items.len;
157 try link.string_bytes.appendSlice(link.comp.gpa, string);
158 return .{ .start = @intCast(start), .len = @intCast(string.len) };
159}
160
161fn valueRefIdentifier(link: *const Link, value: InternPool.Value) []const u8 {
162 var t = tracer.trace(@src());
163 defer t.end();
164
165 return (link.decls.get(value) orelse if (core.runtime_safety) {
166 std.debug.panic("Value \"{}\" is not present in top level value map", .{
167 value.dump(&link.comp.intern_pool),
168 });
169 } else unreachable).toString(link).slice(link);
170}
171
172fn printTopLevelValue(link: *Link, value: InternPool.Value, writer: Writer) !void {
173 var t = tracer.trace(@src());
174 defer t.end();
175
176 try writer.writeAll("let ");
177 try writer.writeAll(link.valueRefIdentifier(value));
178 try writer.writeAll(" = ");
179
180 const tree = link.treeForFunction(value);
181
182 if (tree.param_count != 1)
183 try writer.writeAll("(");
184 for (0..tree.param_count) |id| {
185 try link.printLocal(@intCast(id), writer);
186 if (id != tree.param_count - 1) try writer.writeAll(", ");
187 }
188 if (tree.param_count != 1)
189 try writer.writeAll(")");
190 try writer.writeAll(" => ");
191
192 const stmts = tree.mainStatements();
193 if (stmts.len == 1 and tree.nodeTag(stmts[0]) == .stmt_return) {
194 if (tree.nodeData(.stmt_return, stmts[0]).operand.unwrap()) |operand| {
195 try link.printExprLevel(tree, operand, .statement, writer);
196 } else {
197 try writer.writeAll("{}");
198 }
199 } else {
200 try writer.writeAll("{\n");
201 try link.printDeclContents(tree, writer);
202 try writer.writeAll("}");
203 }
204
205 try writer.writeAll(";\n");
206}
207
208fn printDeclContents(link: *Link, tree: *const Tree, writer: Writer) !void {
209 var t = tracer.trace(@src());
210 defer t.end();
211
212 for (tree.mainStatements()) |stmt| {
213 try link.printExprLevel(tree, stmt, .statement, writer);
214 try writer.writeAll(";\n");
215 }
216}
217
218fn printBlockContents(
219 link: *Link,
220 tree: *const Tree,
221 list: Tree.IndexList,
222 writer: Writer,
223) !void {
224 var t = tracer.trace(@src());
225 defer t.end();
226
227 assert(list.slice(tree).len > 0);
228 for (list.slice(tree)) |stmt| {
229 try link.printExpr(tree, stmt, writer);
230 switch (tree.nodeTag(stmt)) {
231 .stmt_block,
232 .stmt_if,
233 => {},
234 else => try writer.writeAll(";"),
235 }
236 try writer.writeAll("\n");
237 }
238}
239
240fn printBlock(link: *Link, tree: *const Tree, list: Tree.IndexList, writer: Writer) !void {
241 var t = tracer.trace(@src());
242 defer t.end();
243
244 if (list.len != 1) {
245 try writer.writeAll("{");
246 try link.printBlockContents(tree, list, writer);
247 try writer.writeAll("}");
248 } else {
249 try link.printBlockContents(tree, list, writer);
250 }
251}
252
253fn printExpr(
254 link: *Link,
255 tree: *const Tree,
256 node: Node.Index,
257 writer: Writer,
258) @TypeOf(writer).Error!void {
259 return link.printExprLevel(tree, node, .comma, writer);
260}
261
262fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytype) !void {
263 switch (value.toKey(&link.comp.intern_pool)) {
264 // TODO: minification
265 .int32 => |val| try writer.print("{d}", .{val}),
266 // TODO: correct printing of many value types
267 .float => |val| try writer.print("{d}", .{val}),
268 .simple_value => |val| try writer.writeAll(switch (val) {
269 .true => "true",
270 .false => "false",
271 // TODO: make codegen decide from null and undefined
272 // instead of emitting void nodes.
273 .void => "/* void */ undefined",
274 }),
275 .string => |val| try writeString(val, writer),
276
277 .comptime_float => unreachable, // was not lowered to float
278
279 .struct_value => |struct_value| {
280 const wrap = level == .statement;
281 if (wrap)
282 try writer.writeAll("(");
283
284 try writer.writeAll("{");
285
286 for (
287 struct_value.ty.toStructType(&link.comp.intern_pool).?
288 .field_keys.get(&link.comp.intern_pool),
289 struct_value.field_values.get(&link.comp.intern_pool),
290 0..,
291 ) |key, inner_value, i| {
292 if (i != 0)
293 try writer.writeAll(",");
294
295 // TODO: handle special identifiers
296 try writer.writeAll(key.toSlice(&link.comp.intern_pool));
297 try writer.writeAll(":");
298 try link.printValue(inner_value, .comma, writer);
299 }
300 try writer.writeAll("}");
301
302 if (wrap)
303 try writer.writeAll(")");
304 },
305
306 .array_value => |array_value| {
307 try writer.writeAll("[");
308
309 for (array_value.items.get(&link.comp.intern_pool), 0..) |inner_value, i| {
310 if (i != 0)
311 try writer.writeAll(",");
312
313 try link.printValue(inner_value, .assignment, writer);
314 }
315 try writer.writeAll("]");
316 },
317
318 .tuple_value => |tuple_value| {
319 try writer.writeAll("[");
320
321 for (tuple_value.items.get(&link.comp.intern_pool), 0..) |inner_value, i| {
322 if (i != 0)
323 try writer.writeAll(",");
324
325 try link.printValue(inner_value, .assignment, writer);
326 }
327 try writer.writeAll("]");
328 },
329
330 .external_value => |external_value| {
331 for (external_value.key.get(&link.comp.intern_pool), 0..) |part, i| {
332 if (i != 0) try writer.writeAll(".");
333 try writer.writeAll(part.toSlice(&link.comp.intern_pool));
334 }
335 },
336
337 else => unreachable, // this value is not a plain_value
338 }
339}
340
341fn printExprLevel(
342 link: *Link,
343 tree: *const Tree,
344 node: Node.Index,
345 level: Level,
346 writer: Writer,
347) @TypeOf(writer).Error!void {
348 var t = tracer.trace(@src());
349 defer t.end();
350
351 switch (tree.nodeTag(node)) {
352 .root => unreachable,
353
354 .plain_value => {
355 const plain_value: Tree.PlainValue = tree.nodeData(.plain_value, node);
356 try link.printValue(plain_value.value, level, writer);
357 },
358
359 .local => {
360 const ref: Tree.LocalRef = tree.nodeData(.local, node);
361 try link.printLocal(ref.id, writer);
362 },
363
364 .global => {
365 const ref: Tree.GlobalRef = tree.nodeData(.global, node);
366 try writer.writeAll(ref.name.toSlice(&link.comp.intern_pool));
367 },
368
369 .top_level_value => {
370 const ref: Tree.ValueRef = tree.nodeData(.top_level_value, node);
371 try writer.writeAll(link.valueRefIdentifier(ref.value));
372 },
373
374 .stmt_break => {
375 const stmt: Tree.StmtBreak = tree.nodeData(.stmt_break, node);
376 try writer.writeAll("break");
377 if (true) {
378 try writer.writeAll(" ");
379 try link.printLabelIdentifier(stmt.label, writer);
380 }
381 },
382
383 .stmt_return => {
384 const stmt: Tree.StmtReturn = tree.nodeData(.stmt_return, node);
385 try writer.writeAll("return");
386 if (stmt.operand.unwrap()) |operand| {
387 try writer.writeAll(" ");
388 try link.printExpr(tree, operand, writer);
389 }
390 },
391
392 .stmt_throw => {
393 const stmt: Tree.StmtThrow = tree.nodeData(.stmt_throw, node);
394 try writer.writeAll("throw ");
395 try link.printExpr(tree, stmt.operand, writer);
396 },
397
398 .stmt_declare_local => {
399 const stmt: Tree.StmtDeclareLocal = tree.nodeData(.stmt_declare_local, node);
400 try writer.writeAll("let ");
401 try link.printLocal(stmt.identifier_index, writer);
402 if (stmt.init.unwrap()) |init| {
403 try writer.writeAll(" = ");
404 try link.printExpr(tree, init, writer);
405 }
406 },
407
408 .stmt_if => {
409 const stmt: Tree.StmtIf = tree.nodeData(.stmt_if, node);
410 const data = stmt.data.get(tree);
411 try writer.writeAll("if (");
412 try link.printExpr(tree, stmt.cond, writer);
413 try writer.writeAll(") ");
414 if (data.flags.yes_label) {
415 try link.printLabelIdentifier(data.flags.block_label, writer);
416 try writer.writeAll(":");
417 }
418 try link.printBlock(tree, data.yes, writer);
419 if (data.no.len > 0) {
420 try writer.writeAll(" else ");
421 if (data.flags.no_label) {
422 try link.printLabelIdentifier(data.flags.block_label, writer);
423 try writer.writeAll(":");
424 }
425 try link.printBlock(tree, data.no, writer);
426 }
427 },
428
429 .stmt_block => {
430 const stmt: Tree.StmtBlock = tree.nodeData(.stmt_block, node);
431 try link.printLabelIdentifier(stmt.label, writer);
432 try writer.writeAll(": {\n");
433 try link.printBlockContents(tree, stmt.content.get(tree), writer);
434 try writer.writeAll("}");
435 },
436
437 inline .assign,
438 .add,
439 .sub,
440 .mul,
441 .div,
442 .eq,
443 .lt,
444 .gt,
445 .ge,
446 .le,
447 .assign_add,
448 .assign_sub,
449 .assign_mul,
450 .assign_div,
451 .@"and",
452 .@"or",
453 => |tag| {
454 const bin: Tree.Bin = tree.nodeData(tag, node);
455 const op: Operator = @field(Operator, @tagName(tag));
456 const inner_level = comptime op.level();
457 if (inner_level.needsParens(level))
458 try writer.writeAll("(");
459 try link.printExprLevel(tree, bin.lhs, inner_level, writer);
460 try writer.writeAll(" ");
461 try writer.writeAll(comptime op.symbol());
462 try writer.writeAll(" ");
463 try link.printExprLevel(tree, bin.rhs, inner_level, writer);
464 if (inner_level.needsParens(level))
465 try writer.writeAll(")");
466 },
467
468 .index => {
469 const bin: Tree.Bin = tree.nodeData(.index, node);
470 if (Level.access_and_call.needsParens(level))
471 try writer.writeAll("(");
472 try link.printExprLevel(tree, bin.lhs, level, writer);
473 try writer.writeAll("[");
474 try link.printExprLevel(tree, bin.rhs, level, writer);
475 try writer.writeAll("]");
476 if (Level.access_and_call.needsParens(level))
477 try writer.writeAll(")");
478 },
479
480 .comma_chain => {
481 if (Level.comma.needsParens(level))
482 try writer.writeAll("(");
483 const bin: Tree.IndexList = tree.nodeData(.comma_chain, node);
484 for (bin.slice(tree), 0..) |expr, i| {
485 if (i != 0) try writer.writeAll(", ");
486 try link.printExprLevel(tree, expr, .assignment, writer);
487 }
488 if (Level.comma.needsParens(level))
489 try writer.writeAll(")");
490 },
491
492 inline .negate,
493 .bool_not,
494 .pre_dec,
495 .pre_inc,
496 => |tag| {
497 const un: Tree.Un = tree.nodeData(tag, node);
498 const op: Operator = @field(Operator, @tagName(tag));
499 try writer.writeAll(comptime op.symbol());
500 try link.printExpr(tree, un.operand, writer);
501 },
502
503 .ternary => {
504 const ternary: Tree.Ternary = tree.nodeData(.ternary, node);
505 const data = ternary.data.get(tree);
506 const wrap = Level.conditional.needsParens(level);
507 if (wrap)
508 try writer.writeAll("(");
509 try link.printExprLevel(tree, ternary.cond, .logical_or, writer);
510 try writer.writeAll(" ? ");
511 try link.printExprLevel(tree, data.yes, .assignment, writer);
512 try writer.writeAll(" : ");
513 try link.printExprLevel(tree, data.no, .assignment, writer);
514 if (wrap)
515 try writer.writeAll(")");
516 },
517
518 .call => {
519 const call: Tree.Call = tree.nodeData(.call, node);
520 const args = call.args.get(tree);
521
522 if (tree.nodeTag(call.base) == .plain_value and brk: {
523 const k = tree.nodeData(.plain_value, call.base).value.toKey(&link.comp.intern_pool);
524 break :brk k == .external_value and
525 k.external_value.key.len == 1 and
526 std.mem.eql(
527 u8,
528 k.external_value.key.at(&link.comp.intern_pool, 0).toSlice(&link.comp.intern_pool),
529 "eval",
530 );
531 }) {
532 @branchHint(.unlikely);
533 try writer.writeAll("(0, eval)");
534 } else {
535 try link.printExpr(tree, call.base, writer);
536 }
537
538 try writer.writeAll("(");
539
540 for (args.slice(tree), 0..) |arg, i| {
541 if (i != 0) try writer.writeAll(", ");
542 try link.printExprLevel(tree, arg, .assignment, writer);
543 }
544
545 try writer.writeAll(")");
546 },
547
548 .js_new => {
549 const call: Tree.Call = tree.nodeData(.js_new, node);
550 const args = call.args.get(tree);
551
552 const wrap = Level.new.needsParens(level) and level != .member;
553 if (wrap)
554 try writer.writeAll("(");
555 try writer.writeAll("new ");
556 try link.printExpr(tree, call.base, writer);
557
558 if (args.len > 0 or level == .member) {
559 try writer.writeAll("(");
560
561 for (args.slice(tree), 0..) |arg, i| {
562 if (i != 0) try writer.writeAll(", ");
563 try link.printExpr(tree, arg, writer);
564 }
565
566 try writer.writeAll(")");
567 }
568 if (wrap)
569 try writer.writeAll(")");
570 },
571
572 .field => {
573 const field: Tree.Field = tree.nodeData(.field, node);
574
575 try link.printExprLevel(tree, field.base, .member, writer);
576 try writer.writeAll(".");
577 try writer.writeAll(field.field.toSlice(&link.comp.intern_pool));
578 },
579
580 .object_literal => {
581 const obj: Tree.ObjectLiteral = tree.nodeData(.object_literal, node);
582
583 const keys: []const Tree.ObjectLiteral.Key =
584 @ptrCast(tree.extra.list.items[obj.extra_index..][0..obj.property_count]);
585 const values: []const Node.Index =
586 @ptrCast(tree.extra.list.items[obj.extra_index + obj.property_count ..][0..obj.property_count]);
587
588 if (level == .statement)
589 try writer.writeAll("(");
590
591 try writer.writeAll("{");
592
593 for (keys, values, 0..) |key, value, i| {
594 if (i != 0)
595 try writer.writeAll(",");
596
597 switch (key.decode()) {
598 .static => |str| {
599 // TODO: handle strings that cannot be printed as identifiers.
600 try writer.writeAll(str.toSlice(&link.comp.intern_pool));
601 },
602 .computed => @panic("TODO: computed property"),
603 }
604
605 try writer.writeAll(":");
606
607 try link.printExprLevel(tree, value, .assignment, writer);
608 }
609
610 try writer.writeAll("}");
611
612 if (level == .statement)
613 try writer.writeAll(")");
614 },
615
616 .array => {
617 const items: Tree.IndexList = tree.nodeData(.array, node);
618 try writer.writeAll("[");
619 for (items.slice(tree), 0..) |item, i| {
620 if (i != 0) try writer.writeAll(", ");
621 try link.printExprLevel(tree, item, .assignment, writer);
622 }
623 try writer.writeAll("]");
624 },
625 }
626}
627
628pub fn canPrintWithoutEscape(c: u21) bool {
629 const first_ascii_printable = 0x20;
630 const last_ascii_printable = 0x7E;
631 const first_high_surrogate = 0xD800;
632 const last_low_surrogate = 0xDFFF;
633
634 if (c <= last_ascii_printable) {
635 return c >= first_ascii_printable and c != '\\' and c != '"';
636 } else {
637 return c != 0xFEFF and (c < first_high_surrogate or c > last_low_surrogate);
638 }
639}
640
641pub fn indexOfNeedsEscape(slice: []const u8) ?usize {
642 var it = std.unicode.Wtf8Iterator{ .bytes = slice, .i = 0 };
643 var i: usize = 0;
644 while (it.nextCodepoint()) |c| {
645 if (!canPrintWithoutEscape(c))
646 return i;
647 i = it.i;
648 }
649 return null;
650}
651
652// This function is loosely based on Bun's `writeJSONString`, which
653// is a Zig port of esbuild's `printUnquotedUTF16` function.
654// Both projects are licensed under MIT
655fn writeString(decoded: []const u8, writer: Writer) !void {
656 try writer.writeByte('"');
657
658 var text = decoded;
659 while (text.len > 0) {
660 const width = std.unicode.utf8ByteSequenceLength(text[0]) catch
661 unreachable; // string is encoded incorrectly
662
663 const c: u21 = std.unicode.wtf8Decode(text[0..width]) catch
664 unreachable; // string is encoded incorrectly
665
666 if (canPrintWithoutEscape(c)) {
667 if (indexOfNeedsEscape(text)) |j| {
668 try writer.writeAll(text[0..j]);
669 text = text[j..];
670 continue;
671 } else {
672 try writer.writeAll(text);
673 break;
674 }
675 }
676
677 text = text[width..];
678
679 switch (c) {
680 // Special-case the bell character since it may cause dumping this
681 // file to the terminal to make a sound, which is undesirable. Note
682 // that an octal literal to print this shorter since octal literals
683 // are not allowed in strict mode (or in template strings).
684 0x07 => {
685 try writer.writeAll("\\x07");
686 },
687 0x08 => {
688 try writer.writeAll("\\b");
689 },
690 0x0C => {
691 try writer.writeAll("\\f");
692 },
693 '\n' => {
694 try writer.writeAll("\\n");
695 },
696 std.ascii.control_code.cr => {
697 try writer.writeAll("\\r");
698 },
699 std.ascii.control_code.vt => {
700 try writer.writeAll("\\v");
701 },
702 '\\' => {
703 try writer.writeAll("\\\\");
704 },
705 '"' => {
706 try writer.writeAll("\\\"");
707 },
708 '\t' => {
709 try writer.writeAll("\\t");
710 },
711
712 else => {
713 const hex_chars = "0123456789ABCDEF";
714
715 if (c < 0xFFFF) {
716 const k = @as(usize, @intCast(c));
717
718 try writer.writeAll(&[_]u8{
719 '\\',
720 'u',
721 hex_chars[(k >> 12) & 0xF],
722 hex_chars[(k >> 8) & 0xF],
723 hex_chars[(k >> 4) & 0xF],
724 hex_chars[k & 0xF],
725 });
726 } else {
727 const first_high_surrogate = 0xD800;
728 const first_low_surrogate = 0xDC00;
729
730 const k = c - 0x10000;
731 const lo = @as(usize, @intCast(first_high_surrogate + ((k >> 10) & 0x3FF)));
732 const hi = @as(usize, @intCast(first_low_surrogate + (k & 0x3FF)));
733
734 try writer.writeAll(&[_]u8{
735 '\\',
736 'u',
737 hex_chars[lo >> 12],
738 hex_chars[(lo >> 8) & 15],
739 hex_chars[(lo >> 4) & 15],
740 hex_chars[lo & 15],
741 '\\',
742 'u',
743 hex_chars[hi >> 12],
744 hex_chars[(hi >> 8) & 15],
745 hex_chars[(hi >> 4) & 15],
746 hex_chars[hi & 15],
747 });
748 }
749 },
750 }
751 }
752 try writer.writeByte('"');
753}
754
755fn printLocal(link: *Link, local: Tree.LocalIndex, writer: Writer) !void {
756 var t = tracer.trace(@src());
757 defer t.end();
758
759 _ = link; // todo: be smarter about this
760 try writer.print("v{d}", .{local});
761}
762
763fn printLabelIdentifier(link: *Link, local: Tree.LocalIndex, writer: Writer) !void {
764 var t = tracer.trace(@src());
765 defer t.end();
766
767 _ = link; // todo: be smarter about this
768 try writer.print("label{d}", .{local});
769}
770
771const GlobalOwner = enum {
772 external,
773 decl,
774};
775
776// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence
777const Level = enum {
778 /// Extra level used for object literals in arrow function returns
779 statement,
780 comma,
781 assignment,
782 conditional,
783 logical_or,
784 logical_and,
785 bitwise_or,
786 bitwise_xor,
787 bitwise_and,
788 equality,
789 relational,
790 bitshift,
791 additive,
792 multiplicative,
793 exponentiation,
794 prefix,
795 postfix,
796 new,
797 access_and_call,
798 member,
799
800 pub fn needsParens(inner_level: Level, outer_level: Level) bool {
801 return @intFromEnum(outer_level) > @intFromEnum(inner_level);
802 }
803};
804
805const Operator = enum {
806 assign,
807 assign_add,
808 assign_sub,
809 assign_mul,
810 assign_div,
811 add,
812 sub,
813 mul,
814 div,
815 eq,
816 lt,
817 gt,
818 ge,
819 le,
820 negate,
821 bool_not,
822 pre_inc,
823 pre_dec,
824 @"and",
825 @"or",
826
827 pub fn level(op: Operator) Level {
828 return switch (op) {
829 .assign,
830 .assign_add,
831 .assign_sub,
832 .assign_mul,
833 .assign_div,
834 => .assignment,
835 .add, .sub => .additive,
836 .mul, .div => .multiplicative,
837 .eq => .equality,
838 .lt, .gt, .ge, .le => .relational,
839 .@"and" => .logical_and,
840 .@"or" => .logical_or,
841 .pre_inc,
842 .pre_dec,
843 .bool_not,
844 .negate,
845 => .prefix,
846 };
847 }
848
849 pub fn symbol(op: Operator) []const u8 {
850 return switch (op) {
851 .assign => "=",
852 .assign_add => "+=",
853 .assign_sub => "-=",
854 .assign_mul => "*=",
855 .assign_div => "/=",
856 .add => "+",
857 .sub => "-",
858 .mul => "*",
859 .div => "/",
860 .eq => "===",
861 .lt => "<",
862 .gt => ">",
863 .le => "<=",
864 .ge => ">=",
865
866 .@"and" => "&&",
867 .@"or" => "||",
868
869 .pre_inc => "++",
870 .pre_dec => "++",
871
872 .negate => "-",
873 .bool_not => "!",
874 };
875 }
876};
877
878const LinkString = struct {
879 start: u32,
880 len: u32,
881
882 fn slice(str: LinkString, link: *const Link) []const u8 {
883 return link.string_bytes.items[str.start..][0..str.len];
884 }
885
886 const Index = enum(u32) {
887 _,
888
889 pub fn toString(index: Index, link: *const Link) LinkString {
890 return link.decl_strings.keys()[@intFromEnum(index)];
891 }
892 };
893
894 const Context = struct {
895 link: *const Link,
896
897 pub fn hash(ctx: Context, str: LinkString) u32 {
898 assert(str.start + str.len < ctx.link.string_bytes.items.len);
899 return @truncate(Wyhash.hash(0, str.slice(ctx.link)));
900 }
901
902 pub fn eql(ctx: Context, a: LinkString, b: LinkString, _: usize) bool {
903 assert(a.start + a.len < ctx.link.string_bytes.items.len);
904 assert(b.start + b.len < ctx.link.string_bytes.items.len);
905 return a.start == b.start;
906 }
907 };
908
909 const SliceContext = struct {
910 link: *const Link,
911
912 pub fn hash(_: SliceContext, str: []const u8) u32 {
913 return @truncate(Wyhash.hash(0, str));
914 }
915
916 pub fn eql(ctx: SliceContext, a: []const u8, b: LinkString, _: usize) bool {
917 // since the given string is not in string_bytes we do comparisons
918 return std.mem.eql(u8, a, b.slice(ctx.link));
919 }
920 };
921};
922
923const Link = @This();
924const std = @import("std");
925const assert = std.debug.assert;
926const Wyhash = std.hash.Wyhash;
927const Writer = std.io.AnyWriter;
928
929const core = @import("../mod.zig");
930const Comp = core.Comp;
931const Decl = Comp.Decl;
932
933const Js = core.Js;
934const Tree = Js.Tree;
935const Node = Tree.Node;
936
937const InternPool = core.InternPool;
938const String = InternPool.String;
939
940const tracer = @import("tracer");