hare

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

commit f4d83fe36175b6aadafedabe7c74f5f6be7133d8
parent 92a64297a98c7e21400b6c2e811c61c36fdd49ec
Author: Eyal Sawady <ecs@d2evs.net>
Date:   Wed, 24 Mar 2021 01:30:00 -0400

hare::ast: flesh out

Diffstat:
Ahare/ast/decl.ha | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahare/ast/expr.ha | 404+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahare/ast/ident.ha | 29+++++++++++++++++++++++++++++
Ahare/ast/import.ha | 35+++++++++++++++++++++++++++++++++++
Ahare/ast/type.ha | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dhare/ast/types.ha | 74--------------------------------------------------------------------------
Ahare/ast/unit.ha | 16++++++++++++++++
Mscripts/gen-stdlib | 9+++++++--
Mstdlib.mk | 18++++++++++++++----
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 \