diff --git a/lang/README.md b/lang/README.md index 57a1ad87..153b2477 100644 --- a/lang/README.md +++ b/lang/README.md @@ -51,6 +51,7 @@ main := fn(): uint { } ``` + #### floating_point_arithmetic ```hb main := fn(): f32 { @@ -113,6 +114,21 @@ main := fn(): uint { } ``` +#### hex_octal_binary_literals +```hb +main := fn(): uint { + hex := 0xFF + decimal := 255 + octal := 0o377 + binary := 0b11111111 + + if hex == decimal & octal == decimal & binary == decimal { + return 0 + } + return 1 +} +``` + #### loops ```hb main := fn(): uint { @@ -158,6 +174,60 @@ drop := fn(a: uint): void { } ``` +#### structs +```hb +Ty := struct { + // comment + + a: uint, +} + +Ty2 := struct { + ty: Ty, + c: uint, +} + +useless := struct {} + +main := fn(): uint { + // `packed` structs have no padding (all fields are alighred to 1) + if @sizeof(packed struct {a: u8, b: u16}) != 3 { + return 9001 + } + + finst := Ty2.{ty: .{a: 4}, c: 3} + inst := odher_pass(finst) + if inst.c == 3 { + return pass(&inst.ty) + } + return 0 +} + +pass := fn(t: ^Ty): uint { + return t.a +} + +odher_pass := fn(t: Ty2): Ty2 { + return t +} +``` + +#### enums +```hb +Enum := enum {A, B, C} + +some_enum := fn(): Enum return .A + +main := fn(): uint { + e := some_enum() + + match e { + .A => return 0, + _ => return 100, + } +} +``` + #### nullable_types ```hb main := fn(): uint { @@ -215,74 +285,6 @@ new_bar := fn(a: ?^uint): ?Bar return .(a, 1) decide := fn(): bool return !false ``` -#### structs -```hb -Ty := struct { - // comment - - a: uint, -} - -Ty2 := struct { - ty: Ty, - c: uint, -} - -useless := struct {} - -main := fn(): uint { - // `packed` structs have no padding (all fields are alighred to 1) - if @sizeof(packed struct {a: u8, b: u16}) != 3 { - return 9001 - } - - finst := Ty2.{ty: .{a: 4}, c: 3} - inst := odher_pass(finst) - if inst.c == 3 { - return pass(&inst.ty) - } - return 0 -} - -pass := fn(t: ^Ty): uint { - return t.a -} - -odher_pass := fn(t: Ty2): Ty2 { - return t -} -``` - -#### hex_octal_binary_literals -```hb -main := fn(): uint { - hex := 0xFF - decimal := 255 - octal := 0o377 - binary := 0b11111111 - - if hex == decimal & octal == decimal & binary == decimal { - return 0 - } - return 1 -} -``` - -#### wrong_dead_code_elimination -```hb -Color := struct {b: u8} -main := fn(): void { - color := Color.(0) - n := @as(u8, 1) - loop { - if color.b == 255 | color.b == 0 { - n = -n - } - color.b += n - } -} -``` - #### inline_return_stack ```hb $fun := fn(): [uint; 3] { @@ -649,6 +651,21 @@ main := fn(): uint { ### Purely Testing Examples +#### wrong_dead_code_elimination +```hb +Color := struct {b: u8} +main := fn(): void { + color := Color.(0) + n := @as(u8, 1) + loop { + if color.b == 255 | color.b == 0 { + n = -n + } + color.b += n + } +} +``` + #### different_function_destinations ```hb Stru := struct {a: uint, b: uint} diff --git a/lang/src/fmt.rs b/lang/src/fmt.rs index 08e6ef66..54574e72 100644 --- a/lang/src/fmt.rs +++ b/lang/src/fmt.rs @@ -1,7 +1,7 @@ use { crate::{ lexer::{self, Lexer, TokenKind}, - parser::{self, CommentOr, CtorField, Expr, Poser, Radix, StructField}, + parser::{self, CommentOr, CtorField, EnumField, Expr, Poser, Radix, StructField}, }, core::fmt::{self}, }; @@ -28,50 +28,44 @@ pub fn display_radix(radix: Radix, mut value: u64, buf: &mut [u8; 64]) -> &str { #[repr(u8)] enum TokenGroup { - Blank = 0, - Comment = 1, - Keyword = 2, - Identifier = 3, - Directive = 4, - Number = 5, - String = 6, - Op = 7, - Assign = 8, - Paren = 9, - Bracket = 10, - Colon = 11, - Comma = 12, - Dot = 13, - Ctor = 14, + Blank, + Comment, + Keyword, + Identifier, + Directive, + Number, + String, + Op, + Assign, + Paren, + Bracket, + Colon, + Comma, + Dot, + Ctor, } fn token_group(kind: TokenKind) -> TokenGroup { - use crate::lexer::TokenKind::*; + use {crate::lexer::TokenKind::*, TokenGroup as TG}; match kind { - // unused/unimplemented - | BSlash | Pound | Eof | Ct => TokenGroup::Blank, - | Comment => TokenGroup::Comment, - | Directive => TokenGroup::Directive, - | Colon => TokenGroup::Colon, - | Semi | Comma => TokenGroup::Comma, - | Dot => TokenGroup::Dot, - | Ctor | Tupl => TokenGroup::Ctor, - | LParen | RParen => TokenGroup::Paren, - | LBrace | RBrace | LBrack | RBrack => TokenGroup::Bracket, - | Number | Float => TokenGroup::Number, - | Under | CtIdent | Ident => TokenGroup::Identifier, - | Tick | Tilde | Que - | Not | Mod | Band | Bor | Xor - | Mul | Add | Sub | Div - | Shl | Shr | Or | And - | Lt | Gt | Eq | Le | Ge | Ne => TokenGroup::Op, - | Decl | Assign - | BorAss | XorAss | BandAss - | AddAss | SubAss | MulAss | DivAss | ModAss - | ShrAss | ShlAss => TokenGroup::Assign, - | DQuote | Quote => TokenGroup::String, - | Return | If | Else | Loop | Break | Continue | Fn | Idk | Die - | Struct | Packed | True | False | Null => TokenGroup::Keyword, + BSlash | Pound | Eof | Ct => TG::Blank, + Comment => TG::Comment, + Directive => TG::Directive, + Colon => TG::Colon, + Semi | Comma => TG::Comma, + Dot => TG::Dot, + Ctor | Tupl | TArrow => TG::Ctor, + LParen | RParen => TG::Paren, + LBrace | RBrace | LBrack | RBrack => TG::Bracket, + Number | Float => TG::Number, + Under | CtIdent | Ident => TG::Identifier, + Tick | Tilde | Que | Not | Mod | Band | Bor | Xor | Mul | Add | Sub | Div | Shl | Shr + | Or | And | Lt | Gt | Eq | Le | Ge | Ne => TG::Op, + Decl | Assign | BorAss | XorAss | BandAss | AddAss | SubAss | MulAss | DivAss | ModAss + | ShrAss | ShlAss => TG::Assign, + DQuote | Quote => TG::String, + Return | If | Else | Loop | Break | Continue | Fn | Idk | Die | Struct | Packed | True + | False | Null | Match | Enum => TG::Keyword, } } @@ -295,7 +289,7 @@ impl<'a> Formatter<'a> { f.write_str("packed ")?; } - write!(f, "struct {{")?; + f.write_str("struct {")?; self.fmt_list_low(f, trailing_comma, "}", ",", fields, |s, field, f| { match field { CommentOr::Or(StructField { name, ty, .. }) => { @@ -311,6 +305,21 @@ impl<'a> Formatter<'a> { Ok(field.or().is_some()) }) } + Expr::Enum { variants, trailing_comma, .. } => { + f.write_str("enum {")?; + self.fmt_list_low(f, trailing_comma, "}", ",", variants, |_, var, f| { + match var { + CommentOr::Or(EnumField { name, .. }) => { + f.write_str(name)?; + } + CommentOr::Comment { literal, .. } => { + f.write_str(literal)?; + f.write_str("\n")?; + } + } + Ok(var.or().is_some()) + }) + } Expr::Ctor { ty, fields, trailing_comma, .. } => { if let Some(ty) = ty { self.fmt_paren(ty, f, unary)?; @@ -385,6 +394,16 @@ impl<'a> Formatter<'a> { } Ok(()) } + Expr::Match { value, branches, .. } => { + f.write_str("match ")?; + self.fmt(value, f)?; + f.write_str(" {")?; + self.fmt_list(f, true, "}", ",", branches, |s, br, f| { + s.fmt(&br.pat, f)?; + f.write_str(" => ")?; + s.fmt(&br.body, f) + }) + } Expr::Loop { body, .. } => { f.write_str("loop ")?; self.fmt(body, f) diff --git a/lang/src/lexer.rs b/lang/src/lexer.rs index 002d4fda..ee4e04d9 100644 --- a/lang/src/lexer.rs +++ b/lang/src/lexer.rs @@ -121,23 +121,9 @@ pub enum TokenKind { Ct, - Return, - If, - Else, - Loop, - Break, - Continue, - Fn, - Struct, - Packed, - True, - False, - Null, - Idk, - Die, - Ctor, Tupl, + TArrow, Or, And, @@ -147,8 +133,26 @@ pub enum TokenKind { BSlash = b'\\', RBrack = b']', Xor = b'^', - Tick = b'`', Under = b'_', + Tick = b'`', + + Return, + If, + Match, + Else, + Loop, + Break, + Continue, + Fn, + Struct, + Packed, + Enum, + True, + False, + Null, + Idk, + Die, + // Unused = a-z LBrace = b'{', Bor = b'|', @@ -300,6 +304,7 @@ gen_token_kind! { #[keywords] Return = b"return", If = b"if", + Match = b"match", Else = b"else", Loop = b"loop", Break = b"break", @@ -307,6 +312,7 @@ gen_token_kind! { Fn = b"fn", Struct = b"struct", Packed = b"packed", + Enum = b"enum", True = b"true", False = b"false", Null = b"null", @@ -316,6 +322,7 @@ gen_token_kind! { #[punkt] Ctor = ".{", Tupl = ".(", + TArrow = "=>", // #define OP: each `#[prec]` delimeters a level of precedence from lowest to highest #[ops] #[prec] @@ -514,6 +521,7 @@ impl<'a> Lexer<'a> { } b'.' if self.advance_if(b'{') => T::Ctor, b'.' if self.advance_if(b'(') => T::Tupl, + b'=' if self.advance_if(b'>') => T::TArrow, b'&' if self.advance_if(b'&') => T::And, b'|' if self.advance_if(b'|') => T::Or, b'$' if self.advance_if(b':') => T::Ct, diff --git a/lang/src/lib.rs b/lang/src/lib.rs index 5d13c4fd..971701d2 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -352,6 +352,11 @@ pub mod ty { debug_assert_ne!(st.pos, Pos::MAX); crate::SymKey::Struct(st.file, st.pos, st.captures) } + Kind::Enum(e) => { + let en = &ctx.enums[e]; + debug_assert_ne!(en.pos, Pos::MAX); + crate::SymKey::Enum(en.file, en.pos) + } Kind::Ptr(p) => crate::SymKey::Pointer(&ctx.ptrs[p]), Kind::Opt(p) => crate::SymKey::Optional(&ctx.opts[p]), Kind::Func(f) => { @@ -485,7 +490,7 @@ pub mod ty { { Loc::Reg } - Kind::Ptr(_) | Kind::Builtin(_) => Loc::Reg, + Kind::Ptr(_) | Kind::Enum(_) | Kind::Builtin(_) => Loc::Reg, Kind::Struct(_) if tys.size_of(*self) == 0 => Loc::Reg, Kind::Struct(_) | Kind::Slice(_) | Kind::Opt(_) => Loc::Stack, Kind::Func(_) | Kind::Global(_) | Kind::Module(_) | Kind::Const(_) => { @@ -640,6 +645,7 @@ pub mod ty { pub enum Kind { Builtin, Struct, + Enum, Ptr, Slice, Opt, @@ -720,7 +726,7 @@ pub mod ty { f.write_str("[")?; idx.fmt(f)?; f.write_str("]{")?; - for (i, &super::Field { name, ty }) in + for (i, &super::StructField { name, ty }) in self.tys.struct_fields(idx).iter().enumerate() { if i != 0 { @@ -736,6 +742,12 @@ pub mod ty { f.write_str(file.ident_str(record.name)) } } + TK::Enum(idx) => { + let enm = &self.tys.ins.enums[idx]; + debug_assert!(!enm.name.is_null()); + let file = &self.files[enm.file.index()]; + f.write_str(file.ident_str(enm.name)) + } TK::Func(idx) => { f.write_str("fn")?; idx.fmt(f) @@ -773,6 +785,7 @@ pub enum SymKey<'a> { Pointer(&'a Ptr), Optional(&'a Opt), Struct(Module, Pos, ty::Tuple), + Enum(Module, Pos), FuncInst(ty::Func, ty::Tuple), Decl(Module, Ident), Array(&'a Array), @@ -852,7 +865,19 @@ impl Reloc { } } -struct Field { +struct EnumField { + name: Ident, +} + +#[derive(Default)] +struct Enum { + name: Ident, + pos: Pos, + file: Module, + field_start: u32, +} + +struct StructField { name: Ident, ty: ty::Id, } @@ -955,18 +980,21 @@ impl IdentInterner { #[derive(Default)] struct TypesTmp { - fields: Vec, + struct_fields: Vec, + enum_fields: Vec, args: Vec, } #[derive(Default)] pub struct TypeIns { args: Vec, - fields: Vec, + struct_fields: Vec, + enum_fields: Vec, funcs: EntVec, globals: EntVec, consts: EntVec, structs: EntVec, + enums: EntVec, ptrs: EntVec, opts: EntVec, slices: EntVec, @@ -1149,11 +1177,11 @@ trait TypeParser { return ty; } - let prev_tmp = self.tys().tmp.fields.len(); + let prev_tmp = self.tys().tmp.struct_fields.len(); for field in fields.iter().filter_map(CommentOr::or) { let ty = self.parse_ty(file, &field.ty, None, files); - let field = Field { name: self.tys().names.intern(field.name), ty }; - self.tys().tmp.fields.push(field); + let field = StructField { name: self.tys().names.intern(field.name), ty }; + self.tys().tmp.struct_fields.push(field); } let tys = self.tys(); @@ -1164,13 +1192,43 @@ trait TypeParser { file, pos, name: name.unwrap_or_default(), - field_start: tys.ins.fields.len() as _, + field_start: tys.ins.struct_fields.len() as _, explicit_alignment: packed.then_some(1), ..Default::default() }) .into(); - tys.ins.fields.extend(tys.tmp.fields.drain(prev_tmp..)); + tys.ins.struct_fields.extend(tys.tmp.struct_fields.drain(prev_tmp..)); + + tys.syms.insert(sym, ty, &tys.ins); + ty + } + Expr::Enum { pos, variants, .. } => { + let sym = SymKey::Enum(file, pos); + let tys = self.tys(); + if let Some(&ty) = tys.syms.get(sym, &tys.ins) { + return ty; + } + + let prev_tmp = self.tys().tmp.enum_fields.len(); + for field in variants.iter().filter_map(CommentOr::or) { + let field = EnumField { name: self.tys().names.intern(field.name) }; + self.tys().tmp.enum_fields.push(field); + } + + let tys = self.tys(); + let ty = tys + .ins + .enums + .push(Enum { + file, + pos, + name: name.unwrap_or_default(), + field_start: tys.ins.enum_fields.len() as _, + }) + .into(); + + tys.ins.enum_fields.extend(tys.tmp.enum_fields.drain(prev_tmp..)); tys.syms.insert(sym, ty, &tys.ins); ty @@ -1211,13 +1269,6 @@ trait TypeParser { } impl Types { - fn struct_field_range(&self, strct: ty::Struct) -> Range { - let start = self.ins.structs[strct].field_start as usize; - let end = - self.ins.structs.next(strct).map_or(self.ins.fields.len(), |s| s.field_start as usize); - start..end - } - fn pack_args(&mut self, arg_base: usize) -> Option { let base = self.ins.args.len(); self.ins.args.extend(self.tmp.args.drain(arg_base..)); @@ -1233,8 +1284,29 @@ impl Types { ty::Tuple::new(sp, len) } - fn struct_fields(&self, strct: ty::Struct) -> &[Field] { - &self.ins.fields[self.struct_field_range(strct)] + fn struct_fields(&self, strct: ty::Struct) -> &[StructField] { + &self.ins.struct_fields[self.struct_field_range(strct)] + } + + fn struct_field_range(&self, strct: ty::Struct) -> Range { + let start = self.ins.structs[strct].field_start as usize; + let end = self + .ins + .structs + .next(strct) + .map_or(self.ins.struct_fields.len(), |s| s.field_start as usize); + start..end + } + + fn enum_fields(&self, enm: ty::Enum) -> &[EnumField] { + &self.ins.enum_fields[self.enum_field_range(enm)] + } + + fn enum_field_range(&self, enm: ty::Enum) -> Range { + let start = self.ins.enums[enm].field_start as usize; + let end = + self.ins.enums.next(enm).map_or(self.ins.enum_fields.len(), |s| s.field_start as usize); + start..end } fn parama(&self, ret: impl Into) -> (Option, ParamAlloc) { @@ -1285,6 +1357,7 @@ impl Types { self.ins.structs[stru].size.set(oiter.offset); oiter.offset } + ty::Kind::Enum(enm) => (self.enum_field_range(enm).len().ilog2() + 7) / 8, ty::Kind::Opt(opt) => { let base = self.ins.opts[opt].base; if self.nieche_of(base).is_some() { @@ -1308,7 +1381,7 @@ impl Types { || { self.struct_fields(stru) .iter() - .map(|&Field { ty, .. }| self.align_of(ty)) + .map(|&StructField { ty, .. }| self.align_of(ty)) .max() .unwrap_or(1) }, @@ -1380,11 +1453,11 @@ impl Types { self.ins.args.clear(); self.ins.globals.clear(); self.ins.structs.clear(); - self.ins.fields.clear(); + self.ins.struct_fields.clear(); self.ins.ptrs.clear(); self.ins.slices.clear(); - debug_assert_eq!(self.tmp.fields.len(), 0); + debug_assert_eq!(self.tmp.struct_fields.len(), 0); debug_assert_eq!(self.tmp.args.len(), 0); debug_assert_eq!(self.tasks.len(), 0); @@ -1416,9 +1489,9 @@ impl OffsetIter { .map(|(f, off)| (off, f.ty)) } - fn next<'a>(&mut self, tys: &'a Types) -> Option<(&'a Field, Offset)> { + fn next<'a>(&mut self, tys: &'a Types) -> Option<(&'a StructField, Offset)> { let stru = &tys.ins.structs[self.strct]; - let field = &tys.ins.fields[self.fields.next()?]; + let field = &tys.ins.struct_fields[self.fields.next()?]; let align = stru.explicit_alignment.map_or_else(|| tys.align_of(field.ty), |a| a as u32); self.offset = (self.offset + align - 1) & !(align - 1); @@ -1433,7 +1506,7 @@ impl OffsetIter { Some((field.ty, off)) } - fn into_iter(mut self, tys: &Types) -> impl Iterator { + fn into_iter(mut self, tys: &Types) -> impl Iterator { core::iter::from_fn(move || self.next(tys)) } } diff --git a/lang/src/parser.rs b/lang/src/parser.rs index 7d8ef97a..afc4a787 100644 --- a/lang/src/parser.rs +++ b/lang/src/parser.rs @@ -397,6 +397,22 @@ impl<'a, 'b> Parser<'a, 'b> { }, trailing_comma: core::mem::take(&mut self.trailing_sep), }, + T::Enum => E::Enum { + pos, + variants: { + self.expect_advance(T::LBrace)?; + self.collect_list(T::Comma, T::RBrace, |s| { + let tok = s.token; + Some(if s.advance_if(T::Comment) { + CommentOr::Comment { literal: s.tok_str(tok), pos: tok.start } + } else { + let name = s.expect_advance(T::Ident)?; + CommentOr::Or(EnumField { pos: name.start, name: s.tok_str(name) }) + }) + }) + }, + trailing_comma: core::mem::take(&mut self.trailing_sep), + }, T::Ident | T::CtIdent => { let (id, is_first) = self.resolve_ident(token); E::Ident { @@ -413,6 +429,20 @@ impl<'a, 'b> Parser<'a, 'b> { then: self.ptr_expr()?, else_: self.advance_if(T::Else).then(|| self.ptr_expr()).trans()?, }, + T::Match => E::Match { + pos, + value: self.ptr_expr()?, + branches: { + self.expect_advance(T::LBrace)?; + self.collect_list(T::Comma, T::RBrace, |s| { + Some(MatchBranch { + pat: s.expr()?, + pos: s.expect_advance(T::TArrow)?.start, + body: s.expr()?, + }) + }) + }, + }, T::Loop => E::Loop { pos, body: self.ptr_expr()? }, T::Break => E::Break { pos }, T::Continue => E::Continue { pos }, @@ -459,14 +489,18 @@ impl<'a, 'b> Parser<'a, 'b> { pos }, }, - T::Band | T::Mul | T::Xor | T::Sub | T::Que | T::Not => E::UnOp { + T::Band | T::Mul | T::Xor | T::Sub | T::Que | T::Not | T::Dot => E::UnOp { pos, op: token.kind, val: { + let prev_ident_stack = self.ctx.idents.len(); let expr = self.ptr_unit_expr()?; if token.kind == T::Band { self.flag_idents(*expr, idfl::REFERENCED); } + if token.kind == T::Dot { + self.ctx.idents.truncate(prev_ident_stack); + } expr }, }, @@ -858,6 +892,11 @@ generate_expr! { then: &'a Self, else_: Option<&'a Self>, }, + Match { + pos: Pos, + value: &'a Self, + branches: &'a [MatchBranch<'a>], + }, /// `'loop' Expr` Loop { pos: Pos, @@ -877,6 +916,12 @@ generate_expr! { trailing_comma: bool, packed: bool, }, + /// `'enum' LIST('{', ',', '}', Ident)` + Enum { + pos: Pos, + variants: &'a [CommentOr<'a, EnumField<'a>>], + trailing_comma: bool, + }, /// `[Expr] LIST('.{', ',', '}', Ident [':' Expr])` Ctor { pos: Pos, @@ -992,6 +1037,31 @@ impl Expr<'_> { } } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct MatchBranch<'a> { + pub pat: Expr<'a>, + pub pos: Pos, + pub body: Expr<'a>, +} + +impl Poser for MatchBranch<'_> { + fn posi(&self) -> Pos { + self.pat.pos() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct EnumField<'a> { + pub pos: Pos, + pub name: &'a str, +} + +impl Poser for EnumField<'_> { + fn posi(&self) -> Pos { + self.pos + } +} + #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct StructField<'a> { pub pos: Pos, diff --git a/lang/src/son.rs b/lang/src/son.rs index 007792c5..7b43812f 100644 --- a/lang/src/son.rs +++ b/lang/src/son.rs @@ -10,7 +10,7 @@ use { parser::{ self, idfl::{self}, - CtorField, Expr, Pos, + CtorField, Expr, MatchBranch, Pos, }, ty::{self, Arg, ArrayLen, Loc, Module, Tuple}, utils::{BitSet, Ent, Vc}, @@ -2944,6 +2944,41 @@ impl<'a> Codegen<'a> { vl.ty = base; Some(vl) } + Expr::UnOp { pos, op: TokenKind::Dot, val: &Expr::Ident { id, .. } } => { + inference!(ty, ctx, self, pos, "enum type", ".Variant"); + + let ty::Kind::Enum(e) = ty.expand() else { + self.report( + pos, + fa!("expected inferred type to be enum but got '{}'", self.ty_display(ty)), + ); + return Value::NEVER; + }; + + let intrnd = self.tys.names.project(self.file().ident_str(id)); + let Some(index) = + self.tys.enum_fields(e).iter().position(|f| Some(f.name) == intrnd) + else { + let field_list = self + .tys + .enum_fields(e) + .iter() + .map(|f| self.tys.names.ident_str(f.name)) + .intersperse("', '") + .collect::(); + self.report( + pos, + fa!( + "the '{}' does not have this variant, \ + but it does have '{field_list}'", + self.ty_display(ty) + ), + ); + return Value::NEVER; + }; + + Some(self.ci.nodes.new_const_lit(ty, index as i64)) + } Expr::UnOp { pos, op: op @ TokenKind::Sub, val } => { let val = self.expr_ctx(val, Ctx::default().with_ty(ctx.ty.unwrap_or(ty::Id::INT)))?; @@ -3569,6 +3604,17 @@ impl<'a> Codegen<'a> { for stmt in stmts { ret = ret.and(self.expr(stmt)); if let Some(mut id) = ret { + if id.ty != ty::Id::VOID { + self.report( + stmt.pos(), + fa!( + "statements need to evaluate to 'void', \ + but this statements evaluates '{}', \ + use '_ = ' to discard the value if its intentional", + self.ty_display(id.ty) + ), + ); + } self.assert_ty(stmt.pos(), &mut id, ty::Id::VOID, "statement"); } else { break; @@ -3885,6 +3931,119 @@ impl<'a> Codegen<'a> { Some(Value::VOID) } + Expr::Match { pos, value, branches } => { + let value = self.expr(value)?; + + let ty::Kind::Enum(e) = value.ty.expand() else { + self.report( + pos, + fa!( + "match operates on enums (for now), '{}' is not an enum", + self.ty_display(value.ty) + ), + ); + return Value::NEVER; + }; + + let mut covered_values = vec![Pos::MAX; self.tys.enum_field_range(e).len()]; + let mut scopes = vec![]; + let mut else_branch = None; + for &MatchBranch { pat, pos: bp, body } in branches { + if let Expr::Wildcard { .. } = pat { + else_branch = Some(body); + continue; + } + let pat_val = self.eval_const(self.ci.file, &pat, value.ty); + if covered_values[pat_val as usize] != Pos::MAX { + self.report(bp, "duplicate branch"); + self.report(covered_values[pat_val as usize], "first branch declared here"); + continue; + } + covered_values[pat_val as usize] = bp; + + let pat_val = self.ci.nodes.new_const(value.ty, pat_val as i64); + let cnd = self.ci.nodes.new_node( + ty::Id::BOOL, + Kind::BinOp { op: TokenKind::Eq }, + [VOID, value.id, pat_val], + self.tys, + ); + + let if_node = self.ci.nodes.new_node( + ty::Id::VOID, + Kind::If, + [self.ci.ctrl.get(), cnd], + self.tys, + ); + + let cached_scope = self.ci.scope.dup(&mut self.ci.nodes); + self.ci.ctrl.set( + self.ci.nodes.new_node(ty::Id::VOID, Kind::Then, [if_node], self.tys), + &mut self.ci.nodes, + ); + let ctrl = self.expr(&body).map_or(Nid::MAX, |_| self.ci.ctrl.get()); + scopes.push((ctrl, mem::replace(&mut self.ci.scope, cached_scope))); + + self.ci.ctrl.set( + self.ci.nodes.new_node(ty::Id::VOID, Kind::Else, [if_node], self.tys), + &mut self.ci.nodes, + ); + } + + let mut rcntrl = if let Some(ebr) = else_branch { + self.expr(&ebr).map_or(Nid::MAX, |_| self.ci.ctrl.get()) + } else { + let missing_branches = covered_values + .into_iter() + .zip(self.tys.enum_fields(e)) + .filter(|&(f, _)| f == Pos::MAX) + .map(|(_, f)| self.tys.names.ident_str(f.name)) + .intersperse("', '") + .collect::(); + + if !missing_branches.is_empty() { + self.report( + pos, + fa!("not all cases covered, missing '{missing_branches}'"), + ); + } + self.ci.ctrl.get() + }; + + for (lcntrl, mut then_scope) in scopes.into_iter().rev() { + if lcntrl == Nid::MAX && rcntrl == Nid::MAX { + then_scope.clear(&mut self.ci.nodes); + return None; + } else if lcntrl == Nid::MAX { + then_scope.clear(&mut self.ci.nodes); + return Some(Value::VOID); + } else if rcntrl == Nid::MAX { + self.ci.scope.clear(&mut self.ci.nodes); + self.ci.scope = then_scope; + self.ci.ctrl.set(lcntrl, &mut self.ci.nodes); + return Some(Value::VOID); + } + + rcntrl = self.ci.nodes.new_node( + ty::Id::VOID, + Kind::Region, + [lcntrl, rcntrl], + self.tys, + ); + self.ci.ctrl.set(rcntrl, &mut self.ci.nodes); + + self.ci.nodes.merge_scopes( + &mut self.ci.loops, + &self.ci.ctrl, + &mut self.ci.scope, + &mut then_scope, + self.tys, + ); + then_scope.clear(&mut self.ci.nodes); + } + + Some(Value::VOID) + } ref e => { self.report_unhandled_ast(e, "bruh"); Value::NEVER @@ -4953,11 +5112,12 @@ mod tests { comments; if_statements; variables; + hex_octal_binary_literals; loops; pointers; - nullable_types; structs; - hex_octal_binary_literals; + enums; + nullable_types; struct_operators; global_variables; constants; diff --git a/lang/tests/son_tests_enums.txt b/lang/tests/son_tests_enums.txt new file mode 100644 index 00000000..fc16c8d7 --- /dev/null +++ b/lang/tests/son_tests_enums.txt @@ -0,0 +1,20 @@ +main: + ADDI64 r254, r254, -16d + ST r31, r254, 0a, 16h + JAL r31, r0, :some_enum + CP r32, r1 + ANDI r32, r32, 255d + JNE r32, r0, :0 + CP r1, r0 + JMP :1 + 0: LI64 r32, 100d + CP r1, r32 + 1: LD r31, r254, 0a, 16h + ADDI64 r254, r254, 16d + JALA r0, r31, 0a +some_enum: + CP r1, r0 + JALA r0, r31, 0a +code size: 128 +ret: 0 +status: Ok(())