hare

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

type.ha (12424B)


      1 // License: MPL-2.0
      2 // (c) 2021-2022 Alexey Yerin <yyp@disroot.org>
      3 // (c) 2021 Drew DeVault <sir@cmpwn.com>
      4 // (c) 2021 Eyal Sawady <ecs@d2evs.net>
      5 use fmt;
      6 use io;
      7 use hare::ast;
      8 use hare::ast::{variadism};
      9 use hare::lex;
     10 use strings;
     11 use strio;
     12 
     13 // Returns a builtin type as a string.
     14 export fn builtin_type(b: ast::builtin_type) str = switch (b) {
     15 case ast::builtin_type::FCONST, ast::builtin_type::ICONST =>
     16 	abort("ICONST and FCONST have no lexical representation");
     17 case ast::builtin_type::BOOL =>
     18 	yield "bool";
     19 case ast::builtin_type::CHAR =>
     20 	yield "char";
     21 case ast::builtin_type::F32 =>
     22 	yield "f32";
     23 case ast::builtin_type::F64 =>
     24 	yield "f64";
     25 case ast::builtin_type::I16 =>
     26 	yield "i16";
     27 case ast::builtin_type::I32 =>
     28 	yield "i32";
     29 case ast::builtin_type::I64 =>
     30 	yield "i64";
     31 case ast::builtin_type::I8 =>
     32 	yield "i8";
     33 case ast::builtin_type::INT =>
     34 	yield "int";
     35 case ast::builtin_type::NULL =>
     36 	yield "null";
     37 case ast::builtin_type::RUNE =>
     38 	yield "rune";
     39 case ast::builtin_type::SIZE =>
     40 	yield "size";
     41 case ast::builtin_type::STR =>
     42 	yield "str";
     43 case ast::builtin_type::U16 =>
     44 	yield "u16";
     45 case ast::builtin_type::U32 =>
     46 	yield "u32";
     47 case ast::builtin_type::U64 =>
     48 	yield "u64";
     49 case ast::builtin_type::U8 =>
     50 	yield "u8";
     51 case ast::builtin_type::UINT =>
     52 	yield "uint";
     53 case ast::builtin_type::UINTPTR =>
     54 	yield "uintptr";
     55 case ast::builtin_type::VALIST =>
     56 	yield "valist";
     57 case ast::builtin_type::VOID =>
     58 	yield "void";
     59 };
     60 
     61 // Unparses a prototype.
     62 export fn prototype(
     63 	out: io::handle,
     64 	indent: size,
     65 	t: ast::func_type,
     66 ) (size | io::error) = {
     67 	let n = 0z;
     68 	n += fmt::fprint(out, "(")?;
     69 
     70 	let typenames: []str = [];
     71 	// TODO: https://todo.sr.ht/~sircmpwn/hare/581
     72 	if (len(t.params) > 0) {
     73 		typenames = alloc([""...], len(t.params));
     74 	};
     75 	defer strings::freeall(typenames);
     76 	let retname = "";
     77 	defer free(retname);
     78 
     79 	// estimate length of prototype to determine if it should span multiple
     80 	// lines
     81 	const linelen = if (len(t.params) == 0) {
     82 		let strm = strio::dynamic();
     83 		defer io::close(&strm)!;
     84 		_type(&strm, indent, *t.result)?;
     85 		retname = strings::dup(strio::string(&strm));
     86 		yield 0z; // only use one line if there's no parameters
     87 	} else {
     88 		let strm = strio::dynamic();
     89 		defer io::close(&strm)!;
     90 		let linelen = indent * 8 + 5;
     91 		linelen += if (len(t.params) != 0) len(t.params) * 3 - 1 else 0;
     92 		for (let i = 0z; i < len(t.params); i += 1) {
     93 			const param = t.params[i];
     94 			linelen += _type(&strm, indent, *param._type)?;
     95 			typenames[i] = strings::dup(strio::string(&strm));
     96 			linelen += if (param.name == "") 1 else len(param.name);
     97 			strio::reset(&strm);
     98 		};
     99 		switch (t.variadism) {
    100 		case variadism::NONE => void;
    101 		case variadism::HARE =>
    102 			linelen += 3;
    103 		case variadism::C =>
    104 			linelen += 5;
    105 		};
    106 		linelen += _type(&strm, indent, *t.result)?;
    107 		retname = strings::dup(strio::string(&strm));
    108 		yield linelen;
    109 	};
    110 
    111 	// use 72 instead of 80 to give a bit of leeway for preceding text
    112 	if (linelen > 72) {
    113 		indent += 1;
    114 		for (let i = 0z; i < len(t.params); i += 1) {
    115 			const param = t.params[i];
    116 			n += newline(out, indent)?;
    117 			n += fmt::fprintf(out, "{}: ",
    118 				if (param.name == "") "_" else param.name)?;
    119 			n += fmt::fprint(out, typenames[i])?;
    120 			if (i + 1 == len(t.params)
    121 					&& t.variadism == variadism::HARE) {
    122 				n += fmt::fprint(out, "...")?;
    123 			} else {
    124 				n += fmt::fprint(out, ",")?;
    125 			};
    126 		};
    127 		if (t.variadism == variadism::C) {
    128 			n += newline(out, indent)?;
    129 			n += fmt::fprint(out, "...")?;
    130 		};
    131 		indent -= 1;
    132 		n += newline(out, indent)?;
    133 	} else for (let i = 0z; i < len(t.params); i += 1) {
    134 		const param = t.params[i];
    135 		n += fmt::fprintf(out, "{}: ",
    136 			if (param.name == "") "_" else param.name)?;
    137 		n += fmt::fprint(out, typenames[i])?;
    138 		if (i + 1 == len(t.params)) {
    139 			switch (t.variadism) {
    140 			case variadism::NONE => void;
    141 			case variadism::HARE =>
    142 				n += fmt::fprint(out, "...")?;
    143 			case variadism::C =>
    144 				n += fmt::fprint(out, ", ...")?;
    145 			};
    146 		} else {
    147 			n += fmt::fprint(out, ", ")?;
    148 		};
    149 	};
    150 
    151 	n += fmt::fprint(out, ")", retname)?;
    152 	return n;
    153 };
    154 
    155 // Unparses a struct or union type.
    156 fn struct_union_type(
    157 	out: io::handle,
    158 	indent: size,
    159 	t: ast::_type,
    160 ) (size | io::error) = {
    161 	let z = 0z;
    162 	let membs = match (t.repr) {
    163 	case let st: ast::struct_type =>
    164 		z += fmt::fprint(out, "struct {")?;
    165 		yield st: []ast::struct_member;
    166 	case let ut: ast::union_type =>
    167 		z += fmt::fprint(out, "union {")?;
    168 		yield ut: []ast::struct_member;
    169 	};
    170 
    171 	indent += 1z;
    172 	for (let i = 0z; i < len(membs); i += 1) {
    173 		z += fmt::fprintln(out)?;
    174 		if (membs[i].docs != "") {
    175 			z += comment(out, membs[i].docs, indent)?;
    176 		};
    177 		for (let i = 0z; i < indent; i += 1) {
    178 			z += fmt::fprint(out, "\t")?;
    179 		};
    180 
    181 		match (membs[i]._offset) {
    182 		case null => void;
    183 		case let ex: *ast::expr =>
    184 			z += fmt::fprint(out, "@offset(")?;
    185 			z += expr(out, indent, *ex)?;
    186 			z += fmt::fprint(out, ") ")?;
    187 		};
    188 
    189 		match (membs[i].member) {
    190 		case let se: ast::struct_embedded =>
    191 			z += _type(out, indent, *se)?;
    192 		case let sa: ast::struct_alias =>
    193 			z += ident(out, sa)?;
    194 		case let sf: ast::struct_field =>
    195 			z += fmt::fprintf(out, "{}: ", sf.name)?;
    196 			z += _type(out, indent, *sf._type)?;
    197 		};
    198 
    199 		z += fmt::fprint(out, ",")?;
    200 	};
    201 
    202 	indent -= 1;
    203 	z += newline(out, indent)?;
    204 	z += fmt::fprint(out, "}")?;
    205 	return z;
    206 };
    207 
    208 fn multiline_comment(s: str) bool =
    209 	strings::byteindex(s, '\n') as size != len(s) - 1;
    210 
    211 // Unparses a [[hare::ast::_type]].
    212 export fn _type(
    213 	out: io::handle,
    214 	indent: size,
    215 	t: ast::_type,
    216 ) (size | io::error) = {
    217 	let n = 0z;
    218 	if (t.flags & ast::type_flags::CONST != 0
    219 			&& !(t.repr is ast::func_type)) {
    220 		n += fmt::fprint(out, "const ")?;
    221 	};
    222 	if (t.flags & ast::type_flags::ERROR != 0) {
    223 		n += fmt::fprint(out, "!")?;
    224 	};
    225 	match (t.repr) {
    226 	case let a: ast::alias_type =>
    227 		if (a.unwrap) {
    228 			n += fmt::fprint(out, "...")?;
    229 		};
    230 		n += ident(out, a.ident)?;
    231 	case let b: ast::builtin_type =>
    232 		n += fmt::fprint(out, builtin_type(b))?;
    233 	case let e: ast::enum_type =>
    234 		if (e.storage != ast::builtin_type::INT) {
    235 			n += fmt::fprint(out, "enum",
    236 				builtin_type(e.storage), "{")?;
    237 		} else {
    238 			n += fmt::fprint(out, "enum {")?;
    239 		};
    240 		indent += 1;
    241 		n += fmt::fprintln(out)?;
    242 		for (let i = 0z; i < len(e.values); i += 1) {
    243 			let value = e.values[i];
    244 			let wrotedocs = false;
    245 			if (value.docs != "") {
    246 				// Check if comment should go above or next to
    247 				// field
    248 				if (multiline_comment(value.docs)) {
    249 					n += comment(out, value.docs, indent)?;
    250 					wrotedocs = true;
    251 				};
    252 			};
    253 			for (let i = 0z; i < indent; i += 1) {
    254 				n += fmt::fprint(out, "\t")?;
    255 			};
    256 			n += fmt::fprint(out, value.name)?;
    257 			match (value.value) {
    258 			case null => void;
    259 			case let e: *ast::expr =>
    260 				n += fmt::fprint(out, " = ")?;
    261 				n += expr(out, indent, *e)?;
    262 			};
    263 			n += fmt::fprint(out, ",")?;
    264 			if (value.docs != "" && !wrotedocs) {
    265 				n += fmt::fprint(out, " ")?;
    266 				n += comment(out, value.docs, 0)?;
    267 			} else {
    268 				n += fmt::fprintln(out)?;
    269 			};
    270 		};
    271 		indent -= 1;
    272 		for (let i = 0z; i < indent; i += 1) {
    273 			n += fmt::fprint(out, "\t")?;
    274 		};
    275 		n += fmt::fprint(out, "}")?;
    276 	case let f: ast::func_type =>
    277 		if (f.attrs & ast::func_attrs::NORETURN != 0) {
    278 			n += fmt::fprint(out, "@noreturn ")?;
    279 		};
    280 		n += fmt::fprint(out, "fn")?;
    281 		n += prototype(out, indent, f)?;
    282 	case let l: ast::list_type =>
    283 		n += fmt::fprint(out, "[")?;
    284 		match (l.length) {
    285 		case ast::len_slice => void;
    286 		case ast::len_unbounded =>
    287 			n += fmt::fprint(out, "*")?;
    288 		case ast::len_contextual =>
    289 			n += fmt::fprint(out, "_")?;
    290 		case let e: *ast::expr =>
    291 			n += expr(out, indent, *e)?;
    292 		};
    293 		n += fmt::fprint(out, "]")?;
    294 		n += _type(out, indent, *l.members)?;
    295 	case let p: ast::pointer_type =>
    296 		if (p.flags & ast::pointer_flags::NULLABLE != 0) {
    297 			n += fmt::fprint(out, "nullable ")?;
    298 		};
    299 		n += fmt::fprint(out, "*")?;
    300 		n += _type(out, indent, *p.referent)?;
    301 	case ast::struct_type =>
    302 		n += struct_union_type(out, indent, t)?;
    303 	case ast::union_type =>
    304 		n += struct_union_type(out, indent, t)?;
    305 	case let t: ast::tagged_type =>
    306 		// rough estimate of current line length
    307 		let linelen = n;
    308 		n = 0;
    309 		linelen += fmt::fprint(out, "(")?;
    310 		for (let i = 0z; i < len(t); i += 1) {
    311 			linelen += _type(out, indent, *t[i])?;
    312 			if (i + 1 == len(t)) break;
    313 			linelen += fmt::fprint(out, " |")?;
    314 			// use 72 instead of 80 to give a bit of leeway for long
    315 			// type names
    316 			if (linelen + (indent + 1) * 8 > 72) {
    317 				n += linelen;
    318 				linelen = 0;
    319 				n += fmt::fprintln(out)?;
    320 				for (let i = 0z; i <= indent; i += 1) {
    321 					n += fmt::fprint(out, "\t")?;
    322 				};
    323 			} else {
    324 				linelen += fmt::fprint(out, " ")?;
    325 			};
    326 		};
    327 		n += linelen;
    328 		n += fmt::fprint(out, ")")?;
    329 	case let t: ast::tuple_type =>
    330 		// rough estimate of current line length
    331 		let linelen = n;
    332 		n = 0;
    333 		linelen += fmt::fprint(out, "(")?;
    334 		for (let i = 0z; i < len(t); i += 1) {
    335 			linelen += _type(out, indent, *t[i])?;
    336 			if (i + 1 == len(t)) break;
    337 			linelen += fmt::fprint(out, ",")?;
    338 			// use 72 instead of 80 to give a bit of leeway for long
    339 			// type names
    340 			if (linelen + (indent + 1) * 8 > 72) {
    341 				n += linelen;
    342 				linelen = 0;
    343 				n += fmt::fprintln(out)?;
    344 				for (let i = 0z; i <= indent; i += 1) {
    345 					n += fmt::fprint(out, "\t")?;
    346 				};
    347 			} else {
    348 				linelen += fmt::fprint(out, " ")?;
    349 			};
    350 		};
    351 		n += linelen;
    352 		n += fmt::fprint(out, ")")?;
    353 	};
    354 	return n;
    355 };
    356 
    357 fn type_test(t: ast::_type, expected: str) void = {
    358 	let buf = strio::dynamic();
    359 	_type(&buf, 0, t) as size;
    360 	let s = strio::string(&buf);
    361 	defer free(s);
    362 	if (s != expected) {
    363 		fmt::errorfln("=== wanted\n{}", expected)!;
    364 		fmt::errorfln("=== got\n{}", s)!;
    365 		abort();
    366 	};
    367 };
    368 
    369 @test fn _type() void = {
    370 	let loc = lex::location {
    371 		path = "<test>",
    372 		line = 0,
    373 		col = 0,
    374 	};
    375 	let t = ast::_type {
    376 		start = loc,
    377 		end = loc,
    378 		flags = ast::type_flags::CONST,
    379 		repr = ast::alias_type {
    380 			unwrap = false,
    381 			ident = ["foo", "bar"],
    382 		},
    383 	};
    384 	let type_int = ast::_type {
    385 		start = loc,
    386 		end = loc,
    387 		flags = 0,
    388 		repr = ast::builtin_type::INT,
    389 	};
    390 	let expr_void = ast::expr {
    391 		start = lex::location { ... },
    392 		end = lex::location { ... },
    393 		expr = void,
    394 	};
    395 
    396 	type_test(t, "const foo::bar");
    397 	t.flags = 0;
    398 	t.repr = ast::alias_type {
    399 		unwrap = true,
    400 		ident = ["baz"],
    401 	};
    402 	type_test(t, "...baz");
    403 
    404 	t.flags = ast::type_flags::ERROR;
    405 	t.repr = ast::builtin_type::INT;
    406 	type_test(t, "!int");
    407 
    408 	t.flags = ast::type_flags::CONST | ast::type_flags::ERROR;
    409 	t.repr = ast::enum_type {
    410 		storage = ast::builtin_type::U32,
    411 		values = [
    412 			ast::enum_field {
    413 				name = "FOO",
    414 				value = null,
    415 				loc = loc,
    416 				docs = "",
    417 			},
    418 			ast::enum_field {
    419 				name = "BAR",
    420 				value = &expr_void,
    421 				loc = loc,
    422 				docs = "",
    423 			},
    424 		],
    425 	};
    426 	type_test(t, "const !enum u32 {\n\tFOO,\n\tBAR = void,\n}");
    427 
    428 	t.flags = 0;
    429 
    430 	t.repr = ast::func_type {
    431 		result = &type_int,
    432 		attrs = 0,
    433 		variadism = variadism::NONE,
    434 		params = [],
    435 	};
    436 	type_test(t, "fn() int");
    437 	t.repr = ast::func_type {
    438 		result = &type_int,
    439 		attrs = ast::func_attrs::NORETURN,
    440 		variadism = variadism::C,
    441 		params = [
    442 			ast::func_param {
    443 				loc = loc,
    444 				name = "",
    445 				_type = &type_int,
    446 			},
    447 		],
    448 	};
    449 	type_test(t, "@noreturn fn(_: int, ...) int");
    450 	t.repr = ast::func_type {
    451 		result = &type_int,
    452 		attrs = 0,
    453 		variadism = variadism::HARE,
    454 		params = [
    455 			ast::func_param {
    456 				loc = loc,
    457 				name = "foo",
    458 				_type = &type_int,
    459 			},
    460 			ast::func_param {
    461 				loc = loc,
    462 				name = "bar",
    463 				_type = &type_int,
    464 			},
    465 		],
    466 	};
    467 	type_test(t, "fn(foo: int, bar: int...) int");
    468 
    469 	t.flags = ast::type_flags::CONST;
    470 	type_test(t, "fn(foo: int, bar: int...) int");
    471 
    472 	t.flags = 0;
    473 	t.repr = ast::list_type {
    474 		length = ast::len_slice,
    475 		members = &type_int,
    476 	};
    477 	type_test(t, "[]int");
    478 	t.repr = ast::list_type {
    479 		length = ast::len_unbounded,
    480 		members = &type_int,
    481 	};
    482 	type_test(t, "[*]int");
    483 	t.repr = ast::list_type {
    484 		length = ast::len_contextual,
    485 		members = &type_int,
    486 	};
    487 	type_test(t, "[_]int");
    488 	t.repr = ast::list_type {
    489 		length = &expr_void,
    490 		members = &type_int,
    491 	};
    492 	type_test(t, "[void]int");
    493 
    494 	t.repr = ast::pointer_type {
    495 		referent = &type_int,
    496 		flags = 0,
    497 	};
    498 	type_test(t, "*int");
    499 	t.repr = ast::pointer_type {
    500 		referent = &type_int,
    501 		flags = ast::pointer_flags::NULLABLE,
    502 	};
    503 	type_test(t, "nullable *int");
    504 
    505 	t.repr = [&type_int, &type_int]: ast::tagged_type;
    506 	type_test(t, "(int | int)");
    507 
    508 	t.repr = [&type_int, &type_int]: ast::tuple_type;
    509 	type_test(t, "(int, int)");
    510 };