hare

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

commit 7d8ff23f1974831692c323e9f70e50eb644fabfc
parent ac9c010cb1c8e009c1a486ed1b2c829bf2a4aa9d
Author: Drew DeVault <sir@cmpwn.com>
Date:   Wed, 14 Apr 2021 17:08:41 -0400

hare::parse: implement struct and union types

Diffstat:
Mhare/ast/type.ha | 55+++++++++++++++++++++++++++++++++----------------------
Mhare/parse/+test/roundtrip.ha | 6+++++-
Ahare/parse/+test/types.ha | 18++++++++++++++++++
Mhare/parse/type.ha | 108++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mhare/parse/util.ha | 2+-
Mhare/unparse/type.ha | 49++++++++++++++++++++++++++++++++++++++++++++++++-
Mscripts/gen-stdlib | 1+
Mstdlib.mk | 1+
8 files changed, 214 insertions(+), 26 deletions(-)

diff --git a/hare/ast/type.ha b/hare/ast/type.ha @@ -78,11 +78,16 @@ export type struct_alias = ident; // struct { @offset(10) foo: int, struct { bar: int }, baz::quux } export type struct_member = struct { - is_union: bool, _offset: nullable *expr, member: (struct_field | struct_embedded | struct_alias), }; +// struct { ... } +export type struct_type = []struct_member; + +// union { ... } +export type union_type = []struct_member; + // (int | bool, ...) export type tagged_type = []*_type; @@ -99,8 +104,32 @@ export type _type = struct { loc: lex::location, flags: type_flags, _type: (alias_type | builtin_type | enum_type | func_type | - list_type | pointer_type | []struct_member | tagged_type | - tuple_type), + list_type | pointer_type | struct_type | union_type | + tagged_type | tuple_type), +}; + +fn struct_type_free(t: (struct_type | union_type)) void = { + let membs = match (t) { + s: struct_type => s: []struct_member, + u: union_type => u: []struct_member, + }; + for (let i = 0z; i < len(membs); i += 1) { + match (membs[i]._offset) { + null => void, + e: *expr => expr_free(e), + }; + match (membs[i].member) { + f: struct_field => { + free(f.name); + type_free(f._type); + }, + e: struct_embedded => { + type_free(e: *_type); + }, + a: struct_alias => ident_free(a), + }; + }; + free(membs); }; export fn type_free(t: (_type | nullable *_type)) void = match (t) { @@ -143,25 +172,7 @@ export fn type_free(t: (_type | nullable *_type)) void = match (t) { type_free(l.members); }, p: pointer_type => type_free(p.referent), - s: []struct_member => { - for (let i = 0z; i < len(s); i += 1) { - match (s[i]._offset) { - null => void, - e: *expr => expr_free(e), - }; - match (s[i].member) { - f: struct_field => { - free(f.name); - type_free(f._type); - }, - e: struct_embedded => { - type_free(e: *_type); - }, - a: struct_alias => ident_free(a), - }; - }; - free(s); - }, + s: (struct_type | union_type) => struct_type_free(s), t: tagged_type => { for (let i = 0z; i < len(t); i += 1) { type_free(t[i]); diff --git a/hare/parse/+test/roundtrip.ha b/hare/parse/+test/roundtrip.ha @@ -27,5 +27,9 @@ fn roundtrip(src: str) void = { unparse::subunit(out, u) as size; let unsrc = strio::finish(out); defer free(unsrc); - assert(unsrc == src); + if (unsrc != src) { + fmt::errorfln("=== wanted\n{}", src); + fmt::errorfln("=== got\n{}", unsrc); + abort(); + }; }; diff --git a/hare/parse/+test/types.ha b/hare/parse/+test/types.ha @@ -0,0 +1,18 @@ +@test fn struct_union() void = { + roundtrip("export type foo = struct { + @offset(void) x: int, + y: int, +}; +export type bar = union { + x: int, + y: int, +}; +export type baz = struct { + embedded, + struct { + x: int, + y: int, + }, +}; +"); +}; diff --git a/hare/parse/type.ha b/hare/parse/type.ha @@ -196,6 +196,112 @@ fn fn_type(lexer: *lex::lexer) (ast::_type | error) = { }; }; +fn struct_union_type(lexer: *lex::lexer) (ast::_type | error) = { + let membs: []ast::struct_member = []; + let kind = want(lexer, ltok::STRUCT, ltok::UNION)?; + want(lexer, ltok::LBRACE)?; + + for (true) { + if (try(lexer, ltok::RBRACE) is lex::token) { + synassert(mkloc(lexer), len(membs) != 0, + "Expected field list")?; + break; + }; + + let offs: nullable *ast::expr = match (try(lexer, ltok::ATTR_OFFSET)?) { + void => null, + lex::token => { + want(lexer, ltok::LPAREN)?; + let ex = expression(lexer)?; + want(lexer, ltok::RPAREN)?; + alloc(ex); + }, + }; + + let tok = want(lexer, ltok::NAME, ltok::STRUCT, ltok::UNION)?; + switch (tok.0) { + ltok::NAME => { + lex::unlex(lexer, tok); + let memb = struct_embed_or_field(lexer, offs)?; + append(membs, memb); + }, + ltok::STRUCT, ltok::UNION => { + lex::unlex(lexer, tok); + let subtype = struct_union_type(lexer)?; + append(membs, ast::struct_member { + _offset = offs, + member = alloc(subtype), + }); + }, + * => abort(), + }; + + switch (want(lexer, ltok::COMMA, ltok::RBRACE)?.0) { + ltok::COMMA => void, + ltok::RBRACE => break, + * => abort(), + }; + }; + + return ast::_type { + loc = kind.2, + _type = switch (kind.0) { + ltok::STRUCT => membs: ast::struct_type, + ltok::UNION => membs: ast::union_type, + * => abort(), + }, + ... + }; +}; + +fn struct_embed_or_field( + lexer: *lex::lexer, + offs: nullable *ast::expr, +) (ast::struct_member | error) = { + // Disambiguates between `name: type` and `identifier` + // + // struct-union-field + // name : type + // identifier + // + // identifier + // name + // name :: identifier + let name = want(lexer, ltok::NAME)?; + + let id: ast::ident = match (try(lexer, ltok::COLON, ltok::DOUBLE_COLON)?) { + void => alloc([name.1 as str]), + tok: lex::token => switch (tok.0) { + ltok::COLON => { + let field = ast::struct_field { + name = name.1 as str, + _type = alloc(_type(lexer)?), + }; + return ast::struct_member { + _offset = offs, + member = field, + }; + }, + ltok::DOUBLE_COLON => { + // XXX: insert + let rest = ident(lexer)?; + let id: ast::ident = alloc([ + name.1 as str, + ]); + append(id, rest...); + free(rest); + id; + }, + * => abort(), + }, + }; + + return ast::struct_member { + _offset = offs, + member = id: ast::struct_alias, + }; +}; + // Parses a type export fn _type(lexer: *lex::lexer) (ast::_type | error) = { let flags: ast::type_flags = match (try(lexer, ltok::CONST)?) { @@ -212,7 +318,7 @@ export fn _type(lexer: *lex::lexer) (ast::_type | error) = { ltok::VOID, ltok::NULL => primitive_type(lexer)?, ltok::ENUM => abort(), // TODO ltok::NULLABLE, ltok::TIMES => pointer_type(lexer)?, - ltok::STRUCT, ltok::UNION => abort(), // TODO + ltok::STRUCT, ltok::UNION => struct_union_type(lexer)?, ltok::LBRACKET => abort(), // TODO ltok::LPAREN => { want(lexer, ltok::LPAREN)?; diff --git a/hare/parse/util.ha b/hare/parse/util.ha @@ -20,7 +20,7 @@ fn want(lexer: *lex::lexer, want: lex::ltok...) (lex::token | error) = { let buf = strio::dynamic(); defer io::close(buf); for (let i = 0z; i < len(want); i += 1) { - fmt::fprintf(buf, lex::tokstr((want[i], void, mkloc(lexer)))); + fmt::fprintf(buf, "{}", lex::tokstr((want[i], void, mkloc(lexer)))); if (i + 1 < len(want)) { fmt::fprint(buf, ", "); }; diff --git a/hare/unparse/type.ha b/hare/unparse/type.ha @@ -57,6 +57,52 @@ fn prototype( return n; }; +fn struct_union_type( + out: *io::stream, + indent: size, + t: ast::_type, +) (size | io::error) = { + let z = 0z; + let membs = match (t._type) { + st: ast::struct_type => { + z += fmt::fprint(out, "struct {")?; + st: []ast::struct_member; + }, + ut: ast::union_type => { + z += fmt::fprint(out, "union {")?; + ut: []ast::struct_member; + }, + }; + + indent += 1z; + for (let i = 0z; i < len(membs); i += 1) { + z += newline(out, indent)?; + + z += match (membs[i]._offset) { + null => 0z, + ex: *ast::expr => fmt::fprint(out, "@offset(")? + + expr(out, indent, *ex)? + + fmt::fprint(out, ") ")?, + }; + + z += match (membs[i].member) { + se: ast::struct_embedded => _type(out, indent, *se)?, + sa: ast::struct_alias => ident(out, sa)?, + sf: ast::struct_field => { + fmt::fprintf(out, "{}: ", sf.name)? + + _type(out, indent, *sf._type)?; + }, + }; + + z += fmt::fprint(out, ",")?; + }; + + indent -= 1; + z += newline(out, indent)?; + z += fmt::fprint(out, "}")?; + return z; +}; + export fn _type( out: *io::stream, indent: size, @@ -121,7 +167,8 @@ export fn _type( n += fmt::fprint(out, "*")?; n += _type(out, indent, *p.referent)?; }, - s: []ast::struct_member => abort(), // TODO + ast::struct_type => n += struct_union_type(out, indent, t)?, + ast::union_type => n += struct_union_type(out, indent, t)?, t: ast::tagged_type => { n += fmt::fprint(out, "(")?; for (let i = 0z; i < len(t); i += 1) { diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -375,6 +375,7 @@ hare_parse() { +test/expr.ha \ +test/ident.ha \ +test/roundtrip.ha \ + +test/types.ha \ +test/unit.ha gen_ssa hare::parse bufio fmt hare::ast hare::lex hare::unparse io \ strings strio fmt diff --git a/stdlib.mk b/stdlib.mk @@ -1357,6 +1357,7 @@ testlib_hare_parse_srcs= \ $(STDLIB)/hare/parse/+test/expr.ha \ $(STDLIB)/hare/parse/+test/ident.ha \ $(STDLIB)/hare/parse/+test/roundtrip.ha \ + $(STDLIB)/hare/parse/+test/types.ha \ $(STDLIB)/hare/parse/+test/unit.ha $(TESTCACHE)/hare/parse/hare_parse.ssa: $(testlib_hare_parse_srcs) $(testlib_rt) $(testlib_bufio) $(testlib_fmt) $(testlib_hare_ast) $(testlib_hare_lex) $(testlib_hare_unparse) $(testlib_io) $(testlib_strings) $(testlib_strio) $(testlib_fmt)