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:
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)