hare

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

commit 917f24dae32ad42b6d86848c0b2804cd26f150e0
parent 8db8f5319ee0408303e2082effca13f0f8683c46
Author: Drew DeVault <sir@cmpwn.com>
Date:   Fri,  3 Sep 2021 09:18:00 +0200

cmd/harec: implement object access expressions

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Mcmd/harec/context.ha | 10++++++++++
Mcmd/harec/gen.ha | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mcmd/harec/qbe.ha | 38++++++++++++++++++++++++++++++++++++++
Ahare/types/class.ha | 43+++++++++++++++++++++++++++++++++++++++++++
Mhare/unit/expr.ha | 29++++++++++++++++++++++++-----
Mhare/unit/process.ha | 50++++++++++++++++++++++++++++++++++++--------------
Mhare/unit/scope.ha | 36+++++++++++++++++++++++++++++-------
Mscripts/gen-stdlib | 1+
Mstdlib.mk | 2++
9 files changed, 243 insertions(+), 34 deletions(-)

diff --git a/cmd/harec/context.ha b/cmd/harec/context.ha @@ -18,3 +18,13 @@ type binding = struct { object: *unit::object, name: temporary, }; + +fn binding_lookup(ctx: *context, object: *unit::object) *binding = { + // XXX: We could use a hash map here + for (let i = 0z; i < len(ctx.bindings); i += 1) { + if (ctx.bindings[i].object == object) { + return &ctx.bindings[i]; + }; + }; + abort(); +}; diff --git a/cmd/harec/gen.ha b/cmd/harec/gen.ha @@ -6,6 +6,7 @@ use hare::module; use hare::types; use hare::types::{builtin}; use hare::unit; +use hare::unit::{object_kind}; use io; use os; use strings; @@ -95,12 +96,34 @@ fn gen_store(ctx: *context, object: value, value: value) void = { emit(ctx.out, void, instr, value, object); }; +fn gen_load(ctx: *context, object: value) value = { + match (types::dealias(object._type).repr) { + bi: types::builtin => switch (bi) { + builtin::STR => abort(), // TODO + * => void, + }, + types::_struct => abort(), // TODO + (types::array | types::slice | types::tagged | types::tuple) => + abort(), // TODO + * => void, + }; + const instr = qinstr_load(ctx, object._type); + const temp = mktemp(ctx); + const ty = qtype_lookup(ctx, object._type, false); + emit(ctx.out, (ty, temp), instr, object); + return value { + value = temp, + _type = object._type, + }; +}; + fn gen_expr(ctx: *context, expr: *unit::expr) value = { return match (expr.expr) { - unit::binding_expr => gen_binding(ctx, expr), - unit::compound_expr => gen_compound(ctx, expr), - unit::constant_expr => gen_const(ctx, expr), - unit::return_expr => gen_return(ctx, expr), + unit::access => gen_access(ctx, expr), + unit::bindings => gen_binding(ctx, expr), + unit::compound => gen_compound(ctx, expr), + unit::constant => gen_const(ctx, expr), + unit::_return => gen_return(ctx, expr), }; }; @@ -113,8 +136,37 @@ fn gen_expr_at(ctx: *context, expr: *unit::expr, at: value) void = { gen_store(ctx, at, value); }; +fn gen_access_object(ctx: *context, object: *unit::object) value = { + return switch (object.kind) { + object_kind::CONST, object_kind::TYPE => abort(), + object_kind::BIND => { + const binding = binding_lookup(ctx, object); + yield value { + value = binding.name, + _type = object._type, + }; + }, + object_kind::DECL => abort(), // TODO + }; +}; + +fn gen_access_addr(ctx: *context, expr: *unit::expr) value = { + const access = expr.expr as unit::access; + return match (access) { + ao: unit::access_object => gen_access_object(ctx, ao), + ai: unit::access_index => abort(), // TODO + af: unit::access_field => abort(), // TODO + at: unit::access_tuple => abort(), // TODO + }; +}; + +fn gen_access(ctx: *context, expr: *unit::expr) value = { + const addr = gen_access_addr(ctx, expr); + return gen_load(ctx, addr); +}; + fn gen_binding(ctx: *context, expr: *unit::expr) value = { - const bindings = expr.expr as unit::binding_expr; + const bindings = expr.expr as unit::bindings; for (let i = 0z; i < len(bindings); i += 1) { const item = bindings[i]; const temp = mktemp(ctx); @@ -137,7 +189,7 @@ fn gen_binding(ctx: *context, expr: *unit::expr) value = { }; fn gen_compound(ctx: *context, expr: *unit::expr) value = { - const compound = expr.expr as unit::compound_expr; + const compound = expr.expr as unit::compound; for (let i = 0z; i < len(compound); i += 1) { gen_expr(ctx, compound[i]); }; @@ -145,7 +197,7 @@ fn gen_compound(ctx: *context, expr: *unit::expr) value = { }; fn gen_const(ctx: *context, expr: *unit::expr) value = { - const constexpr = expr.expr as unit::constant_expr; + const constexpr = expr.expr as unit::constant; const val: qval = match (constexpr) { void => return vvoid, b: bool => (if (b) 1u32 else 0u32): constant, @@ -163,7 +215,7 @@ fn gen_const(ctx: *context, expr: *unit::expr) value = { }; fn gen_return(ctx: *context, expr: *unit::expr) value = { - const rexpr = expr.expr as unit::return_expr; + const rexpr = expr.expr as unit::_return; match (rexpr) { null => emit(ctx.out, void, qinstr::RET), expr: *unit::expr => { diff --git a/cmd/harec/qbe.ha b/cmd/harec/qbe.ha @@ -185,6 +185,44 @@ fn qinstr_store( * => abort(), }; +fn qinstr_load( + ctx: *context, + ty: *types::_type, +) qinstr = match (ty.repr) { + al: types::alias => qinstr_load(ctx, al.secondary: *types::_type), + bi: types::builtin => switch (bi) { + builtin::STR, builtin::VOID => abort(), + builtin::F32 => qinstr::LOADS, + builtin::F64 => qinstr::LOADD, + * => switch (ty.sz) { + 1 => if (types::is_signed(ty)) { + yield qinstr::LOADSB; + } else { + yield qinstr::LOADUB; + }, + 2 => if (types::is_signed(ty)) { + yield qinstr::LOADSH; + } else { + yield qinstr::LOADUH; + }, + 4 => if (types::is_signed(ty)) { + yield qinstr::LOADSW; + } else { + yield qinstr::LOADUW; + }, + 8 => qinstr::LOADL, + * => abort(), + }, + }, + types::pointer => if (ctx.arch.ptr == &qlong) { + yield qinstr::LOADL; + } else if (ctx.arch.ptr == &qword) { + yield qinstr::LOADUW; + } else abort(), + en: types::_enum => abort(), // TODO + * => abort(), +}; + type qinstr = enum { ADD, ALLOC16, diff --git a/hare/types/class.ha b/hare/types/class.ha @@ -0,0 +1,43 @@ +// Returns true if the given type is a signed type. +export fn is_signed(ty: const *_type) bool = { + return match (ty.repr) { + bi: builtin => switch (bi) { + builtin::F32, builtin::F64, + builtin::I16, builtin::I32, builtin::I64, builtin::I8, + builtin::INT => true, + * => false, + }, + e: _enum => switch (e.storage) { + builtin::I16, builtin::I32, builtin::I64, builtin::I8, + builtin::INT => true, + * => false, + }, + * => false, + }; +}; + +// Returns true if the given type is a floating-point type. +export fn is_float(ty: const *_type) bool = { + return match (ty.repr) { + bi: builtin => switch (bi) { + builtin::F32, builtin::F64 => true, + * => false, + }, + * => false, + }; +}; + +// Returns true if the given type is an integer type. +export fn is_integer(ty: const *_type) bool = { + return match (ty.repr) { + bi: builtin => switch (bi) { + builtin::INT, builtin::UINT, + builtin::I16, builtin::I32, builtin::I64, builtin::I8, + builtin::U16, builtin::U32, builtin::U64, builtin::U8, + builtin::CHAR, builtin::SIZE, builtin::UINTPTR => true, + * => false, + }, + _enum => true, + * => false, + }; +}; diff --git a/hare/unit/expr.ha b/hare/unit/expr.ha @@ -8,7 +8,26 @@ export type expr = struct { end: lex::location, result: const *types::_type, terminates: bool, - expr: (binding_expr | compound_expr | constant_expr | return_expr), + expr: (access | bindings | compound | constant | _return), +}; + +export type access = (access_object | access_field | access_index | access_tuple); + +export type access_object = *object; + +export type access_field = struct { + object: *expr, + field: const *types::struct_field, +}; + +export type access_index = struct { + object: *expr, + index: *expr, +}; + +export type access_tuple = struct { + object: *expr, + value: const *types::tuple_value, }; // A single variable biding. @@ -20,13 +39,13 @@ export type binding = struct { }; // A list of variable bindings. -export type binding_expr = []binding; +export type bindings = []binding; // A compound expression, i.e. { ... } -export type compound_expr = []*expr; +export type compound = []*expr; // The value of a constant expression. -export type constant_expr = ast::value; // TODO: composite types +export type constant = ast::value; // TODO: composite types // A return expression, i.e. return <value> -export type return_expr = nullable *expr; +export type _return = nullable *expr; diff --git a/hare/unit/process.ha b/hare/unit/process.ha @@ -73,7 +73,7 @@ fn process_expr( ctx: *context, expr: *ast::expr, ) (*expr | error) = match (expr.expr) { - ast::access_expr => abort(), // TODO + ast::access_expr => process_access(ctx, expr), ast::alloc_expr => abort(), // TODO ast::append_expr => abort(), // TODO ast::assert_expr => abort(), // TODO @@ -102,11 +102,33 @@ fn process_expr( ast::unarithm_expr => abort(), // TODO }; +fn process_access(ctx: *context, aexpr: *ast::expr) (*expr | error) = { + const access = aexpr.expr as ast::access_expr; + const op: (const *types::_type, access) = match (access) { + ai: ast::access_identifier => { + const object = match (ctx_lookup(ctx, ai)) { + null => abort(), // TODO: Error + obj: *object => obj, + }; + yield (object._type, object); + }, + ai: ast::access_index => abort(), // TODO + af: ast::access_field => abort(), // TODO + at: ast::access_tuple => abort(), // TODO + }; + return alloc(expr { + start = aexpr.start, + end = aexpr.end, + result = op.0, + expr = op.1, + }); +}; + fn process_binding(ctx: *context, aexpr: *ast::expr) (*expr | error) = { const bind = aexpr.expr as ast::binding_expr; assert(!bind.is_static && !bind.is_const); // TODO - let bindings: binding_expr = []; + let bindings: bindings = []; for (let i = 0z; i < len(bind.bindings); i += 1) { const item = bind.bindings[i]; const init = process_expr(ctx, item.init)?; @@ -139,7 +161,7 @@ fn process_compound(ctx: *context, aexpr: *ast::expr) (*expr | error) = { const compound = aexpr.expr as ast::compound_expr; const scope = scope_push(ctx, scope_class::COMPOUND); - let exprs: compound_expr = alloc([], len(compound.exprs)); + let exprs: compound = alloc([], len(compound.exprs)); let i = 0z; for (i < len(compound.exprs); i += 1) { append(exprs, process_expr(ctx, compound.exprs[i])?); @@ -163,7 +185,7 @@ fn process_compound(ctx: *context, aexpr: *ast::expr) (*expr | error) = { defer ast::expr_free(aexpr); const expr = process_compound(&ctx, aexpr)!; assert(expr.result.repr as types::builtin == types::builtin::VOID); - const compound = expr.expr as compound_expr; + const compound = expr.expr as compound; assert(len(compound) == 3); const aexpr = parse_expr("{ return; }"); @@ -177,7 +199,7 @@ fn process_compound(ctx: *context, aexpr: *ast::expr) (*expr | error) = { fn process_constant(ctx: *context, aexpr: *ast::expr) (*expr | error) = { const constexpr = aexpr.expr as ast::constant_expr; // TODO: Tuple unpacking - const er: (const *types::_type, constant_expr) = match (constexpr) { + const er: (const *types::_type, constant) = match (constexpr) { v: ast::value => ( // TODO: iconst/fconst lowering types::lookup_builtin(ctx.store, match (v) { @@ -212,28 +234,28 @@ fn process_constant(ctx: *context, aexpr: *ast::expr) (*expr | error) = { defer ast::expr_free(aexpr); const expr = process_constant(&ctx, aexpr)!; assert(expr.result.repr as types::builtin == types::builtin::VOID); - const constexpr = expr.expr as constant_expr; + const constexpr = expr.expr as constant; assert(constexpr is void); const aexpr = parse_expr("true"); defer ast::expr_free(aexpr); const expr = process_constant(&ctx, aexpr)!; assert(expr.result.repr as types::builtin == types::builtin::BOOL); - const constexpr = expr.expr as constant_expr; + const constexpr = expr.expr as constant; assert(constexpr as bool == true); const aexpr = parse_expr("false"); defer ast::expr_free(aexpr); const expr = process_constant(&ctx, aexpr)!; assert(expr.result.repr as types::builtin == types::builtin::BOOL); - const constexpr = expr.expr as constant_expr; + const constexpr = expr.expr as constant; assert(constexpr as bool == false); const aexpr = parse_expr("null"); defer ast::expr_free(aexpr); const expr = process_constant(&ctx, aexpr)!; assert(expr.result.repr as types::builtin == types::builtin::NULL); - assert(expr.expr is constant_expr); + assert(expr.expr is constant); const cases: [_](str, types::builtin, ast::value) = [ ("1234", types::builtin::INT, 1234), @@ -248,7 +270,7 @@ fn process_constant(ctx: *context, aexpr: *ast::expr) (*expr | error) = { defer ast::expr_free(aexpr); const expr = process_constant(&ctx, aexpr)!; assert(expr.result.repr as types::builtin == case.1); - const constexpr = expr.expr as constant_expr; + const constexpr = expr.expr as constant; match (case.2) { s: str => assert(constexpr as str == s), r: rune => assert(constexpr as rune == r), @@ -272,7 +294,7 @@ fn process_return(ctx: *context, aexpr: *ast::expr) (*expr | error) = { end = aexpr.end, terminates = true, result = &types::builtin_void, - expr = rval: return_expr, + expr = rval: _return, }); }; @@ -284,7 +306,7 @@ fn process_return(ctx: *context, aexpr: *ast::expr) (*expr | error) = { const expr = process_return(&ctx, aexpr)!; assert(expr.terminates); assert(expr.result.repr as types::builtin == types::builtin::VOID); - const rval = expr.expr as return_expr; + const rval = expr.expr as _return; assert(rval == null); const aexpr = parse_expr("return 10;"); @@ -292,7 +314,7 @@ fn process_return(ctx: *context, aexpr: *ast::expr) (*expr | error) = { const expr = process_return(&ctx, aexpr)!; assert(expr.terminates); assert(expr.result.repr as types::builtin == types::builtin::VOID); - const rval = expr.expr as return_expr; + const rval = expr.expr as _return; assert(rval != null); - assert((rval: *expr).expr is constant_expr); + assert((rval: *expr).expr is constant); }; diff --git a/hare/unit/scope.ha b/hare/unit/scope.ha @@ -59,19 +59,41 @@ fn scope_pop(ctx: *context) *scope = { return scope; }; -fn scope_insert(ctx: *context, obj: object) *object = { - const scope = ctx.scope; - append(scope.objects, obj); - let obj = &scope.objects[len(scope.objects) - 1]; +fn ident_hash(ident: ast::ident) u64 = { let hash = fnv::fnv64a(); defer hash::close(hash); const zerobuf = [0u8]; - for (let i = len(obj.ident); i > 0; i -= 1) { - hash::write(hash, strings::toutf8(obj.ident[i - 1])); + for (let i = len(ident); i > 0; i -= 1) { + hash::write(hash, strings::toutf8(ident[i - 1])); hash::write(hash, zerobuf[..]); }; - let hash = fnv::sum64(hash); + return fnv::sum64(hash); +}; + +fn scope_insert(ctx: *context, obj: object) *object = { + const scope = ctx.scope; + append(scope.objects, obj); + let obj = &scope.objects[len(scope.objects) - 1]; + const hash = ident_hash(obj.ident); obj.hash = hash; append(scope.hashmap[hash: size % SCOPE_BUCKETS], obj); return obj; }; + +fn ctx_lookup(ctx: *context, ident: ast::ident) nullable *object = + scope_lookup(ctx.scope, ident); + +fn scope_lookup(scope: *scope, ident: ast::ident) nullable *object = { + const hash = ident_hash(ident); + const bucket = scope.hashmap[hash: size % SCOPE_BUCKETS]; + for (let i = 0z; i < len(bucket); i += 1) { + if (ast::ident_eq(bucket[i].name, ident) + || ast::ident_eq(bucket[i].ident, ident)) { + return bucket[i]; + }; + }; + return match (scope.parent) { + null => null, + scope: *scope => scope_lookup(scope, ident), + }; +}; diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -363,6 +363,7 @@ gensrcs_hare_types() { gen_srcs hare::types \ arch.ha \ builtins.ha \ + class.ha \ hash.ha \ lookup.ha \ store.ha \ diff --git a/stdlib.mk b/stdlib.mk @@ -684,6 +684,7 @@ $(HARECACHE)/hare/parse/hare_parse.ssa: $(stdlib_hare_parse_srcs) $(stdlib_rt) $ stdlib_hare_types_srcs= \ $(STDLIB)/hare/types/arch.ha \ $(STDLIB)/hare/types/builtins.ha \ + $(STDLIB)/hare/types/class.ha \ $(STDLIB)/hare/types/hash.ha \ $(STDLIB)/hare/types/lookup.ha \ $(STDLIB)/hare/types/store.ha \ @@ -1912,6 +1913,7 @@ $(TESTCACHE)/hare/parse/hare_parse.ssa: $(testlib_hare_parse_srcs) $(testlib_rt) testlib_hare_types_srcs= \ $(STDLIB)/hare/types/arch.ha \ $(STDLIB)/hare/types/builtins.ha \ + $(STDLIB)/hare/types/class.ha \ $(STDLIB)/hare/types/hash.ha \ $(STDLIB)/hare/types/lookup.ha \ $(STDLIB)/hare/types/store.ha \