syn.ha (5354B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use fmt; 5 use hare::ast; 6 use io; 7 8 // A user-supplied function which writes unparsed Hare source code to a handle, 9 // optionally including extra stylistic features. The function is expected to 10 // write to, at the minimum, write the provided string to ctx.out, and update 11 // ctx.linelen based on how much data was written. 12 // 13 // [[syn_nowrap]] and [[syn_wrap]] are provided for when no additional styling 14 // is desired. 15 export type synfunc = fn( 16 ctx: *context, 17 s: str, 18 kind: synkind, 19 ) (size | io::error); 20 21 // The kind of thing being unparsed. 22 export type synkind = enum { 23 IDENT, 24 COMMENT, 25 CONSTANT, 26 FUNCTION, 27 GLOBAL, 28 TYPEDEF, 29 IMPORT_ALIAS, 30 SECONDARY, 31 KEYWORD, 32 TYPE, 33 ATTRIBUTE, 34 OPERATOR, 35 PUNCTUATION, 36 RUNE_STRING, 37 NUMBER, 38 LABEL, 39 }; 40 41 // Context about the unparsing state supplied to a [[synfunc]]. The linelen and 42 // indent fields may be mutated. 43 export type context = struct { 44 out: io::handle, 45 stack: nullable *stack, 46 linelen: size, 47 indent: size, 48 }; 49 50 // A linked list of AST nodes currently being unparsed. 51 export type stack = struct { 52 cur: (*ast::decl | *ast::expr | *ast::_type | *ast::import), 53 up: nullable *stack, 54 extra: nullable *opaque, 55 }; 56 57 // A [[synfunc]] implementation which unparses without additional styling, and 58 // without wrapping any long lines. 59 export fn syn_nowrap( 60 ctx: *context, 61 s: str, 62 kind: synkind, 63 ) (size | io::error) = { 64 const z = fmt::fprint(ctx.out, s)?; 65 ctx.linelen += z; 66 return z; 67 }; 68 69 type syn_wrap_extra = enum { 70 NONE, 71 MULTILINE_FN_PARAM, 72 MULTILINE_FN_OTHER, 73 MULTILINE_TAGGED_OR_TUPLE, 74 }; 75 76 // A [[synfunc]] implementation which unparses without additional styling, but 77 // which wraps some long lines at 80 columns, in accordance with the style 78 // guide. 79 export fn syn_wrap(ctx: *context, s: str, kind: synkind) (size | io::error) = { 80 let extra = :extra { 81 let st = match (ctx.stack) { 82 case let st: *stack => 83 yield st; 84 case null => 85 yield :extra, &syn_wrap_extra::NONE; 86 }; 87 88 match (st.extra) { 89 case let p: *opaque => 90 yield :extra, p: *syn_wrap_extra; 91 case null => 92 match (st.up) { 93 case let st: *stack => 94 match (st.extra) { 95 case let p: *opaque => 96 const p = p: *syn_wrap_extra; 97 if (*p == syn_wrap_extra::MULTILINE_FN_PARAM) { 98 yield :extra, p; 99 }; 100 case null => void; 101 }; 102 case null => void; 103 }; 104 }; 105 106 if (s == "(") match (st.cur) { 107 case let t: *ast::_type => 108 match (t.repr) { 109 case ast::func_type => void; 110 case => 111 yield :extra, &syn_wrap_extra::NONE; 112 }; 113 114 let z = _type(io::empty, &syn_nowrap, t)!; 115 if (ctx.linelen + z < 80) yield; 116 st.extra = alloc(syn_wrap_extra::MULTILINE_FN_PARAM); 117 z = fmt::fprintln(ctx.out, s)?; 118 ctx.linelen = 0; 119 ctx.indent += 1; 120 return z; 121 case => 122 yield :extra, &syn_wrap_extra::NONE; 123 }; 124 125 // use 72 as max linelen instead of 80 to give a bit of leeway. 126 // XXX: this probably could be made more accurate 127 if (ctx.linelen < 72 || (s != "," && s != "|")) { 128 yield :extra, &syn_wrap_extra::NONE; 129 }; 130 131 const t = match (st.cur) { 132 case let t: *ast::_type => 133 yield t; 134 case => 135 yield :extra, &syn_wrap_extra::NONE; 136 }; 137 138 match (t.repr) { 139 case (ast::tagged_type | ast::tuple_type) => void; 140 case => 141 yield :extra, &syn_wrap_extra::NONE; 142 }; 143 144 st.extra = alloc(syn_wrap_extra::MULTILINE_TAGGED_OR_TUPLE); 145 let z = fmt::fprintln(ctx.out, s)?; 146 ctx.indent += 1; 147 ctx.linelen = ctx.indent * 8; 148 for (let i = 0z; i < ctx.indent; i += 1) { 149 z += fmt::fprint(ctx.out, "\t")?; 150 }; 151 return z; 152 }; 153 154 let z = 0z; 155 156 switch (*extra) { 157 case syn_wrap_extra::NONE => void; 158 case syn_wrap_extra::MULTILINE_FN_PARAM => 159 switch (s) { 160 case ")" => 161 match (ctx.stack) { 162 case let st: *stack => 163 free(st.extra); 164 st.extra = null; 165 case null => void; 166 }; 167 ctx.indent -= 1; 168 case "..." => 169 match (ctx.stack) { 170 case let st: *stack => 171 free(st.extra); 172 st.extra = null; 173 case null => void; 174 }; 175 for (let i = 0z; i < ctx.indent; i += 1) { 176 z += fmt::fprint(ctx.out, "\t")?; 177 }; 178 z += fmt::fprintln(ctx.out, s)?; 179 ctx.indent -= 1; 180 ctx.linelen = 0; 181 return z; 182 case => 183 *extra = syn_wrap_extra::MULTILINE_FN_OTHER; 184 ctx.linelen = ctx.indent * 8; 185 for (let i = 0z; i < ctx.indent; i += 1) { 186 z += fmt::fprint(ctx.out, "\t")?; 187 }; 188 }; 189 case syn_wrap_extra::MULTILINE_FN_OTHER => 190 switch (s) { 191 case ")" => 192 match (ctx.stack) { 193 case let st: *stack => 194 free(st.extra); 195 st.extra = null; 196 case null => void; 197 }; 198 ctx.indent -= 1; 199 ctx.linelen = ctx.indent * 8; 200 z += fmt::fprintln(ctx.out, ",")?; 201 for (let i = 0z; i < ctx.indent; i += 1) { 202 z += fmt::fprint(ctx.out, "\t")?; 203 }; 204 case ",", "..." => 205 *extra = syn_wrap_extra::MULTILINE_FN_PARAM; 206 ctx.linelen = 0; 207 return fmt::fprintln(ctx.out, s)?; 208 case => void; 209 }; 210 case syn_wrap_extra::MULTILINE_TAGGED_OR_TUPLE => 211 switch (s) { 212 case ")" => 213 let st = ctx.stack as *stack; 214 free(st.extra); 215 st.extra = null; 216 ctx.indent -= 1; 217 case ",", "|" => 218 if (ctx.linelen < 72) yield; 219 z += fmt::fprintln(ctx.out, s)?; 220 ctx.linelen = ctx.indent * 8; 221 for (let i = 0z; i < ctx.indent; i += 1) { 222 z += fmt::fprint(ctx.out, "\t")?; 223 }; 224 return z; 225 case => void; 226 }; 227 }; 228 229 z += syn_nowrap(ctx, s, kind)?; 230 return z; 231 };