hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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 };