Commit b1d8a0a5a6

Andrew Kelley <andrew@ziglang.org>
2021-02-09 06:03:23
zig fmt: asm expressions
1 parent d869133
Changed files (4)
lib/std/zig/ast.zig
@@ -419,13 +419,16 @@ pub const Tree = struct {
                 n = extra.start;
             },
 
+            .AsmOutput, .AsmInput => {
+                assert(token_tags[main_tokens[n] - 1] == .LBracket);
+                return main_tokens[n] - 1;
+            },
+
             .WhileSimple => unreachable, // TODO
             .WhileCont => unreachable, // TODO
             .While => unreachable, // TODO
             .ForSimple => unreachable, // TODO
             .For => unreachable, // TODO
-            .AsmOutput => unreachable, // TODO
-            .AsmInput => unreachable, // TODO
             .ErrorValue => unreachable, // TODO
         };
     }
@@ -515,6 +518,9 @@ pub const Tree = struct {
             .GroupedExpression,
             .StringLiteral,
             .ErrorSetDecl,
+            .AsmSimple,
+            .AsmOutput,
+            .AsmInput,
             => return datas[n].rhs + end_offset,
 
             .AnyType,
@@ -566,6 +572,10 @@ pub const Tree = struct {
                     n = tree.extra_data[members.end - 1]; // last parameter
                 }
             },
+            .Asm => {
+                const extra = tree.extraData(datas[n].rhs, Node.Asm);
+                return extra.rparen + end_offset;
+            },
             .ContainerDeclArgComma,
             .SwitchComma,
             => {
@@ -765,8 +775,6 @@ pub const Tree = struct {
             .TaggedUnionEnumTagComma => unreachable, // TODO
             .If => unreachable, // TODO
             .Continue => unreachable, // TODO
-            .AsmSimple => unreachable, // TODO
-            .Asm => unreachable, // TODO
             .SwitchRange => unreachable, // TODO
             .ArrayType => unreachable, // TODO
             .ArrayTypeSentinel => unreachable, // TODO
@@ -778,8 +786,6 @@ pub const Tree = struct {
             .FnProtoMulti => unreachable, // TODO
             .FnProtoOne => unreachable, // TODO
             .FnProto => unreachable, // TODO
-            .AsmOutput => unreachable, // TODO
-            .AsmInput => unreachable, // TODO
             .ErrorValue => unreachable, // TODO
         };
     }
@@ -790,7 +796,7 @@ pub const Tree = struct {
         return mem.indexOfScalar(u8, source, '\n') == null;
     }
 
-    pub fn globalVarDecl(tree: Tree, node: Node.Index) Full.VarDecl {
+    pub fn globalVarDecl(tree: Tree, node: Node.Index) full.VarDecl {
         assert(tree.nodes.items(.tag)[node] == .GlobalVarDecl);
         const data = tree.nodes.items(.data)[node];
         const extra = tree.extraData(data.lhs, Node.GlobalVarDecl);
@@ -803,7 +809,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn localVarDecl(tree: Tree, node: Node.Index) Full.VarDecl {
+    pub fn localVarDecl(tree: Tree, node: Node.Index) full.VarDecl {
         assert(tree.nodes.items(.tag)[node] == .LocalVarDecl);
         const data = tree.nodes.items(.data)[node];
         const extra = tree.extraData(data.lhs, Node.LocalVarDecl);
@@ -816,7 +822,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn simpleVarDecl(tree: Tree, node: Node.Index) Full.VarDecl {
+    pub fn simpleVarDecl(tree: Tree, node: Node.Index) full.VarDecl {
         assert(tree.nodes.items(.tag)[node] == .SimpleVarDecl);
         const data = tree.nodes.items(.data)[node];
         return tree.fullVarDecl(.{
@@ -828,7 +834,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn alignedVarDecl(tree: Tree, node: Node.Index) Full.VarDecl {
+    pub fn alignedVarDecl(tree: Tree, node: Node.Index) full.VarDecl {
         assert(tree.nodes.items(.tag)[node] == .AlignedVarDecl);
         const data = tree.nodes.items(.data)[node];
         return tree.fullVarDecl(.{
@@ -840,7 +846,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn ifSimple(tree: Tree, node: Node.Index) Full.If {
+    pub fn ifSimple(tree: Tree, node: Node.Index) full.If {
         assert(tree.nodes.items(.tag)[node] == .IfSimple);
         const data = tree.nodes.items(.data)[node];
         return tree.fullIf(.{
@@ -851,7 +857,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn ifFull(tree: Tree, node: Node.Index) Full.If {
+    pub fn ifFull(tree: Tree, node: Node.Index) full.If {
         assert(tree.nodes.items(.tag)[node] == .If);
         const data = tree.nodes.items(.data)[node];
         const extra = tree.extraData(data.rhs, Node.If);
@@ -863,7 +869,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn containerField(tree: Tree, node: Node.Index) Full.ContainerField {
+    pub fn containerField(tree: Tree, node: Node.Index) full.ContainerField {
         assert(tree.nodes.items(.tag)[node] == .ContainerField);
         const data = tree.nodes.items(.data)[node];
         const extra = tree.extraData(data.rhs, Node.ContainerField);
@@ -875,7 +881,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn containerFieldInit(tree: Tree, node: Node.Index) Full.ContainerField {
+    pub fn containerFieldInit(tree: Tree, node: Node.Index) full.ContainerField {
         assert(tree.nodes.items(.tag)[node] == .ContainerFieldInit);
         const data = tree.nodes.items(.data)[node];
         return tree.fullContainerField(.{
@@ -886,7 +892,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn containerFieldAlign(tree: Tree, node: Node.Index) Full.ContainerField {
+    pub fn containerFieldAlign(tree: Tree, node: Node.Index) full.ContainerField {
         assert(tree.nodes.items(.tag)[node] == .ContainerFieldAlign);
         const data = tree.nodes.items(.data)[node];
         return tree.fullContainerField(.{
@@ -897,7 +903,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn fnProtoSimple(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) Full.FnProto {
+    pub fn fnProtoSimple(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) full.FnProto {
         assert(tree.nodes.items(.tag)[node] == .FnProtoSimple);
         const data = tree.nodes.items(.data)[node];
         buffer[0] = data.lhs;
@@ -912,7 +918,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn fnProtoMulti(tree: Tree, node: Node.Index) Full.FnProto {
+    pub fn fnProtoMulti(tree: Tree, node: Node.Index) full.FnProto {
         assert(tree.nodes.items(.tag)[node] == .FnProtoMulti);
         const data = tree.nodes.items(.data)[node];
         const params_range = tree.extraData(data.lhs, Node.SubRange);
@@ -927,7 +933,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn fnProtoOne(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) Full.FnProto {
+    pub fn fnProtoOne(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) full.FnProto {
         assert(tree.nodes.items(.tag)[node] == .FnProtoOne);
         const data = tree.nodes.items(.data)[node];
         const extra = tree.extraData(data.lhs, Node.FnProtoOne);
@@ -943,7 +949,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn fnProto(tree: Tree, node: Node.Index) Full.FnProto {
+    pub fn fnProto(tree: Tree, node: Node.Index) full.FnProto {
         assert(tree.nodes.items(.tag)[node] == .FnProto);
         const data = tree.nodes.items(.data)[node];
         const extra = tree.extraData(data.lhs, Node.FnProto);
@@ -958,7 +964,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn structInitOne(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) Full.StructInit {
+    pub fn structInitOne(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) full.StructInit {
         assert(tree.nodes.items(.tag)[node] == .StructInitOne);
         const data = tree.nodes.items(.data)[node];
         buffer[0] = data.rhs;
@@ -970,7 +976,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn structInitDotTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) Full.StructInit {
+    pub fn structInitDotTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) full.StructInit {
         assert(tree.nodes.items(.tag)[node] == .StructInitDotTwo or
             tree.nodes.items(.tag)[node] == .StructInitDotTwoComma);
         const data = tree.nodes.items(.data)[node];
@@ -988,7 +994,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn structInitDot(tree: Tree, node: Node.Index) Full.StructInit {
+    pub fn structInitDot(tree: Tree, node: Node.Index) full.StructInit {
         assert(tree.nodes.items(.tag)[node] == .StructInitDot);
         const data = tree.nodes.items(.data)[node];
         return tree.fullStructInit(.{
@@ -998,7 +1004,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn structInit(tree: Tree, node: Node.Index) Full.StructInit {
+    pub fn structInit(tree: Tree, node: Node.Index) full.StructInit {
         assert(tree.nodes.items(.tag)[node] == .StructInit);
         const data = tree.nodes.items(.data)[node];
         const fields_range = tree.extraData(data.rhs, Node.SubRange);
@@ -1009,7 +1015,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn arrayInitOne(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) Full.ArrayInit {
+    pub fn arrayInitOne(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) full.ArrayInit {
         assert(tree.nodes.items(.tag)[node] == .ArrayInitOne);
         const data = tree.nodes.items(.data)[node];
         buffer[0] = data.rhs;
@@ -1023,7 +1029,7 @@ pub const Tree = struct {
         };
     }
 
-    pub fn arrayInitDotTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) Full.ArrayInit {
+    pub fn arrayInitDotTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) full.ArrayInit {
         assert(tree.nodes.items(.tag)[node] == .ArrayInitDotTwo or
             tree.nodes.items(.tag)[node] == .ArrayInitDotTwoComma);
         const data = tree.nodes.items(.data)[node];
@@ -1043,7 +1049,7 @@ pub const Tree = struct {
         };
     }
 
-    pub fn arrayInitDot(tree: Tree, node: Node.Index) Full.ArrayInit {
+    pub fn arrayInitDot(tree: Tree, node: Node.Index) full.ArrayInit {
         assert(tree.nodes.items(.tag)[node] == .ArrayInitDot);
         const data = tree.nodes.items(.data)[node];
         return .{
@@ -1055,7 +1061,7 @@ pub const Tree = struct {
         };
     }
 
-    pub fn arrayInit(tree: Tree, node: Node.Index) Full.ArrayInit {
+    pub fn arrayInit(tree: Tree, node: Node.Index) full.ArrayInit {
         assert(tree.nodes.items(.tag)[node] == .ArrayInit);
         const data = tree.nodes.items(.data)[node];
         const elem_range = tree.extraData(data.rhs, Node.SubRange);
@@ -1068,7 +1074,7 @@ pub const Tree = struct {
         };
     }
 
-    pub fn arrayType(tree: Tree, node: Node.Index) Full.ArrayType {
+    pub fn arrayType(tree: Tree, node: Node.Index) full.ArrayType {
         assert(tree.nodes.items(.tag)[node] == .ArrayType);
         const data = tree.nodes.items(.data)[node];
         return .{
@@ -1081,7 +1087,7 @@ pub const Tree = struct {
         };
     }
 
-    pub fn arrayTypeSentinel(tree: Tree, node: Node.Index) Full.ArrayType {
+    pub fn arrayTypeSentinel(tree: Tree, node: Node.Index) full.ArrayType {
         assert(tree.nodes.items(.tag)[node] == .ArrayTypeSentinel);
         const data = tree.nodes.items(.data)[node];
         const extra = tree.extraData(data.rhs, Node.ArrayTypeSentinel);
@@ -1095,7 +1101,7 @@ pub const Tree = struct {
         };
     }
 
-    pub fn ptrTypeAligned(tree: Tree, node: Node.Index) Full.PtrType {
+    pub fn ptrTypeAligned(tree: Tree, node: Node.Index) full.PtrType {
         assert(tree.nodes.items(.tag)[node] == .PtrTypeAligned);
         const data = tree.nodes.items(.data)[node];
         return tree.fullPtrType(.{
@@ -1108,7 +1114,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn ptrTypeSentinel(tree: Tree, node: Node.Index) Full.PtrType {
+    pub fn ptrTypeSentinel(tree: Tree, node: Node.Index) full.PtrType {
         assert(tree.nodes.items(.tag)[node] == .PtrTypeSentinel);
         const data = tree.nodes.items(.data)[node];
         return tree.fullPtrType(.{
@@ -1121,7 +1127,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn ptrType(tree: Tree, node: Node.Index) Full.PtrType {
+    pub fn ptrType(tree: Tree, node: Node.Index) full.PtrType {
         assert(tree.nodes.items(.tag)[node] == .PtrType);
         const data = tree.nodes.items(.data)[node];
         const extra = tree.extraData(data.lhs, Node.PtrType);
@@ -1135,7 +1141,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn ptrTypeBitRange(tree: Tree, node: Node.Index) Full.PtrType {
+    pub fn ptrTypeBitRange(tree: Tree, node: Node.Index) full.PtrType {
         assert(tree.nodes.items(.tag)[node] == .PtrTypeBitRange);
         const data = tree.nodes.items(.data)[node];
         const extra = tree.extraData(data.lhs, Node.PtrTypeBitRange);
@@ -1149,7 +1155,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn sliceOpen(tree: Tree, node: Node.Index) Full.Slice {
+    pub fn sliceOpen(tree: Tree, node: Node.Index) full.Slice {
         assert(tree.nodes.items(.tag)[node] == .SliceOpen);
         const data = tree.nodes.items(.data)[node];
         return .{
@@ -1163,7 +1169,7 @@ pub const Tree = struct {
         };
     }
 
-    pub fn slice(tree: Tree, node: Node.Index) Full.Slice {
+    pub fn slice(tree: Tree, node: Node.Index) full.Slice {
         assert(tree.nodes.items(.tag)[node] == .Slice);
         const data = tree.nodes.items(.data)[node];
         const extra = tree.extraData(data.rhs, Node.Slice);
@@ -1178,7 +1184,7 @@ pub const Tree = struct {
         };
     }
 
-    pub fn sliceSentinel(tree: Tree, node: Node.Index) Full.Slice {
+    pub fn sliceSentinel(tree: Tree, node: Node.Index) full.Slice {
         assert(tree.nodes.items(.tag)[node] == .SliceSentinel);
         const data = tree.nodes.items(.data)[node];
         const extra = tree.extraData(data.rhs, Node.SliceSentinel);
@@ -1193,7 +1199,7 @@ pub const Tree = struct {
         };
     }
 
-    pub fn containerDeclTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) Full.ContainerDecl {
+    pub fn containerDeclTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) full.ContainerDecl {
         assert(tree.nodes.items(.tag)[node] == .ContainerDeclTwo or
             tree.nodes.items(.tag)[node] == .ContainerDeclTwoComma);
         const data = tree.nodes.items(.data)[node];
@@ -1212,7 +1218,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn containerDecl(tree: Tree, node: Node.Index) Full.ContainerDecl {
+    pub fn containerDecl(tree: Tree, node: Node.Index) full.ContainerDecl {
         assert(tree.nodes.items(.tag)[node] == .ContainerDecl or
             tree.nodes.items(.tag)[node] == .ContainerDeclComma);
         const data = tree.nodes.items(.data)[node];
@@ -1224,7 +1230,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn containerDeclArg(tree: Tree, node: Node.Index) Full.ContainerDecl {
+    pub fn containerDeclArg(tree: Tree, node: Node.Index) full.ContainerDecl {
         assert(tree.nodes.items(.tag)[node] == .ContainerDeclArg or
             tree.nodes.items(.tag)[node] == .ContainerDeclArgComma);
         const data = tree.nodes.items(.data)[node];
@@ -1237,7 +1243,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn taggedUnionTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) Full.ContainerDecl {
+    pub fn taggedUnionTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) full.ContainerDecl {
         assert(tree.nodes.items(.tag)[node] == .TaggedUnionTwo or
             tree.nodes.items(.tag)[node] == .TaggedUnionTwoComma);
         const data = tree.nodes.items(.data)[node];
@@ -1257,7 +1263,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn taggedUnion(tree: Tree, node: Node.Index) Full.ContainerDecl {
+    pub fn taggedUnion(tree: Tree, node: Node.Index) full.ContainerDecl {
         assert(tree.nodes.items(.tag)[node] == .TaggedUnion or
             tree.nodes.items(.tag)[node] == .TaggedUnionComma);
         const data = tree.nodes.items(.data)[node];
@@ -1270,7 +1276,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn taggedUnionEnumTag(tree: Tree, node: Node.Index) Full.ContainerDecl {
+    pub fn taggedUnionEnumTag(tree: Tree, node: Node.Index) full.ContainerDecl {
         assert(tree.nodes.items(.tag)[node] == .TaggedUnionEnumTag or
             tree.nodes.items(.tag)[node] == .TaggedUnionEnumTagComma);
         const data = tree.nodes.items(.data)[node];
@@ -1284,7 +1290,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn switchCaseOne(tree: Tree, node: Node.Index) Full.SwitchCase {
+    pub fn switchCaseOne(tree: Tree, node: Node.Index) full.SwitchCase {
         const data = &tree.nodes.items(.data)[node];
         return tree.fullSwitchCase(.{
             .values = if (data.lhs == 0) &.{} else @ptrCast([*]Node.Index, &data.lhs)[0..1],
@@ -1293,7 +1299,7 @@ pub const Tree = struct {
         });
     }
 
-    pub fn switchCase(tree: Tree, node: Node.Index) Full.SwitchCase {
+    pub fn switchCase(tree: Tree, node: Node.Index) full.SwitchCase {
         const data = tree.nodes.items(.data)[node];
         const extra = tree.extraData(data.lhs, Node.SubRange);
         return tree.fullSwitchCase(.{
@@ -1303,9 +1309,30 @@ pub const Tree = struct {
         });
     }
 
-    fn fullVarDecl(tree: Tree, info: Full.VarDecl.Ast) Full.VarDecl {
+    pub fn asmSimple(tree: Tree, node: Node.Index) full.Asm {
+        const data = tree.nodes.items(.data)[node];
+        return tree.fullAsm(.{
+            .asm_token = tree.nodes.items(.main_token)[node],
+            .template = data.lhs,
+            .items = &.{},
+            .rparen = data.rhs,
+        });
+    }
+
+    pub fn asmFull(tree: Tree, node: Node.Index) full.Asm {
+        const data = tree.nodes.items(.data)[node];
+        const extra = tree.extraData(data.rhs, Node.Asm);
+        return tree.fullAsm(.{
+            .asm_token = tree.nodes.items(.main_token)[node],
+            .template = data.lhs,
+            .items = tree.extra_data[extra.items_start..extra.items_end],
+            .rparen = extra.rparen,
+        });
+    }
+
+    fn fullVarDecl(tree: Tree, info: full.VarDecl.Ast) full.VarDecl {
         const token_tags = tree.tokens.items(.tag);
-        var result: Full.VarDecl = .{
+        var result: full.VarDecl = .{
             .ast = info,
             .visib_token = null,
             .extern_export_token = null,
@@ -1328,9 +1355,9 @@ pub const Tree = struct {
         return result;
     }
 
-    fn fullIf(tree: Tree, info: Full.If.Ast) Full.If {
+    fn fullIf(tree: Tree, info: full.If.Ast) full.If {
         const token_tags = tree.tokens.items(.tag);
-        var result: Full.If = .{
+        var result: full.If = .{
             .ast = info,
             .payload_token = null,
             .error_token = null,
@@ -1353,9 +1380,9 @@ pub const Tree = struct {
         return result;
     }
 
-    fn fullContainerField(tree: Tree, info: Full.ContainerField.Ast) Full.ContainerField {
+    fn fullContainerField(tree: Tree, info: full.ContainerField.Ast) full.ContainerField {
         const token_tags = tree.tokens.items(.tag);
-        var result: Full.ContainerField = .{
+        var result: full.ContainerField = .{
             .ast = info,
             .comptime_token = null,
         };
@@ -1367,27 +1394,27 @@ pub const Tree = struct {
         return result;
     }
 
-    fn fullFnProto(tree: Tree, info: Full.FnProto.Ast) Full.FnProto {
+    fn fullFnProto(tree: Tree, info: full.FnProto.Ast) full.FnProto {
         const token_tags = tree.tokens.items(.tag);
-        var result: Full.FnProto = .{
+        var result: full.FnProto = .{
             .ast = info,
         };
         return result;
     }
 
-    fn fullStructInit(tree: Tree, info: Full.StructInit.Ast) Full.StructInit {
+    fn fullStructInit(tree: Tree, info: full.StructInit.Ast) full.StructInit {
         const token_tags = tree.tokens.items(.tag);
-        var result: Full.StructInit = .{
+        var result: full.StructInit = .{
             .ast = info,
         };
         return result;
     }
 
-    fn fullPtrType(tree: Tree, info: Full.PtrType.Ast) Full.PtrType {
+    fn fullPtrType(tree: Tree, info: full.PtrType.Ast) full.PtrType {
         const token_tags = tree.tokens.items(.tag);
         // TODO: looks like stage1 isn't quite smart enough to handle enum
         // literals in some places here
-        const Kind = Full.PtrType.Kind;
+        const Kind = full.PtrType.Kind;
         const kind: Kind = switch (token_tags[info.main_token]) {
             .Asterisk => switch (token_tags[info.main_token + 1]) {
                 .RBracket => .many,
@@ -1402,7 +1429,7 @@ pub const Tree = struct {
             },
             else => unreachable,
         };
-        var result: Full.PtrType = .{
+        var result: full.PtrType = .{
             .kind = kind,
             .allowzero_token = null,
             .const_token = null,
@@ -1441,9 +1468,9 @@ pub const Tree = struct {
         return result;
     }
 
-    fn fullContainerDecl(tree: Tree, info: Full.ContainerDecl.Ast) Full.ContainerDecl {
+    fn fullContainerDecl(tree: Tree, info: full.ContainerDecl.Ast) full.ContainerDecl {
         const token_tags = tree.tokens.items(.tag);
-        var result: Full.ContainerDecl = .{
+        var result: full.ContainerDecl = .{
             .ast = info,
             .layout_token = null,
         };
@@ -1454,9 +1481,9 @@ pub const Tree = struct {
         return result;
     }
 
-    fn fullSwitchCase(tree: Tree, info: Full.SwitchCase.Ast) Full.SwitchCase {
+    fn fullSwitchCase(tree: Tree, info: full.SwitchCase.Ast) full.SwitchCase {
         const token_tags = tree.tokens.items(.tag);
-        var result: Full.SwitchCase = .{
+        var result: full.SwitchCase = .{
             .ast = info,
             .payload_token = null,
         };
@@ -1465,10 +1492,67 @@ pub const Tree = struct {
         }
         return result;
     }
+
+    fn fullAsm(tree: Tree, info: full.Asm.Ast) full.Asm {
+        const token_tags = tree.tokens.items(.tag);
+        const node_tags = tree.nodes.items(.tag);
+        var result: full.Asm = .{
+            .ast = info,
+            .volatile_token = null,
+            .inputs = &.{},
+            .outputs = &.{},
+            .first_clobber = null,
+        };
+        if (token_tags[info.asm_token + 1] == .Keyword_volatile) {
+            result.volatile_token = info.asm_token + 1;
+        }
+        const outputs_end: usize = for (info.items) |item, i| {
+            switch (node_tags[item]) {
+                .AsmOutput => continue,
+                else => break i,
+            }
+        } else info.items.len;
+
+        result.outputs = info.items[0..outputs_end];
+        result.inputs = info.items[outputs_end..];
+
+        if (info.items.len == 0) {
+            // asm ("foo" ::: "a", "b");
+            const template_token = tree.lastToken(info.template);
+            if (token_tags[template_token + 1] == .Colon and
+                token_tags[template_token + 2] == .Colon and
+                token_tags[template_token + 3] == .Colon and
+                token_tags[template_token + 4] == .StringLiteral)
+            {
+                result.first_clobber = template_token + 4;
+            }
+        } else if (result.inputs.len != 0) {
+            // asm ("foo" :: [_] "" (y) : "a", "b");
+            const last_input = result.inputs[result.inputs.len - 1];
+            const rparen = tree.lastToken(last_input);
+            if (token_tags[rparen + 1] == .Colon and
+                token_tags[rparen + 2] == .StringLiteral)
+            {
+                result.first_clobber = rparen + 2;
+            }
+        } else {
+            // asm ("foo" : [_] "" (x) :: "a", "b");
+            const last_output = result.outputs[result.outputs.len - 1];
+            const rparen = tree.lastToken(last_output);
+            if (token_tags[rparen + 1] == .Colon and
+                token_tags[rparen + 2] == .Colon and
+                token_tags[rparen + 3] == .StringLiteral)
+            {
+                result.first_clobber = rparen + 3;
+            }
+        }
+
+        return result;
+    }
 };
 
 /// Fully assembled AST node information.
-pub const Full = struct {
+pub const full = struct {
     pub const VarDecl = struct {
         visib_token: ?TokenIndex,
         extern_export_token: ?TokenIndex,
@@ -1624,6 +1708,21 @@ pub const Full = struct {
             target_expr: Node.Index,
         };
     };
+
+    pub const Asm = struct {
+        ast: Ast,
+        volatile_token: ?TokenIndex,
+        first_clobber: ?TokenIndex,
+        outputs: []const Node.Index,
+        inputs: []const Node.Index,
+
+        pub const Ast = struct {
+            asm_token: TokenIndex,
+            template: Node.Index,
+            items: []const Node.Index,
+            rparen: TokenIndex,
+        };
+    };
 };
 
 pub const Error = union(enum) {
@@ -2234,15 +2333,15 @@ pub const Node = struct {
         Block,
         /// Same as BlockTwo but there is known to be a semicolon before the rbrace.
         BlockSemicolon,
-        /// `asm(lhs)`. rhs unused.
+        /// `asm(lhs)`. rhs is the token index of the rparen.
         AsmSimple,
-        /// `asm(lhs, a)`. `sub_range_list[rhs]`.
+        /// `asm(lhs, a)`. `Asm[rhs]`.
         Asm,
-        /// `[a] "b" (c)`. lhs is string literal token index, rhs is 0.
-        /// `[a] "b" (-> rhs)`. lhs is the string literal token index, rhs is type expr.
+        /// `[a] "b" (c)`. lhs is 0, rhs is token index of the rparen.
+        /// `[a] "b" (-> lhs)`. rhs is token index of the rparen.
         /// main_token is `a`.
         AsmOutput,
-        /// `[a] "b" (rhs)`. lhs is string literal token index.
+        /// `[a] "b" (lhs)`. rhs is token index of the rparen.
         /// main_token is `a`.
         AsmInput,
         /// `error.a`. lhs is token index of `.`. rhs is token index of `a`.
@@ -2355,4 +2454,11 @@ pub const Node = struct {
         /// Populated if callconv(A) is present.
         callconv_expr: Index,
     };
+
+    pub const Asm = struct {
+        items_start: Index,
+        items_end: Index,
+        /// Needed to make lastToken() work.
+        rparen: TokenIndex,
+    };
 };
lib/std/zig/parse.zig
@@ -472,7 +472,7 @@ const Parser = struct {
 
     /// TestDecl <- KEYWORD_test STRINGLITERALSINGLE? Block
     fn expectTestDecl(p: *Parser) !Node.Index {
-        const test_token = try p.expectToken(.Keyword_test);
+        const test_token = p.assertToken(.Keyword_test);
         const name_token = p.eatToken(.StringLiteral);
         const block_node = try p.parseBlock();
         if (block_node == 0) return p.fail(.{ .ExpectedLBrace = .{ .token = p.tok_i } });
@@ -739,7 +739,7 @@ const Parser = struct {
     /// ContainerField <- KEYWORD_comptime? IDENTIFIER (COLON TypeExpr ByteAlign?)? (EQUAL Expr)?
     fn expectContainerField(p: *Parser) !Node.Index {
         const comptime_token = p.eatToken(.Keyword_comptime);
-        const name_token = try p.expectToken(.Identifier);
+        const name_token = p.assertToken(.Identifier);
 
         var align_expr: Node.Index = 0;
         var type_expr: Node.Index = 0;
@@ -1846,7 +1846,7 @@ const Parser = struct {
     ///      / CurlySuffixExpr
     fn parsePrimaryExpr(p: *Parser) !Node.Index {
         switch (p.token_tags[p.tok_i]) {
-            .Keyword_asm => return p.parseAsmExpr(),
+            .Keyword_asm => return p.expectAsmExpr(),
             .Keyword_if => return p.parseIfExpr(),
             .Keyword_break => {
                 p.tok_i += 1;
@@ -2910,19 +2910,19 @@ const Parser = struct {
     /// StringList <- (STRINGLITERAL COMMA)* STRINGLITERAL?
     /// AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem?
     /// AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?
-    fn parseAsmExpr(p: *Parser) !Node.Index {
+    fn expectAsmExpr(p: *Parser) !Node.Index {
         const asm_token = p.assertToken(.Keyword_asm);
         _ = p.eatToken(.Keyword_volatile);
         _ = try p.expectToken(.LParen);
         const template = try p.expectExpr();
 
-        if (p.eatToken(.RParen)) |_| {
+        if (p.eatToken(.RParen)) |rparen| {
             return p.addNode(.{
                 .tag = .AsmSimple,
                 .main_token = asm_token,
                 .data = .{
                     .lhs = template,
-                    .rhs = undefined,
+                    .rhs = rparen,
                 },
             });
         }
@@ -2981,16 +2981,17 @@ const Parser = struct {
                 }
             }
         }
-        _ = try p.expectToken(.RParen);
+        const rparen = try p.expectToken(.RParen);
         const span = try p.listToSpan(list.items);
         return p.addNode(.{
             .tag = .Asm,
             .main_token = asm_token,
             .data = .{
                 .lhs = template,
-                .rhs = try p.addExtra(Node.SubRange{
-                    .start = span.start,
-                    .end = span.end,
+                .rhs = try p.addExtra(Node.Asm{
+                    .items_start = span.start,
+                    .items_end = span.end,
+                    .rparen = rparen,
                 }),
             },
         });
@@ -3001,16 +3002,23 @@ const Parser = struct {
         _ = p.eatToken(.LBracket) orelse return null_node;
         const identifier = try p.expectToken(.Identifier);
         _ = try p.expectToken(.RBracket);
-        const constraint = try p.expectToken(.StringLiteral);
+        _ = try p.expectToken(.StringLiteral);
         _ = try p.expectToken(.LParen);
-        const rhs: Node.Index = if (p.eatToken(.Arrow)) |_| try p.expectTypeExpr() else null_node;
-        _ = try p.expectToken(.RParen);
+        const type_expr: Node.Index = blk: {
+            if (p.eatToken(.Arrow)) |_| {
+                break :blk try p.expectTypeExpr();
+            } else {
+                _ = try p.expectToken(.Identifier);
+                break :blk null_node;
+            }
+        };
+        const rparen = try p.expectToken(.RParen);
         return p.addNode(.{
             .tag = .AsmOutput,
             .main_token = identifier,
             .data = .{
-                .lhs = constraint,
-                .rhs = rhs,
+                .lhs = type_expr,
+                .rhs = rparen,
             },
         });
     }
@@ -3020,16 +3028,16 @@ const Parser = struct {
         _ = p.eatToken(.LBracket) orelse return null_node;
         const identifier = try p.expectToken(.Identifier);
         _ = try p.expectToken(.RBracket);
-        const constraint = try p.expectToken(.StringLiteral);
+        _ = try p.expectToken(.StringLiteral);
         _ = try p.expectToken(.LParen);
         const expr = try p.expectExpr();
-        _ = try p.expectToken(.RParen);
+        const rparen = try p.expectToken(.RParen);
         return p.addNode(.{
             .tag = .AsmInput,
             .main_token = identifier,
             .data = .{
-                .lhs = constraint,
-                .rhs = expr,
+                .lhs = expr,
+                .rhs = rparen,
             },
         });
     }
lib/std/zig/parser_test.zig
@@ -313,30 +313,30 @@ test "zig fmt: builtin call with trailing comma" {
     );
 }
 
-//test "zig fmt: asm expression with comptime content" {
-//    try testCanonical(
-//        \\comptime {
-//        \\    asm ("foo" ++ "bar");
-//        \\}
-//        \\pub fn main() void {
-//        \\    asm volatile ("foo" ++ "bar");
-//        \\    asm volatile ("foo" ++ "bar"
-//        \\        : [_] "" (x)
-//        \\    );
-//        \\    asm volatile ("foo" ++ "bar"
-//        \\        : [_] "" (x)
-//        \\        : [_] "" (y)
-//        \\    );
-//        \\    asm volatile ("foo" ++ "bar"
-//        \\        : [_] "" (x)
-//        \\        : [_] "" (y)
-//        \\        : "h", "e", "l", "l", "o"
-//        \\    );
-//        \\}
-//        \\
-//    );
-//}
-//
+test "zig fmt: asm expression with comptime content" {
+    try testCanonical(
+        \\comptime {
+        \\    asm ("foo" ++ "bar");
+        \\}
+        \\pub fn main() void {
+        \\    asm volatile ("foo" ++ "bar");
+        \\    asm volatile ("foo" ++ "bar"
+        \\        : [_] "" (x)
+        \\    );
+        \\    asm volatile ("foo" ++ "bar"
+        \\        : [_] "" (x)
+        \\        : [_] "" (y)
+        \\    );
+        \\    asm volatile ("foo" ++ "bar"
+        \\        : [_] "" (x)
+        \\        : [_] "" (y)
+        \\        : "h", "e", "l", "l", "o"
+        \\    );
+        \\}
+        \\
+    );
+}
+
 //test "zig fmt: anytype struct field" {
 //    try testCanonical(
 //        \\pub const Pointer = struct {
lib/std/zig/render.zig
@@ -816,118 +816,8 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         .IfSimple => return renderIf(ais, tree, tree.ifSimple(node), space),
         .If => return renderIf(ais, tree, tree.ifFull(node), space),
 
-        .Asm => unreachable, // TODO
-        .AsmSimple => unreachable, // TODO
-        .AsmOutput => unreachable, // TODO
-        .AsmInput => unreachable, // TODO
-        //.Asm => {
-        //    const asm_node = @fieldParentPtr(ast.Node.Asm, "base", base);
-
-        //    try renderToken(ais, tree, asm_node.asm_token, Space.Space); // asm
-
-        //    if (asm_node.volatile_token) |volatile_token| {
-        //        try renderToken(ais, tree, volatile_token, Space.Space); // volatile
-        //        try renderToken(ais, tree, tree.nextToken(volatile_token), Space.None); // (
-        //    } else {
-        //        try renderToken(ais, tree, tree.nextToken(asm_node.asm_token), Space.None); // (
-        //    }
-
-        //    asmblk: {
-        //        ais.pushIndent();
-        //        defer ais.popIndent();
-
-        //        if (asm_node.outputs.len == 0 and asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) {
-        //            try renderExpression(ais, tree, asm_node.template, Space.None);
-        //            break :asmblk;
-        //        }
-
-        //        try renderExpression(ais, tree, asm_node.template, Space.Newline);
-
-        //        ais.setIndentDelta(asm_indent_delta);
-        //        defer ais.setIndentDelta(indent_delta);
-
-        //        const colon1 = tree.nextToken(asm_node.template.lastToken());
-
-        //        const colon2 = if (asm_node.outputs.len == 0) blk: {
-        //            try renderToken(ais, tree, colon1, Space.Newline); // :
-
-        //            break :blk tree.nextToken(colon1);
-        //        } else blk: {
-        //            try renderToken(ais, tree, colon1, Space.Space); // :
-
-        //            ais.pushIndent();
-        //            defer ais.popIndent();
-
-        //            for (asm_node.outputs) |*asm_output, i| {
-        //                if (i + 1 < asm_node.outputs.len) {
-        //                    const next_asm_output = asm_node.outputs[i + 1];
-        //                    try renderAsmOutput(allocator, ais, tree, asm_output, Space.None);
-
-        //                    const comma = tree.prevToken(next_asm_output.firstToken());
-        //                    try renderToken(ais, tree, comma, Space.Newline); // ,
-        //                    try renderExtraNewlineToken(ais, tree, next_asm_output.firstToken());
-        //                } else if (asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) {
-        //                    try renderAsmOutput(allocator, ais, tree, asm_output, Space.Newline);
-        //                    break :asmblk;
-        //                } else {
-        //                    try renderAsmOutput(allocator, ais, tree, asm_output, Space.Newline);
-        //                    const comma_or_colon = tree.nextToken(asm_output.lastToken());
-        //                    break :blk switch (tree.token_tags[comma_or_colon]) {
-        //                        .Comma => tree.nextToken(comma_or_colon),
-        //                        else => comma_or_colon,
-        //                    };
-        //                }
-        //            }
-        //            unreachable;
-        //        };
-
-        //        const colon3 = if (asm_node.inputs.len == 0) blk: {
-        //            try renderToken(ais, tree, colon2, Space.Newline); // :
-        //            break :blk tree.nextToken(colon2);
-        //        } else blk: {
-        //            try renderToken(ais, tree, colon2, Space.Space); // :
-        //            ais.pushIndent();
-        //            defer ais.popIndent();
-        //            for (asm_node.inputs) |*asm_input, i| {
-        //                if (i + 1 < asm_node.inputs.len) {
-        //                    const next_asm_input = &asm_node.inputs[i + 1];
-        //                    try renderAsmInput(allocator, ais, tree, asm_input, Space.None);
-
-        //                    const comma = tree.prevToken(next_asm_input.firstToken());
-        //                    try renderToken(ais, tree, comma, Space.Newline); // ,
-        //                    try renderExtraNewlineToken(ais, tree, next_asm_input.firstToken());
-        //                } else if (asm_node.clobbers.len == 0) {
-        //                    try renderAsmInput(allocator, ais, tree, asm_input, Space.Newline);
-        //                    break :asmblk;
-        //                } else {
-        //                    try renderAsmInput(allocator, ais, tree, asm_input, Space.Newline);
-        //                    const comma_or_colon = tree.nextToken(asm_input.lastToken());
-        //                    break :blk switch (tree.token_tags[comma_or_colon]) {
-        //                        .Comma => tree.nextToken(comma_or_colon),
-        //                        else => comma_or_colon,
-        //                    };
-        //                }
-        //            }
-        //            unreachable;
-        //        };
-
-        //        try renderToken(ais, tree, colon3, Space.Space); // :
-        //        ais.pushIndent();
-        //        defer ais.popIndent();
-        //        for (asm_node.clobbers) |clobber_node, i| {
-        //            if (i + 1 >= asm_node.clobbers.len) {
-        //                try renderExpression(ais, tree, clobber_node, Space.Newline);
-        //                break :asmblk;
-        //            } else {
-        //                try renderExpression(ais, tree, clobber_node, Space.None);
-        //                const comma = tree.nextToken(clobber_node.lastToken());
-        //                try renderToken(ais, tree, comma, Space.Space); // ,
-        //            }
-        //        }
-        //    }
-
-        //    return renderToken(ais, tree, asm_node.rparen, space);
-        //},
+        .AsmSimple => return renderAsm(ais, tree, tree.asmSimple(node), space),
+        .Asm => return renderAsm(ais, tree, tree.asmFull(node), space),
 
         .EnumLiteral => {
             try renderToken(ais, tree, main_tokens[node] - 1, .None); // .
@@ -945,6 +835,8 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
         .AlignedVarDecl => unreachable,
         .UsingNamespace => unreachable,
         .TestDecl => unreachable,
+        .AsmOutput => unreachable,
+        .AsmInput => unreachable,
     }
 }
 
@@ -952,7 +844,7 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
 fn renderArrayType(
     ais: *Ais,
     tree: ast.Tree,
-    array_type: ast.Full.ArrayType,
+    array_type: ast.full.ArrayType,
     space: Space,
 ) Error!void {
     try renderToken(ais, tree, array_type.ast.lbracket, .None); // lbracket
@@ -968,7 +860,7 @@ fn renderArrayType(
 fn renderPtrType(
     ais: *Ais,
     tree: ast.Tree,
-    ptr_type: ast.Full.PtrType,
+    ptr_type: ast.full.PtrType,
     space: Space,
 ) Error!void {
     switch (ptr_type.kind) {
@@ -1040,7 +932,7 @@ fn renderPtrType(
 fn renderSlice(
     ais: *Ais,
     tree: ast.Tree,
-    slice: ast.Full.Slice,
+    slice: ast.full.Slice,
     space: Space,
 ) Error!void {
     const node_tags = tree.nodes.items(.tag);
@@ -1072,48 +964,56 @@ fn renderSlice(
 }
 
 fn renderAsmOutput(
-    allocator: *mem.Allocator,
     ais: *Ais,
     tree: ast.Tree,
-    asm_output: *const ast.Node.Asm.Output,
+    asm_output: ast.Node.Index,
     space: Space,
 ) Error!void {
-    try ais.writer().writeAll("[");
-    try renderExpression(ais, tree, asm_output.symbolic_name, Space.None);
-    try ais.writer().writeAll("] ");
-    try renderExpression(ais, tree, asm_output.constraint, Space.None);
-    try ais.writer().writeAll(" (");
-
-    switch (asm_output.kind) {
-        .Variable => |variable_name| {
-            try renderExpression(ais, tree, &variable_name.base, Space.None);
-        },
-        .Return => |return_type| {
-            try ais.writer().writeAll("-> ");
-            try renderExpression(ais, tree, return_type, Space.None);
-        },
+    const token_tags = tree.tokens.items(.tag);
+    const node_tags = tree.nodes.items(.tag);
+    const main_tokens = tree.nodes.items(.main_token);
+    const datas = tree.nodes.items(.data);
+    assert(node_tags[asm_output] == .AsmOutput);
+    const symbolic_name = main_tokens[asm_output];
+
+    try renderToken(ais, tree, symbolic_name - 1, .None); // lbracket
+    try renderToken(ais, tree, symbolic_name, .None); // ident
+    try renderToken(ais, tree, symbolic_name + 1, .Space); // rbracket
+    try renderToken(ais, tree, symbolic_name + 2, .Space); // "constraint"
+    try renderToken(ais, tree, symbolic_name + 3, .None); // lparen
+
+    if (token_tags[symbolic_name + 4] == .Arrow) {
+        try renderToken(ais, tree, symbolic_name + 4, .Space); // ->
+        try renderExpression(ais, tree, datas[asm_output].lhs, Space.None);
+        return renderToken(ais, tree, datas[asm_output].rhs, space); // rparen
+    } else {
+        try renderToken(ais, tree, symbolic_name + 4, .None); // ident
+        return renderToken(ais, tree, symbolic_name + 5, space); // rparen
     }
-
-    return renderToken(ais, tree, asm_output.lastToken(), space); // )
 }
 
 fn renderAsmInput(
-    allocator: *mem.Allocator,
     ais: *Ais,
     tree: ast.Tree,
-    asm_input: *const ast.Node.Asm.Input,
+    asm_input: ast.Node.Index,
     space: Space,
 ) Error!void {
-    try ais.writer().writeAll("[");
-    try renderExpression(ais, tree, asm_input.symbolic_name, Space.None);
-    try ais.writer().writeAll("] ");
-    try renderExpression(ais, tree, asm_input.constraint, Space.None);
-    try ais.writer().writeAll(" (");
-    try renderExpression(ais, tree, asm_input.expr, Space.None);
-    return renderToken(ais, tree, asm_input.lastToken(), space); // )
+    const node_tags = tree.nodes.items(.tag);
+    const main_tokens = tree.nodes.items(.main_token);
+    const datas = tree.nodes.items(.data);
+    assert(node_tags[asm_input] == .AsmInput);
+    const symbolic_name = main_tokens[asm_input];
+
+    try renderToken(ais, tree, symbolic_name - 1, .None); // lbracket
+    try renderToken(ais, tree, symbolic_name, .None); // ident
+    try renderToken(ais, tree, symbolic_name + 1, .Space); // rbracket
+    try renderToken(ais, tree, symbolic_name + 2, .Space); // "constraint"
+    try renderToken(ais, tree, symbolic_name + 3, .None); // lparen
+    try renderExpression(ais, tree, datas[asm_input].lhs, Space.None);
+    return renderToken(ais, tree, datas[asm_input].rhs, space); // rparen
 }
 
-fn renderVarDecl(ais: *Ais, tree: ast.Tree, var_decl: ast.Full.VarDecl) Error!void {
+fn renderVarDecl(ais: *Ais, tree: ast.Tree, var_decl: ast.full.VarDecl) Error!void {
     if (var_decl.visib_token) |visib_token| {
         try renderToken(ais, tree, visib_token, Space.Space); // pub
     }
@@ -1200,7 +1100,7 @@ fn renderVarDecl(ais: *Ais, tree: ast.Tree, var_decl: ast.Full.VarDecl) Error!vo
     try renderExpression(ais, tree, var_decl.ast.init_node, .Semicolon);
 }
 
-fn renderIf(ais: *Ais, tree: ast.Tree, if_node: ast.Full.If, space: Space) Error!void {
+fn renderIf(ais: *Ais, tree: ast.Tree, if_node: ast.full.If, space: Space) Error!void {
     const node_tags = tree.nodes.items(.tag);
     const token_tags = tree.tokens.items(.tag);
 
@@ -1334,7 +1234,7 @@ fn renderIf(ais: *Ais, tree: ast.Tree, if_node: ast.Full.If, space: Space) Error
 fn renderContainerField(
     ais: *Ais,
     tree: ast.Tree,
-    field: ast.Full.ContainerField,
+    field: ast.full.ContainerField,
     space: Space,
 ) Error!void {
     const main_tokens = tree.nodes.items(.main_token);
@@ -1430,7 +1330,7 @@ fn renderBuiltinCall(
     }
 }
 
-fn renderFnProto(ais: *Ais, tree: ast.Tree, fn_proto: ast.Full.FnProto, space: Space) Error!void {
+fn renderFnProto(ais: *Ais, tree: ast.Tree, fn_proto: ast.full.FnProto, space: Space) Error!void {
     const token_tags = tree.tokens.items(.tag);
     const token_starts = tree.tokens.items(.start);
 
@@ -1618,7 +1518,7 @@ fn renderFnProto(ais: *Ais, tree: ast.Tree, fn_proto: ast.Full.FnProto, space: S
 fn renderSwitchCase(
     ais: *Ais,
     tree: ast.Tree,
-    switch_case: ast.Full.SwitchCase,
+    switch_case: ast.full.SwitchCase,
     space: Space,
 ) Error!void {
     const token_tags = tree.tokens.items(.tag);
@@ -1709,7 +1609,7 @@ fn renderBlock(
 fn renderStructInit(
     ais: *Ais,
     tree: ast.Tree,
-    struct_init: ast.Full.StructInit,
+    struct_init: ast.full.StructInit,
     space: Space,
 ) Error!void {
     const token_tags = tree.tokens.items(.tag);
@@ -1763,7 +1663,7 @@ fn renderStructInit(
 fn renderArrayInit(
     ais: *Ais,
     tree: ast.Tree,
-    array_init: ast.Full.ArrayInit,
+    array_init: ast.full.ArrayInit,
     space: Space,
 ) Error!void {
     const token_tags = tree.tokens.items(.tag);
@@ -1809,7 +1709,7 @@ fn renderArrayInit(
 fn renderContainerDecl(
     ais: *Ais,
     tree: ast.Tree,
-    container_decl: ast.Full.ContainerDecl,
+    container_decl: ast.full.ContainerDecl,
     space: Space,
 ) Error!void {
     const token_tags = tree.tokens.items(.tag);
@@ -1894,6 +1794,135 @@ fn renderContainerDecl(
     return renderToken(ais, tree, rbrace, space); // rbrace
 }
 
+fn renderAsm(
+    ais: *Ais,
+    tree: ast.Tree,
+    asm_node: ast.full.Asm,
+    space: Space,
+) Error!void {
+    const token_tags = tree.tokens.items(.tag);
+
+    try renderToken(ais, tree, asm_node.ast.asm_token, .Space); // asm
+
+    if (asm_node.volatile_token) |volatile_token| {
+        try renderToken(ais, tree, volatile_token, .Space); // volatile
+        try renderToken(ais, tree, volatile_token + 1, .None); // lparen
+    } else {
+        try renderToken(ais, tree, asm_node.ast.asm_token + 1, .None); // lparen
+    }
+
+    if (asm_node.ast.items.len == 0) {
+        try renderExpression(ais, tree, asm_node.ast.template, .None);
+        if (asm_node.first_clobber) |first_clobber| {
+            // asm ("foo" ::: "a", "b")
+            var tok_i = first_clobber;
+            while (true) : (tok_i += 1) {
+                try renderToken(ais, tree, tok_i, .None);
+                tok_i += 1;
+                switch (token_tags[tok_i]) {
+                    .RParen => return renderToken(ais, tree, tok_i, space),
+                    .Comma => try renderToken(ais, tree, tok_i, .Space),
+                    else => unreachable,
+                }
+            }
+        } else {
+            // asm ("foo")
+            return renderToken(ais, tree, asm_node.ast.rparen, space); // rparen
+        }
+    }
+
+    ais.pushIndent();
+    try renderExpression(ais, tree, asm_node.ast.template, .Newline);
+    ais.setIndentDelta(asm_indent_delta);
+    const colon1 = tree.lastToken(asm_node.ast.template) + 1;
+
+    const colon2 = if (asm_node.outputs.len == 0) colon2: {
+        try renderToken(ais, tree, colon1, .Newline); // :
+        break :colon2 colon1 + 1;
+    } else colon2: {
+        try renderToken(ais, tree, colon1, .Space); // :
+
+        ais.pushIndent();
+        for (asm_node.outputs) |asm_output, i| {
+            if (i + 1 < asm_node.outputs.len) {
+                const next_asm_output = asm_node.outputs[i + 1];
+                try renderAsmOutput(ais, tree, asm_output, .None);
+
+                const comma = tree.firstToken(next_asm_output) - 1;
+                try renderToken(ais, tree, comma, .Newline); // ,
+                try renderExtraNewlineToken(ais, tree, tree.firstToken(next_asm_output));
+            } else if (asm_node.inputs.len == 0 and asm_node.first_clobber == null) {
+                try renderAsmOutput(ais, tree, asm_output, .Newline);
+                ais.popIndent();
+                ais.setIndentDelta(indent_delta);
+                ais.popIndent();
+                return renderToken(ais, tree, asm_node.ast.rparen, space); // rparen
+            } else {
+                try renderAsmOutput(ais, tree, asm_output, .Newline);
+                const comma_or_colon = tree.lastToken(asm_output) + 1;
+                ais.popIndent();
+                break :colon2 switch (token_tags[comma_or_colon]) {
+                    .Comma => comma_or_colon + 1,
+                    else => comma_or_colon,
+                };
+            }
+        } else unreachable;
+    };
+
+    const colon3 = if (asm_node.inputs.len == 0) colon3: {
+        try renderToken(ais, tree, colon2, .Newline); // :
+        break :colon3 colon2 + 1;
+    } else colon3: {
+        try renderToken(ais, tree, colon2, .Space); // :
+        ais.pushIndent();
+        for (asm_node.inputs) |asm_input, i| {
+            if (i + 1 < asm_node.inputs.len) {
+                const next_asm_input = asm_node.inputs[i + 1];
+                try renderAsmInput(ais, tree, asm_input, .None);
+
+                const first_token = tree.firstToken(next_asm_input);
+                try renderToken(ais, tree, first_token - 1, .Newline); // ,
+                try renderExtraNewlineToken(ais, tree, first_token);
+            } else if (asm_node.first_clobber == null) {
+                try renderAsmInput(ais, tree, asm_input, .Newline);
+                ais.popIndent();
+                ais.setIndentDelta(indent_delta);
+                ais.popIndent();
+                return renderToken(ais, tree, asm_node.ast.rparen, space); // rparen
+            } else {
+                try renderAsmInput(ais, tree, asm_input, .Newline);
+                const comma_or_colon = tree.lastToken(asm_input) + 1;
+                ais.popIndent();
+                break :colon3 switch (token_tags[comma_or_colon]) {
+                    .Comma => comma_or_colon + 1,
+                    else => comma_or_colon,
+                };
+            }
+        }
+        unreachable;
+    };
+
+    try renderToken(ais, tree, colon3, .Space); // :
+    const first_clobber = asm_node.first_clobber.?;
+    var tok_i = first_clobber;
+    while (true) {
+        switch (token_tags[tok_i + 1]) {
+            .RParen => {
+                ais.setIndentDelta(indent_delta);
+                ais.popIndent();
+                try renderToken(ais, tree, tok_i, .Newline);
+                return renderToken(ais, tree, tok_i + 1, space);
+            },
+            .Comma => {
+                try renderToken(ais, tree, tok_i, .None);
+                try renderToken(ais, tree, tok_i + 1, .Space);
+                tok_i += 2;
+            },
+            else => unreachable,
+        }
+    } else unreachable; // TODO shouldn't need this on while(true)
+}
+
 /// Render an expression, and the comma that follows it, if it is present in the source.
 fn renderExpressionComma(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Space) Error!void {
     const token_tags = tree.tokens.items(.tag);