commit f4d83fe36175b6aadafedabe7c74f5f6be7133d8
parent 92a64297a98c7e21400b6c2e811c61c36fdd49ec
Author: Eyal Sawady <ecs@d2evs.net>
Date: Wed, 24 Mar 2021 01:30:00 -0400
hare::ast: flesh out
Diffstat:
9 files changed, 750 insertions(+), 80 deletions(-)
diff --git a/hare/ast/decl.ha b/hare/ast/decl.ha
@@ -0,0 +1,67 @@
+use hare::lex;
+
+// let foo: int = 0;
+// const foo: int = 0;
+export type decl_global = struct {
+ is_const: bool,
+ symbol: str,
+ ident: ident,
+ _type: _type,
+ init: expr,
+};
+
+// type foo = int;
+export type decl_type = struct {
+ ident: ident,
+ _type: _type,
+};
+
+// Represents the @fini/@init/@test attributes associated with a function
+export type fndecl_attrs = enum {
+ NONE,
+ FINI,
+ INIT,
+ TEST,
+};
+
+// fn main() void = void;
+export type decl_func = struct {
+ symbol: str,
+ ident: ident,
+ prototype: _type,
+ body: (expr | void),
+ attrs: fndecl_attrs,
+};
+
+// A declaration
+export type decl = struct {
+ exported: bool,
+ loc: lex::location,
+ decl: ([]decl_global | []decl_type | decl_func),
+};
+
+// Frees resources associated with a declaration
+export fn decl_free(d: decl) void = match (d.decl) {
+ g: []decl_global => {
+ for (let i = 0z; i < len(g); i += 1) {
+ free(g[i].symbol);
+ ident_free(g[i].ident);
+ type_free(g[i]._type);
+ expr_free(g[i].init);
+ };
+ free(g);
+ },
+ t: []decl_type => {
+ for (let i = 0z; i < len(t); i += 1) {
+ ident_free(t[i].ident);
+ type_free(t[i]._type);
+ };
+ free(t);
+ },
+ f: decl_func => {
+ free(f.symbol);
+ ident_free(f.ident);
+ type_free(f.prototype);
+ if (f.body is expr) expr_free(f.body as expr);
+ },
+};
diff --git a/hare/ast/expr.ha b/hare/ast/expr.ha
@@ -0,0 +1,404 @@
+use hare::lex;
+
+// foo
+export type access_identifier = ident;
+
+// foo[0]
+export type access_index = struct {
+ object: *expr,
+ index: *expr,
+};
+
+// foo.bar
+export type access_field = struct {
+ object: *expr,
+ field: str,
+};
+
+// foo.1
+export type access_tuple = struct {
+ object: *expr,
+ value: *expr,
+};
+
+export type access_expr =
+ (access_identifier | access_index | access_field | access_tuple);
+
+// alloc(foo)
+export type alloc_expr = struct {
+ expr: *expr,
+ capacity: nullable *expr,
+};
+
+// append(foo, bar, (more), ...baz)
+export type append_expr = struct {
+ expr: *expr,
+ variadic: nullable *expr,
+ values: []*expr,
+};
+
+// assert(foo), assert(foo, "error"), abort(), abort("error")
+export type assert_expr = struct {
+ cond: nullable *expr,
+ message: nullable *expr,
+ is_static: bool,
+};
+
+// foo = bar
+export type assign_expr = struct {
+ op: binarithm_op,
+ object: *expr,
+ value: *expr,
+ indirect: bool,
+};
+
+// TODO: Rehome this with the checked AST?
+export type binarithm_op = enum {
+ BAND, // &
+ BOR, // |
+ DIV, // /
+ GT, // >
+ GTEQ, // >=
+ LAND, // &&
+ LEQUAL, // ==
+ LESS, // <
+ LESSEQ, // <=
+ LOR, // ||
+ LSHIFT, // <<
+ LXOR, // ^^
+ MINUS, // -
+ MODULO, // %
+ NEQUAL, // !=
+ PLUS, // +
+ RSHIFT, // >>
+ TIMES, // *
+ BXOR, // ^
+};
+
+// foo * bar
+export type binarithm_expr = struct {
+ op: binarithm_op,
+ lvalue: *expr,
+ rvalue: *expr,
+};
+
+// let foo: int = bar
+export type binding_expr = struct {
+ name: str,
+ _type: nullable *_type,
+ is_static: bool,
+ init: *expr,
+};
+
+// break :label
+export type break_expr = label;
+
+// foo(bar)
+export type call_expr = struct {
+ lvalue: *expr,
+ variadic: bool,
+ args: []*expr,
+};
+
+// TODO: Should this be rehomed with the checked AST?
+export type cast_kind = enum {
+ CAST,
+ ASSERTION,
+ TEST,
+};
+
+// foo: int
+export type cast_expr = struct {
+ kind: cast_kind,
+ value: *expr,
+ _type: *_type,
+};
+
+// [foo, bar, ...]
+export type array_constant = struct {
+ expand: bool,
+ values: []*expr,
+};
+
+export type struct_value = struct {
+ name: str,
+ _type: nullable *_type,
+ init: *expr,
+};
+
+// struct { foo: int = bar, struct { baz = quux }, ... }
+export type struct_constant = struct {
+ autofill: bool,
+ alias: ident, // [] for anonymous
+ fields: [](struct_value | *struct_constant),
+};
+
+// (foo, bar, ...)
+export type tuple_constant = []*expr;
+
+// A constant
+export type constant_expr = (void | lex::literal | array_constant |
+ struct_constant | tuple_constant);
+
+// continue :label
+export type continue_expr = label;
+
+// defer foo
+export type defer_expr = *expr;
+
+// delete(foo)
+export type delete_expr = *expr;
+
+// :label for (let foo = 0; foo < bar; baz) quux
+export type for_expr = struct {
+ label: label,
+ bindings: nullable *expr,
+ cond: *expr,
+ afterthought: nullable *expr,
+ body: *expr,
+};
+
+// free(foo)
+export type free_expr = *expr;
+
+// if (foo) bar else baz
+export type if_expr = struct {
+ cond: *expr,
+ true_branch: nullable *expr,
+ false_branch: nullable *expr,
+};
+
+// :label. The ":" character is not included.
+export type label = str;
+
+// len(foo)
+export type len_expr = *expr;
+
+// { foo; bar; ... }
+export type list_expr = []*expr;
+
+export type match_case = struct {
+ name: str,
+ _type: *_type,
+ value: *expr,
+};
+
+// match (foo) { int => bar, ... }
+export type match_expr = struct {
+ value: *expr,
+ cases: []match_case,
+ default: nullable *expr,
+};
+
+// offset(foo.bar)
+export type offset_expr = void; // TODO
+
+// foo?
+export type propagate_expr = *expr;
+
+// return foo
+export type return_expr = *expr;
+
+// size(int)
+export type size_expr = *_type;
+
+// foo[bar..baz]
+export type slice_expr = struct {
+ object: *expr,
+ start: nullable *expr,
+ end: nullable *expr,
+};
+
+export type switch_case = struct {
+ options: []*expr, // [] for *
+ value: *expr,
+};
+
+// switch (foo) { bar => baz, ... }
+export type switch_expr = struct {
+ value: *expr,
+ cases: []switch_case,
+};
+
+// A unary operator
+// TODO: Should this be rehomed with the checked AST?
+export type unarithm_op = enum {
+ ADDR, // &
+ BNOT, // ~
+ DEREF, // *
+ LNOT, // !
+ MINUS, // -
+ PLUS, // +
+};
+
+// Unary arithmetic
+export type unarithm_expr = struct {
+ op: unarithm_op,
+ operand: *expr,
+};
+
+// An expression
+export type expr = (access_expr | alloc_expr | append_expr | assert_expr |
+ assign_expr | binarithm_expr | []binding_expr | break_expr | call_expr |
+ cast_expr | constant_expr | continue_expr | defer_expr | delete_expr |
+ for_expr | free_expr | if_expr | list_expr | match_expr | len_expr |
+ size_expr | offset_expr | propagate_expr | return_expr | slice_expr |
+ switch_expr | unarithm_expr);
+
+export fn expr_free(e: (expr | nullable *expr)) void = match (e) {
+ e: nullable *expr => match (e) {
+ null => void,
+ e: *expr => {
+ expr_free(*e);
+ free(e);
+ },
+ },
+ e: expr => match (e) {
+ a: access_expr => match (a) {
+ i: access_identifier => ident_free(i),
+ i: access_index => {
+ expr_free(i.object);
+ expr_free(i.index);
+ },
+ f: access_field => {
+ expr_free(f.object);
+ free(f.field);
+ },
+ t: access_tuple => {
+ expr_free(t.object);
+ expr_free(t.value);
+ },
+ },
+ a: alloc_expr => {
+ expr_free(a.expr);
+ expr_free(a.capacity);
+ },
+ a: append_expr => {
+ expr_free(a.expr);
+ match (a.variadic) {
+ null => void,
+ v: *expr => expr_free(v),
+ };
+ for (let i = 0z; i < len(a.values); i += 1) {
+ expr_free(a.values[i]);
+ };
+ free(a.values);
+ },
+ a: assert_expr => {
+ expr_free(a.cond);
+ expr_free(a.message);
+ },
+ a: assign_expr => {
+ expr_free(a.object);
+ expr_free(a.value);
+ },
+ b: binarithm_expr => {
+ expr_free(b.lvalue);
+ expr_free(b.rvalue);
+ },
+ b: []binding_expr => {
+ for (let i = 0z; i < len(b); i += 1) {
+ free(b[i].name);
+ type_free(b[i]._type);
+ expr_free(b[i].init);
+ };
+ free(b);
+ },
+ b: break_expr => free(b),
+ c: call_expr => {
+ expr_free(c.lvalue);
+ for (let i = 0z; i < len(c.args); i += 1) {
+ expr_free(c.args[i]);
+ };
+ free(c.args);
+ },
+ c: cast_expr => {
+ expr_free(c.value);
+ expr_free(c._type);
+ },
+ c: constant_expr => match(c) {
+ (void | lex::literal) => void,
+ a: array_constant => {
+ for (let i = 0z; i < len(a.values); i += 1) {
+ expr_free(a.values[i]);
+ };
+ free(a.values);
+ },
+ s: struct_constant => {
+ ident_free(s.alias);
+ for (let i = 0z; i < len(s.fields); i += 1) {
+ match (s.fields[i]) {
+ v: struct_value => {
+ free(v.name);
+ type_free(v._type);
+ expr_free(v.init);
+ },
+ c: *struct_constant =>
+ expr_free(c: *expr),
+ };
+ };
+ free(s.fields);
+ },
+ t: tuple_constant => {
+ for (let i = 0z; i < len(t); i += 1) {
+ expr_free(t[i]);
+ };
+ free(t);
+ },
+ },
+ c: continue_expr => free(c),
+ d: defer_expr => expr_free(d: *expr),
+ d: delete_expr => expr_free(d: *expr),
+ f: for_expr => {
+ free(f.label);
+ expr_free(f.bindings);
+ expr_free(f.cond);
+ expr_free(f.afterthought);
+ expr_free(f.body);
+ },
+ f: free_expr => expr_free(f: *expr),
+ i: if_expr => {
+ expr_free(i.cond);
+ expr_free(i.true_branch);
+ expr_free(i.false_branch);
+ },
+ l: len_expr => expr_free(l: *expr),
+ l: list_expr => {
+ for (let i = 0z; i < len(l); i += 1) {
+ expr_free(l[i]);
+ };
+ free(l);
+ },
+ m: match_expr => {
+ expr_free(m.value);
+ for (let i = 0z; i < len(m.cases); i += 1) {
+ free(m.cases[i].name);
+ type_free(m.cases[i]._type);
+ expr_free(m.cases[i].value);
+ };
+ free(m.cases);
+ },
+ o: offset_expr => abort(), // TODO
+ p: propagate_expr => expr_free(p: *expr),
+ r: return_expr => expr_free(r: *expr),
+ s: size_expr => type_free(s: *_type),
+ s: slice_expr => {
+ expr_free(s.object);
+ expr_free(s.start);
+ expr_free(s.end);
+ },
+ s: switch_expr => {
+ expr_free(s.value);
+ for (let i = 0z; i < len(s.cases); i += 1) {
+ let opts = s.cases[i].options;
+ for (let j = 0z; j < len(opts); j += 1) {
+ expr_free(opts[i]);
+ };
+ free(opts);
+ expr_free(s.cases[i].value);
+ };
+ free(s.cases);
+ },
+ u: unarithm_expr => expr_free(u.operand),
+ },
+};
diff --git a/hare/ast/ident.ha b/hare/ast/ident.ha
@@ -0,0 +1,29 @@
+// Identifies a single object, e.g. foo::bar::baz.
+export type ident = []str;
+
+// Maximum length of an identifier, as the sum of the lengths of its parts plus
+// one for each namespace deliniation.
+//
+// In other words, the length of "a::b::c" is 5.
+export def IDENT_MAX: size = 255;
+
+// Frees resources associated with an identifier.
+export fn ident_free(ident: ident) void = {
+ for (let i = 0z; i < len(ident); i += 1) {
+ free(ident[i]);
+ };
+ free(ident);
+};
+
+// Returns true if two idents are identical.
+export fn ident_eq(a: ident, b: ident) bool = {
+ if (len(a) != len(b)) {
+ return false;
+ };
+ for (let i = 0z; i < len(a); i += 1) {
+ if (a[i] != b[i]) {
+ return false;
+ };
+ };
+ return true;
+};
diff --git a/hare/ast/import.ha b/hare/ast/import.ha
@@ -0,0 +1,35 @@
+// use module;
+export type import_module = ident;
+
+// use alias = module;
+export type import_alias = struct {
+ ident: ident,
+ alias: str,
+};
+
+// use module::{foo, bar, baz};
+export type import_objects = struct {
+ ident: ident,
+ objects: []str,
+};
+
+// An imported module
+export type import = (import_module | import_alias | import_objects);
+
+// Frees resources associated with an import.
+export fn import_free(import: import) void = {
+ match (import) {
+ m: import_module => ident_free(m: ident),
+ a: import_alias => {
+ ident_free(a.ident);
+ free(a.alias);
+ },
+ o: import_objects => {
+ ident_free(o.ident);
+ for (let i = 0z; i < len(o.objects); i += 1) {
+ free(o.objects[i]);
+ };
+ free(o.objects);
+ },
+ };
+};
diff --git a/hare/ast/type.ha b/hare/ast/type.ha
@@ -0,0 +1,178 @@
+use hare::lex;
+
+// foo
+export type alias_type = struct {
+ unwrap: bool,
+ ident: ident,
+};
+
+// int, bool, ...
+export type builtin_type = enum {
+ BOOL, CHAR, F32, F64, FCONST, I16, I32, I64, I8, ICONST, INT, NULL,
+ RUNE, SIZE, STR, U16, U32, U64, U8, UINT, UINTPTR, VOID,
+};
+
+export type enum_field = struct {
+ name: str,
+ value: nullable *expr,
+};
+
+// enum { FOO = 0, BAR, ... }
+export type enum_type = struct {
+ storage: builtin_type, // Must be an integer type
+ values: []enum_field,
+};
+
+export type variadism = enum {
+ NONE,
+ C,
+ HARE,
+};
+
+export type func_attrs = enum uint {
+ NORETURN = 1 << 0,
+};
+
+export type func_param = struct {
+ loc: lex::location,
+ name: str,
+ _type: *_type,
+};
+
+// fn(foo: int, ..., baz: ...int) int
+export type func_type = struct {
+ result: *_type,
+ attrs: func_attrs,
+ variadism: variadism,
+ params: []func_param,
+};
+
+export type len_slice = void;
+export type len_unbounded = void;
+export type len_contextual = void;
+
+// []int, [*]int, [_]int, [foo]int
+export type list_type = struct {
+ length: (*expr | len_slice | len_unbounded | len_contextual),
+ members: *_type,
+};
+
+export type pointer_flags = enum uint {
+ NULLABLE = 1 << 0,
+};
+
+// *int
+export type pointer_type = struct {
+ referent: *_type,
+ flags: pointer_flags,
+};
+
+export type struct_field = struct {
+ name: str,
+ _type: *_type,
+};
+
+export type struct_embedded = *_type;
+
+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),
+};
+
+// (int | bool, ...)
+export type tagged_type = []*_type;
+
+// (int, bool, ...)
+export type tuple_type = []*_type;
+
+export type type_flags = enum uint {
+ CONST = 1 << 0,
+ ERROR = 1 << 1,
+};
+
+// A type
+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),
+};
+
+export fn type_free(t: (_type | nullable *_type)) void = match (t) {
+ t: nullable *_type => match (t) {
+ null => void,
+ t: *_type => {
+ type_free(*t);
+ free(t);
+ },
+ },
+ t: _type => match (t._type) {
+ a: alias_type => ident_free(a.ident),
+ builtin_type => void,
+ e: enum_type => {
+ for (let i = 0z; i < len(e.values); i += 1) {
+ free(e.values[i].name);
+ match (e.values[i].value) {
+ null => void,
+ v: *expr => expr_free(v),
+ };
+ };
+ free(e.values);
+ },
+ f: func_type => {
+ type_free(f.result);
+ for (let i = 0z; i < len(f.params); i += 1) {
+ free(f.params[i].name);
+ type_free(f.params[i]._type);
+ };
+ free(f.params);
+ },
+ l: list_type => {
+ match (l.length) {
+ e: *expr => {
+ expr_free(*e);
+ free(e);
+ },
+ * => void,
+ };
+ 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);
+ },
+ t: tagged_type => {
+ for (let i = 0z; i < len(t); i += 1) {
+ type_free(t[i]);
+ };
+ free(t);
+ },
+ t: tuple_type => {
+ for (let i = 0z; i < len(t); i += 1) {
+ type_free(t[i]);
+ };
+ free(t);
+ },
+ },
+};
diff --git a/hare/ast/types.ha b/hare/ast/types.ha
@@ -1,74 +0,0 @@
-// Identifies a single object, e.g. foo::bar::baz.
-export type ident = []str;
-
-// Maximum length of an identifier, as the sum of the lengths of its parts plus
-// one for each namespace deliniation.
-//
-// In other words, the length of "a::b::c" is 5.
-export def IDENT_MAX: size = 255;
-
-// Frees resources associated with an identifier.
-export fn ident_free(ident: ident) void = {
- for (let i = 0z; i < len(ident); i += 1) {
- free(ident[i]);
- };
- free(ident);
-};
-
-// Returns true if two idents are identical.
-export fn ident_eq(a: ident, b: ident) bool = {
- if (len(a) != len(b)) {
- return false;
- };
- for (let i = 0z; i < len(a); i += 1) {
- if (a[i] != b[i]) {
- return false;
- };
- };
- return true;
-};
-
-// A sub-unit, typically representing a single source file.
-export type subunit = struct {
- imports: []import,
- declarations: []declaration,
-};
-
-// use module;
-export type import_module = ident;
-
-// use alias = module;
-export type import_alias = struct {
- ident: ident,
- alias: str,
-};
-
-// use module::{foo, bar, baz};
-export type import_objects = struct {
- ident: ident,
- objects: []str,
-};
-
-// An imported module
-export type import = (import_module | import_alias | import_objects);
-
-// Frees resources associated with an import.
-export fn import_free(import: import) void = {
- match (import) {
- m: import_module => ident_free(m: ident),
- a: import_alias => {
- ident_free(a.ident);
- free(a.alias);
- },
- o: import_objects => {
- ident_free(o.ident);
- for (let i = 0z; i < len(o.objects); i += 1) {
- free(o.objects[i]);
- };
- free(o.objects);
- },
- };
-};
-
-// TODO
-export type declaration = void;
diff --git a/hare/ast/unit.ha b/hare/ast/unit.ha
@@ -0,0 +1,16 @@
+// A sub-unit, typically representing a single source file.
+export type subunit = struct {
+ imports: []import,
+ decls: []decl,
+};
+
+export fn subunit_free(u: subunit) void = {
+ for (let i = 0z; i < len(u.imports); i += 1) {
+ import_free(u.imports[i]);
+ };
+ free(u.imports);
+ for (let i = 0z; i < len(u.decls); i += 1) {
+ decl_free(u.decls[i]);
+ };
+ free(u.decls);
+};
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -244,9 +244,14 @@ getopt() {
hare_ast() {
printf '# hare::ast\n'
gen_srcs hare::ast \
- types.ha \
+ decl.ha \
+ expr.ha \
+ ident.ha \
+ import.ha \
+ type.ha \
+ unit.ha \
unparse.ha
- gen_ssa hare::ast io fmt strio
+ gen_ssa hare::ast hare::lex io fmt strio
}
gensrcs_hare_lex() {
diff --git a/stdlib.mk b/stdlib.mk
@@ -349,10 +349,15 @@ $(HARECACHE)/getopt/getopt.ssa: $(stdlib_getopt_srcs) $(stdlib_rt) $(stdlib_enco
# hare::ast
stdlib_hare_ast_srcs= \
- $(STDLIB)/hare/ast/types.ha \
+ $(STDLIB)/hare/ast/decl.ha \
+ $(STDLIB)/hare/ast/expr.ha \
+ $(STDLIB)/hare/ast/ident.ha \
+ $(STDLIB)/hare/ast/import.ha \
+ $(STDLIB)/hare/ast/type.ha \
+ $(STDLIB)/hare/ast/unit.ha \
$(STDLIB)/hare/ast/unparse.ha
-$(HARECACHE)/hare/ast/hare_ast.ssa: $(stdlib_hare_ast_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_fmt) $(stdlib_strio)
+$(HARECACHE)/hare/ast/hare_ast.ssa: $(stdlib_hare_ast_srcs) $(stdlib_rt) $(stdlib_hare_lex) $(stdlib_io) $(stdlib_fmt) $(stdlib_strio)
@printf 'HAREC \t$@\n'
@mkdir -p $(HARECACHE)/hare/ast
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nhare::ast \
@@ -972,10 +977,15 @@ $(TESTCACHE)/getopt/getopt.ssa: $(testlib_getopt_srcs) $(testlib_rt) $(testlib_e
# hare::ast
testlib_hare_ast_srcs= \
- $(STDLIB)/hare/ast/types.ha \
+ $(STDLIB)/hare/ast/decl.ha \
+ $(STDLIB)/hare/ast/expr.ha \
+ $(STDLIB)/hare/ast/ident.ha \
+ $(STDLIB)/hare/ast/import.ha \
+ $(STDLIB)/hare/ast/type.ha \
+ $(STDLIB)/hare/ast/unit.ha \
$(STDLIB)/hare/ast/unparse.ha
-$(TESTCACHE)/hare/ast/hare_ast.ssa: $(testlib_hare_ast_srcs) $(testlib_rt) $(testlib_io) $(testlib_fmt) $(testlib_strio)
+$(TESTCACHE)/hare/ast/hare_ast.ssa: $(testlib_hare_ast_srcs) $(testlib_rt) $(testlib_hare_lex) $(testlib_io) $(testlib_fmt) $(testlib_strio)
@printf 'HAREC \t$@\n'
@mkdir -p $(TESTCACHE)/hare/ast
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nhare::ast \