From ef6436dc4e73b5f97a23cfc40546701bfc287961 Mon Sep 17 00:00:00 2001 From: Sai Date: Wed, 29 Jul 2026 22:26:01 -0700 Subject: [PATCH] Add VAMS-2023 break/continue/return jump statements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement §5.11 jump statements: break/continue in while loops (banned in analog for), and return with a value from analog user-defined functions, with CFG lowering and validation diagnostics. Co-authored-by: Cursor --- openvaf/hir/src/body.rs | 6 + openvaf/hir_def/src/body/lower.rs | 5 + openvaf/hir_def/src/body/pretty.rs | 10 ++ openvaf/hir_def/src/expr.rs | 63 +++++++++-- openvaf/hir_lower/src/body.rs | 1 + openvaf/hir_lower/src/ctx.rs | 18 +++ openvaf/hir_lower/src/expr.rs | 50 ++++++++- openvaf/hir_lower/src/stmt.rs | 127 ++++++++++++++++++++-- openvaf/hir_ty/src/inference.rs | 12 +- openvaf/hir_ty/src/validation.rs | 62 +++++++++++ openvaf/hir_ty/src/validation/body.rs | 100 ++++++++++++++++- openvaf/parser/src/grammar/stmts.rs | 54 ++++++++- openvaf/syntax/src/ast/generated/nodes.rs | 98 ++++++++++++++++- openvaf/syntax/src/name.rs | 14 ++- openvaf/syntax/veriloga.ungram | 12 ++ openvaf/test_data/mir/vams2023_jumps.mir | 86 +++++++++++++++ openvaf/test_data/mir/vams2023_jumps.va | 34 ++++++ openvaf/test_data/ui/vams2023_jumps.log | 40 +++++++ openvaf/test_data/ui/vams2023_jumps.va | 27 +++++ openvaf/tokens/src/parser/generated.rs | 28 +++-- sourcegen/src/ast/src.rs | 6 + 21 files changed, 815 insertions(+), 38 deletions(-) create mode 100644 openvaf/test_data/mir/vams2023_jumps.mir create mode 100644 openvaf/test_data/mir/vams2023_jumps.va create mode 100644 openvaf/test_data/ui/vams2023_jumps.log create mode 100644 openvaf/test_data/ui/vams2023_jumps.va diff --git a/openvaf/hir/src/body.rs b/openvaf/hir/src/body.rs index a41bd90a..6f37a453 100644 --- a/openvaf/hir/src/body.rs +++ b/openvaf/hir/src/body.rs @@ -236,6 +236,9 @@ impl<'a> BodyRef<'a> { } hir_def::Stmt::WhileLoop { cond, body } => Some(Stmt::WhileLoop { cond, body }), hir_def::Stmt::Case { discr, ref case_arms } => Some(Stmt::Case { discr, case_arms }), + hir_def::Stmt::Break => Some(Stmt::Break), + hir_def::Stmt::Continue => Some(Stmt::Continue), + hir_def::Stmt::Return { value } => Some(Stmt::Return { value }), } } } @@ -277,6 +280,9 @@ pub enum Stmt<'a> { ForLoop { init: StmtId, cond: ExprId, incr: StmtId, body: StmtId }, WhileLoop { cond: ExprId, body: StmtId }, Case { discr: ExprId, case_arms: &'a [Case] }, // TODO lint on unreachable + Break, + Continue, + Return { value: Option }, } impl Stmt<'_> { #[inline] diff --git a/openvaf/hir_def/src/body/lower.rs b/openvaf/hir_def/src/body/lower.rs index f8798ec9..0162bcbe 100644 --- a/openvaf/hir_def/src/body/lower.rs +++ b/openvaf/hir_def/src/body/lower.rs @@ -176,6 +176,11 @@ impl LowerCtx<'_> { ast::Stmt::CaseStmt(stmt) => self.collect_case_stmt(stmt), ast::Stmt::EventStmt(stmt) => return self.collect_event_stmt(stmt), ast::Stmt::BlockStmt(stmt) => self.collect_block(stmt), + ast::Stmt::BreakStmt(_) => Stmt::Break, + ast::Stmt::ContinueStmt(_) => Stmt::Continue, + ast::Stmt::ReturnStmt(stmt) => { + Stmt::Return { value: stmt.value().map(|e| self.collect_expr(e)) } + } }; self.alloc_stmt(s, AstPtr::new(&stmt), stmt.attrs()) } diff --git a/openvaf/hir_def/src/body/pretty.rs b/openvaf/hir_def/src/body/pretty.rs index 9e153b99..d70003bd 100644 --- a/openvaf/hir_def/src/body/pretty.rs +++ b/openvaf/hir_def/src/body/pretty.rs @@ -138,6 +138,16 @@ impl Printer<'_> { }); wln!(self, "endcase"); } + Stmt::Break => wln!(self, "break;"), + Stmt::Continue => wln!(self, "continue;"), + Stmt::Return { value } => { + w!(self, "return"); + if let Some(value) = value { + w!(self, " "); + self.pretty_print_expr(value); + } + wln!(self, ";"); + } } } pub fn pretty_print_expr(&mut self, e: ExprId) { diff --git a/openvaf/hir_def/src/expr.rs b/openvaf/hir_def/src/expr.rs index 0e617a9d..f62bc153 100644 --- a/openvaf/hir_def/src/expr.rs +++ b/openvaf/hir_def/src/expr.rs @@ -133,13 +133,45 @@ pub enum Stmt { Missing, Empty, Expr(ExprId), - EventControl { event: Event, body: StmtId }, - Assignment { dst: ExprId, val: ExprId, assignment_kind: ast::AssignOp }, - Block { /*scope: Option,*/ body: Vec }, - If { cond: ExprId, then_branch: StmtId, else_branch: StmtId }, - ForLoop { init: StmtId, cond: ExprId, incr: StmtId, body: StmtId }, - WhileLoop { cond: ExprId, body: StmtId }, - Case { discr: ExprId, case_arms: Vec }, // TODO lint on unreachable + EventControl { + event: Event, + body: StmtId, + }, + Assignment { + dst: ExprId, + val: ExprId, + assignment_kind: ast::AssignOp, + }, + Block { + /*scope: Option,*/ body: Vec, + }, + If { + cond: ExprId, + then_branch: StmtId, + else_branch: StmtId, + }, + ForLoop { + init: StmtId, + cond: ExprId, + incr: StmtId, + body: StmtId, + }, + WhileLoop { + cond: ExprId, + body: StmtId, + }, + Case { + discr: ExprId, + case_arms: Vec, + }, // TODO lint on unreachable + /// VAMS-2023 §5.11 — exit the innermost loop. + Break, + /// VAMS-2023 §5.11 — skip to the end of the innermost loop (re-check condition). + Continue, + /// VAMS-2023 §5.11 / §4.7.2.2 — early exit from an analog user-defined function. + Return { + value: Option, + }, } #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] @@ -177,11 +209,18 @@ impl Stmt { #[inline] pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) { match *self { - Stmt::Empty | Stmt::Missing | Stmt::Block { .. } | Stmt::EventControl { .. } => (), + Stmt::Empty + | Stmt::Missing + | Stmt::Block { .. } + | Stmt::EventControl { .. } + | Stmt::Break + | Stmt::Continue => (), Stmt::If { cond: expr, .. } | Stmt::ForLoop { cond: expr, .. } | Stmt::WhileLoop { cond: expr, .. } | Stmt::Expr(expr) => f(expr), + Stmt::Return { value: Some(expr) } => f(expr), + Stmt::Return { value: None } => (), Stmt::Assignment { dst, val, .. } => { f(dst); f(val) @@ -202,7 +241,13 @@ impl Stmt { #[inline] pub fn walk_child_stmts(&self, mut f: impl FnMut(StmtId)) { match *self { - Stmt::Expr(_) | Stmt::Assignment { .. } | Stmt::Missing | Stmt::Empty => (), + Stmt::Expr(_) + | Stmt::Assignment { .. } + | Stmt::Missing + | Stmt::Empty + | Stmt::Break + | Stmt::Continue + | Stmt::Return { .. } => (), Stmt::WhileLoop { body, .. } | Stmt::EventControl { body, .. } => f(body), Stmt::If { then_branch: true_stmt, else_branch: false_stmt, .. } => { f(true_stmt); diff --git a/openvaf/hir_lower/src/body.rs b/openvaf/hir_lower/src/body.rs index 48b4f085..c0f668ce 100644 --- a/openvaf/hir_lower/src/body.rs +++ b/openvaf/hir_lower/src/body.rs @@ -99,6 +99,7 @@ impl<'c1, 'c2> BodyLoweringCtx<'_, 'c1, 'c2> { _ => {} }, Stmt::Assignment { .. } | Stmt::Expr(_) | Stmt::Contribute { .. } => {} + Stmt::Break | Stmt::Continue | Stmt::Return { .. } => {} Stmt::EventControl { event, body } => { let inner = in_cross || matches!(event, Event::Cross); self.collect_cross_assigned(body, inner, dst); diff --git a/openvaf/hir_lower/src/ctx.rs b/openvaf/hir_lower/src/ctx.rs index bf51deaf..c0c5524a 100644 --- a/openvaf/hir_lower/src/ctx.rs +++ b/openvaf/hir_lower/src/ctx.rs @@ -35,6 +35,21 @@ pub struct LoweringCtx<'a, 'c> { /// there are their initial value (read from the retained state), not a /// per-evaluation reset. pub in_initial_step: bool, + /// Stack of enclosing loops for `break`/`continue` (innermost last). + pub loop_stack: Vec, + /// Exit block of the analog function currently being lowered, if any. + pub function_exit: Option, + /// Function whose return place early `return` statements write into. + pub function_return: Option, +} + +/// CFG targets for the innermost enclosing loop. +#[derive(Clone, Copy)] +pub struct LoopTargets { + /// Where `continue` jumps (condition head for while; incr head for for). + pub continue_to: Block, + /// Where `break` jumps. + pub break_to: Block, } /// Synthetic constant base used as the (non-parameter) `lim_state` key for retained @@ -59,6 +74,9 @@ impl<'a, 'c> LoweringCtx<'a, 'c> { num_noise_sources: 0, retained_states: AHashMap::default(), in_initial_step: false, + loop_stack: Vec::new(), + function_exit: None, + function_return: None, } } diff --git a/openvaf/hir_lower/src/expr.rs b/openvaf/hir_lower/src/expr.rs index 83456545..08820309 100644 --- a/openvaf/hir_lower/src/expr.rs +++ b/openvaf/hir_lower/src/expr.rs @@ -10,7 +10,10 @@ use hir::signatures::{ NATURE_ACCESS_NODES, NATURE_ACCESS_NODE_GND, NATURE_ACCESS_PORT_FLOW, REAL_EQ, REAL_OP, SIMPARAM_DEFAULT, SIMPARAM_NO_DEFAULT, STR_EQ, }; -use hir::{Body, BuiltIn, Expr, ExprId, Literal, /*ParamSysFun,*/ Ref, ResolvedFun, Type}; +use hir::{ + Body, BodyRef, BuiltIn, Expr, ExprId, Literal, /*ParamSysFun,*/ Ref, ResolvedFun, Stmt, + Type, +}; use mir::builder::InstBuilder; use mir::{Opcode, Value, FALSE, F_ZERO, GRAVESTONE, INFINITY, TRUE, ZERO}; use stdx::iter::zip; @@ -244,7 +247,29 @@ impl BodyLoweringCtx<'_, '_, '_> { self.ctx.def_place(PlaceKind::FunctionReturn(fun), init); let body = fun.body(self.ctx.db); - BodyLoweringCtx { body: body.borrow(), path: self.path, ctx: self.ctx }.lower_entry_stmts(); + let body_ref = body.borrow(); + let needs_exit = body_has_return(&body_ref); + let (prev_exit, prev_fun, exit) = if needs_exit { + let exit = self.ctx.create_block(); + let prev_exit = self.ctx.function_exit.replace(exit); + let prev_fun = self.ctx.function_return.replace(fun); + (prev_exit, prev_fun, Some(exit)) + } else { + (None, None, None) + }; + + BodyLoweringCtx { body: body_ref, path: self.path, ctx: self.ctx }.lower_entry_stmts(); + + if let Some(exit) = exit { + self.ctx.ensured_sealed(); + if !self.ctx.func.is_filled() { + self.ctx.ins().jump(exit); + } + self.ctx.seal_block(exit); + self.ctx.switch_to_block(exit); + self.ctx.function_exit = prev_exit; + self.ctx.function_return = prev_fun; + } // write outputs back to original (including possibly required cast) for (arg, &expr) in args { @@ -1101,3 +1126,24 @@ impl BodyLoweringCtx<'_, '_, '_> { BodyLoweringCtx { ctx: self.ctx, body: body.borrow(), path: self.path }.lower_expr(expr) } } + +fn body_has_return(body: &BodyRef<'_>) -> bool { + fn walk(body: &BodyRef<'_>, stmt: hir::StmtId) -> bool { + match body.get_stmt(stmt) { + Some(Stmt::Return { .. }) => true, + Some(Stmt::Block { body: stmts }) => stmts.iter().any(|&s| walk(body, s)), + Some(Stmt::If { then_branch, else_branch, .. }) => { + walk(body, then_branch) || walk(body, else_branch) + } + Some(Stmt::WhileLoop { body: b, .. }) | Some(Stmt::EventControl { body: b, .. }) => { + walk(body, b) + } + Some(Stmt::ForLoop { init, incr, body: b, .. }) => { + walk(body, init) || walk(body, incr) || walk(body, b) + } + Some(Stmt::Case { case_arms, .. }) => case_arms.iter().any(|arm| walk(body, arm.body)), + _ => false, + } + } + body.entry().iter().any(|&s| walk(body, s)) +} diff --git a/openvaf/hir_lower/src/stmt.rs b/openvaf/hir_lower/src/stmt.rs index a35b29cf..699544cc 100644 --- a/openvaf/hir_lower/src/stmt.rs +++ b/openvaf/hir_lower/src/stmt.rs @@ -86,14 +86,58 @@ impl BodyLoweringCtx<'_, '_, '_> { } Stmt::ForLoop { init, cond, incr, body } => { self.lower_stmt(init); - self.lower_loop(cond, |s| { - s.lower_stmt(body); - s.lower_stmt(incr); - }); + if stmt_has_continue(self.body, body) { + self.lower_for_loop(cond, incr, body); + } else { + // No `continue`: keep the classic body→incr→cond shape so MIR for + // ordinary analog for-loops stays unchanged. + self.lower_while_loop_with(cond, |s| { + s.lower_stmt(body); + s.lower_stmt(incr); + }); + } + } + Stmt::WhileLoop { cond, body } => { + self.lower_while_loop_with(cond, |s| s.lower_stmt(body)) } - Stmt::WhileLoop { cond, body } => self.lower_loop(cond, |s| s.lower_stmt(body)), Stmt::Case { discr, case_arms } => self.lower_case(discr, case_arms), + Stmt::Break => self.lower_break(), + Stmt::Continue => self.lower_continue(), + Stmt::Return { value } => self.lower_return(value), + } + } + + fn after_jump(&mut self) { + // Terminal jump filled the current block; give any following statements an + // unreachable block to lower into (mirrors `$fatal`). + let unreachable_bb = self.ctx.create_block(); + self.ctx.switch_to_block(unreachable_bb); + self.ctx.seal_block(unreachable_bb); + } + + fn lower_break(&mut self) { + let target = + self.ctx.loop_stack.last().expect("break validated to be inside a loop").break_to; + self.ctx.ins().jump(target); + self.after_jump(); + } + + fn lower_continue(&mut self) { + let target = + self.ctx.loop_stack.last().expect("continue validated to be inside a loop").continue_to; + self.ctx.ins().jump(target); + self.after_jump(); + } + + fn lower_return(&mut self, value: Option) { + let fun = self.ctx.function_return.expect("return validated to be inside a function"); + let exit = self.ctx.function_exit.expect("function exit block"); + if let Some(value) = value { + let val = self.lower_expr(value); + self.ctx.def_place(PlaceKind::FunctionReturn(fun), val); } + self.ctx.ins().jump(exit); + self.after_jump(); } fn lower_case(&mut self, discr: ExprId, case_arms: &[Case]) { @@ -225,7 +269,7 @@ impl BodyLoweringCtx<'_, '_, '_> { } } - fn lower_loop(&mut self, cond: ExprId, lower_body: impl FnOnce(&mut Self)) { + fn lower_while_loop_with(&mut self, cond: ExprId, lower_body: impl FnOnce(&mut Self)) { let loop_cond_head = self.ctx.create_block(); let loop_body_head = self.ctx.create_block(); let loop_end = self.ctx.create_block(); @@ -235,15 +279,60 @@ impl BodyLoweringCtx<'_, '_, '_> { let cond = self.lower_expr(cond); self.ctx.ins().br_loop(cond, loop_body_head, loop_end); + // Body has only the loop-branch predecessor. Cond/end stay open until after + // the body so `continue`/`break` can register additional predecessors. self.ctx.seal_block(loop_body_head); - self.ctx.seal_block(loop_end); self.ctx.switch_to_block(loop_body_head); + self.ctx + .loop_stack + .push(crate::ctx::LoopTargets { continue_to: loop_cond_head, break_to: loop_end }); lower_body(self); - self.ctx.ins().jump(loop_cond_head); + self.ctx.loop_stack.pop(); + self.ctx.ensured_sealed(); + if !self.ctx.func.is_filled() { + self.ctx.ins().jump(loop_cond_head); + } self.ctx.seal_block(loop_cond_head); + self.ctx.seal_block(loop_end); + self.ctx.switch_to_block(loop_end); + } + + fn lower_for_loop(&mut self, cond: ExprId, incr: StmtId, body: StmtId) { + let loop_cond_head = self.ctx.create_block(); + let loop_body_head = self.ctx.create_block(); + let loop_continue = self.ctx.create_block(); + let loop_end = self.ctx.create_block(); + self.ctx.ins().jump(loop_cond_head); + self.ctx.switch_to_block(loop_cond_head); + + let cond = self.lower_expr(cond); + self.ctx.ins().br_loop(cond, loop_body_head, loop_end); + self.ctx.seal_block(loop_body_head); + + self.ctx.switch_to_block(loop_body_head); + self.ctx + .loop_stack + .push(crate::ctx::LoopTargets { continue_to: loop_continue, break_to: loop_end }); + self.lower_stmt(body); + self.ctx.loop_stack.pop(); + self.ctx.ensured_sealed(); + if !self.ctx.func.is_filled() { + self.ctx.ins().jump(loop_continue); + } + + self.ctx.seal_block(loop_continue); + self.ctx.switch_to_block(loop_continue); + self.lower_stmt(incr); + self.ctx.ensured_sealed(); + if !self.ctx.func.is_filled() { + self.ctx.ins().jump(loop_cond_head); + } + + self.ctx.seal_block(loop_cond_head); + self.ctx.seal_block(loop_end); self.ctx.switch_to_block(loop_end); } @@ -384,3 +473,25 @@ impl BodyLoweringCtx<'_, '_, '_> { }; } } + +fn stmt_has_continue(body: hir::BodyRef<'_>, stmt: StmtId) -> bool { + match body.get_stmt(stmt) { + Some(Stmt::Continue) => true, + Some(Stmt::Block { body: stmts }) => stmts.iter().any(|&s| stmt_has_continue(body, s)), + Some(Stmt::If { then_branch, else_branch, .. }) => { + stmt_has_continue(body, then_branch) || stmt_has_continue(body, else_branch) + } + Some(Stmt::WhileLoop { body: b, .. }) | Some(Stmt::EventControl { body: b, .. }) => { + stmt_has_continue(body, b) + } + Some(Stmt::ForLoop { init, incr, body: b, .. }) => { + stmt_has_continue(body, init) + || stmt_has_continue(body, incr) + || stmt_has_continue(body, b) + } + Some(Stmt::Case { case_arms, .. }) => { + case_arms.iter().any(|arm| stmt_has_continue(body, arm.body)) + } + _ => false, + } +} diff --git a/openvaf/hir_ty/src/inference.rs b/openvaf/hir_ty/src/inference.rs index 6c8c34c1..4c16cb44 100755 --- a/openvaf/hir_ty/src/inference.rs +++ b/openvaf/hir_ty/src/inference.rs @@ -91,7 +91,7 @@ impl InferenceResult { ..Default::default() }; - let mut ctx = Ctx { result, body: &body, db, expr_stmt_ty: None }; + let mut ctx = Ctx { result, body: &body, db, expr_stmt_ty: None, fn_return_ty: None }; ctx.expr_stmt_ty = match id { DefWithBodyId::ParamId(param) => match &db.param_data(param).ty { Some(ty) => Some(ty.clone()), @@ -106,6 +106,10 @@ impl InferenceResult { Type::Array { ty, .. } => *ty, ty => ty, }), + DefWithBodyId::FunctionId(fun) => { + ctx.fn_return_ty = Some(db.function_data(fun).return_ty.clone()); + None + } _ => None, }; @@ -126,6 +130,8 @@ struct Ctx<'a> { /// For behavioural (anlog body and function) and untype (nature attr) /// bodys this is simply none expr_stmt_ty: Option, + /// Return type of the enclosing analog function, if any. + fn_return_ty: Option, } impl Ctx<'_> { @@ -170,6 +176,10 @@ impl Ctx<'_> { } } } + Stmt::Return { value: Some(value) } => { + let dst_ty = self.fn_return_ty.clone(); + self.infere_assignment(stmt, value, dst_ty); + } _ => (), }; diff --git a/openvaf/hir_ty/src/validation.rs b/openvaf/hir_ty/src/validation.rs index c2ff481a..9bfce94c 100644 --- a/openvaf/hir_ty/src/validation.rs +++ b/openvaf/hir_ty/src/validation.rs @@ -502,6 +502,68 @@ impl Diagnostic for BodyValidationDiagnosticWrapped<'_> { res } + BodyValidationDiagnostic::JumpOutsideLoop { stmt, kind } => { + let FileSpan { range, file } = self.parse.to_file_span( + self.body_sm.stmt_map_back[stmt].as_ref().unwrap().range(), + self.sm, + ); + Report::error() + .with_message(format!("'{}' can only be used inside a loop", kind.as_str())) + .with_labels(vec![Label { + style: LabelStyle::Primary, + file_id: file, + range: range.into(), + message: "not inside a loop".to_owned(), + }]) + } + BodyValidationDiagnostic::JumpInAnalogFor { stmt, kind } => { + let FileSpan { range, file } = self.parse.to_file_span( + self.body_sm.stmt_map_back[stmt].as_ref().unwrap().range(), + self.sm, + ); + Report::error() + .with_message(format!( + "'{}' cannot be used inside an analog for loop", + kind.as_str() + )) + .with_labels(vec![Label { + style: LabelStyle::Primary, + file_id: file, + range: range.into(), + message: "not allowed in analog for".to_owned(), + }]) + .with_notes( + vec!["help: use a while loop if you need break/continue".to_owned()], + ) + } + BodyValidationDiagnostic::ReturnOutsideFunction { stmt } => { + let FileSpan { range, file } = self.parse.to_file_span( + self.body_sm.stmt_map_back[stmt].as_ref().unwrap().range(), + self.sm, + ); + Report::error() + .with_message("'return' can only be used in an analog user-defined function") + .with_labels(vec![Label { + style: LabelStyle::Primary, + file_id: file, + range: range.into(), + message: "not inside a function".to_owned(), + }]) + } + BodyValidationDiagnostic::MissingReturnValue { stmt } => { + let FileSpan { range, file } = self.parse.to_file_span( + self.body_sm.stmt_map_back[stmt].as_ref().unwrap().range(), + self.sm, + ); + Report::error() + .with_message("'return' requires an expression of the function return type") + .with_labels(vec![Label { + style: LabelStyle::Primary, + file_id: file, + range: range.into(), + message: "missing return value".to_owned(), + }]) + } } } diff --git a/openvaf/hir_ty/src/validation/body.rs b/openvaf/hir_ty/src/validation/body.rs index 5ed80c18..a24a4603 100644 --- a/openvaf/hir_ty/src/validation/body.rs +++ b/openvaf/hir_ty/src/validation/body.rs @@ -96,6 +96,43 @@ pub enum BodyValidationDiagnostic { node1: NodeId, node2: NodeId, }, + + /// `break`/`continue` outside any loop (VAMS-2023 §5.11). + JumpOutsideLoop { + stmt: StmtId, + kind: JumpKind, + }, + + /// `break`/`continue` inside an analog `for` loop (VAMS-2023 §5.11 / §5.9.3). + JumpInAnalogFor { + stmt: StmtId, + kind: JumpKind, + }, + + /// `return` outside an analog user-defined function (VAMS-2023 §5.11). + ReturnOutsideFunction { + stmt: StmtId, + }, + + /// `return;` without a value in a function that returns a value (VAMS-2023 §5.11). + MissingReturnValue { + stmt: StmtId, + }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum JumpKind { + Break, + Continue, +} + +impl JumpKind { + pub fn as_str(self) -> &'static str { + match self { + JumpKind::Break => "break", + JumpKind::Continue => "continue", + } + } } impl BodyValidationDiagnostic { @@ -125,6 +162,7 @@ impl BodyValidationDiagnostic { non_const_dominator: Box::default(), non_trivial_branches: HashSet::default(), trivial_probes: HashMap::default(), + loop_stack: Vec::new(), }; for stmt in &*body.entry_stmts { @@ -202,6 +240,14 @@ struct BodyValidator<'a> { non_const_dominator: Box<[ExprId]>, non_trivial_branches: HashSet, trivial_probes: HashMap>, + /// Innermost loop first... actually push on enter so last is innermost. + loop_stack: Vec, +} + +#[derive(Clone, Copy)] +enum LoopKind { + While, + For, } impl BodyValidator<'_> { @@ -241,10 +287,44 @@ impl BodyValidator<'_> { return; } - Stmt::If { cond, .. } - | Stmt::ForLoop { cond, .. } - | Stmt::WhileLoop { cond, .. } - | Stmt::Case { discr: cond, .. } => cond, + Stmt::Break => { + self.validate_jump(stmt, JumpKind::Break); + return; + } + Stmt::Continue => { + self.validate_jump(stmt, JumpKind::Continue); + return; + } + Stmt::Return { value } => { + if !matches!(self.owner, DefWithBodyId::FunctionId(_)) { + self.diagnostics.push(BodyValidationDiagnostic::ReturnOutsideFunction { stmt }); + } else if value.is_none() { + self.diagnostics.push(BodyValidationDiagnostic::MissingReturnValue { stmt }); + } + if let Some(value) = value { + self.validate_expr(value, stmt); + } + return; + } + + Stmt::WhileLoop { cond, body, .. } => { + self.validate_condition(cond, stmt, |s| { + s.loop_stack.push(LoopKind::While); + s.validate_stmt(body); + s.loop_stack.pop(); + }); + return; + } + Stmt::ForLoop { cond, .. } => { + self.validate_condition(cond, stmt, |s| { + s.loop_stack.push(LoopKind::For); + s.body.stmts[stmt].walk_child_stmts(|child| s.validate_stmt(child)); + s.loop_stack.pop(); + }); + return; + } + + Stmt::If { cond, .. } | Stmt::Case { discr: cond, .. } => cond, }; self.validate_condition(cond, stmt, |s| { @@ -252,6 +332,18 @@ impl BodyValidator<'_> { }); } + fn validate_jump(&mut self, stmt: StmtId, kind: JumpKind) { + match self.loop_stack.last() { + None => { + self.diagnostics.push(BodyValidationDiagnostic::JumpOutsideLoop { stmt, kind }); + } + Some(LoopKind::For) => { + self.diagnostics.push(BodyValidationDiagnostic::JumpInAnalogFor { stmt, kind }); + } + Some(LoopKind::While) => {} + } + } + fn validate_condition( &mut self, cond: ExprId, diff --git a/openvaf/parser/src/grammar/stmts.rs b/openvaf/parser/src/grammar/stmts.rs index 42a89380..0e091a0b 100644 --- a/openvaf/parser/src/grammar/stmts.rs +++ b/openvaf/parser/src/grammar/stmts.rs @@ -1,11 +1,33 @@ use super::*; -pub(super) const STMT_TS: TokenSet = - TokenSet::new(&[IF_KW, WHILE_KW, FOR_KW, CASE_KW, BEGIN_KW, T![;], IDENT, SYSFUN, T![@]]); +pub(super) const STMT_TS: TokenSet = TokenSet::new(&[ + IF_KW, + WHILE_KW, + FOR_KW, + CASE_KW, + BEGIN_KW, + BREAK_KW, + CONTINUE_KW, + RETURN_KW, + T![;], + IDENT, + SYSFUN, + T![@], +]); pub(super) const STMT_RECOVER: TokenSet = TokenSet::new(&[EOF, ENDMODULE_KW, T![;]]); -pub(super) const STMT_ATTR_RECOVER: TokenSet = - TokenSet::new(&[IF_KW, WHILE_KW, FOR_KW, CASE_KW, BEGIN_KW, T![;]]).union(STMT_RECOVER); +pub(super) const STMT_ATTR_RECOVER: TokenSet = TokenSet::new(&[ + IF_KW, + WHILE_KW, + FOR_KW, + CASE_KW, + BEGIN_KW, + BREAK_KW, + CONTINUE_KW, + RETURN_KW, + T![;], +]) +.union(STMT_RECOVER); pub(super) fn stmt_with_attrs(p: &mut Parser) { let m = p.start(); @@ -20,6 +42,9 @@ pub(super) fn stmt(p: &mut Parser, m: Marker, expected: TokenSet, recover: Token FOR_KW => for_stmt(p, m), CASE_KW => case_stmt(p, m), BEGIN_KW => block_stmt(p, m), + BREAK_KW => break_stmt(p, m), + CONTINUE_KW => continue_stmt(p, m), + RETURN_KW => return_stmt(p, m), T![@] => event_stmt(p, m), IDENT | SYSFUN => expr_or_assign_stmt::(p, m), _ => { @@ -111,6 +136,27 @@ fn while_stmt(p: &mut Parser, m: Marker) { m.complete(p, WHILE_STMT); } +fn break_stmt(p: &mut Parser, m: Marker) { + p.bump(BREAK_KW); + p.expect(T![;]); + m.complete(p, BREAK_STMT); +} + +fn continue_stmt(p: &mut Parser, m: Marker) { + p.bump(CONTINUE_KW); + p.expect(T![;]); + m.complete(p, CONTINUE_STMT); +} + +fn return_stmt(p: &mut Parser, m: Marker) { + p.bump(RETURN_KW); + if !p.at(T![;]) { + expr(p); + } + p.expect(T![;]); + m.complete(p, RETURN_STMT); +} + fn for_stmt(p: &mut Parser, m: Marker) { p.bump(FOR_KW); diff --git a/openvaf/syntax/src/ast/generated/nodes.rs b/openvaf/syntax/src/ast/generated/nodes.rs index 127af9e8..08ef7714 100644 --- a/openvaf/syntax/src/ast/generated/nodes.rs +++ b/openvaf/syntax/src/ast/generated/nodes.rs @@ -162,6 +162,36 @@ impl BlockStmt { pub fn end_token(&self) -> Option { support::token(&self.syntax, T![end]) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct BreakStmt { + pub(crate) syntax: SyntaxNode, +} +impl ast::AttrsOwner for BreakStmt {} +impl BreakStmt { + pub fn break_token(&self) -> Option { support::token(&self.syntax, T![break]) } + pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) } +} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ContinueStmt { + pub(crate) syntax: SyntaxNode, +} +impl ast::AttrsOwner for ContinueStmt {} +impl ContinueStmt { + pub fn continue_token(&self) -> Option { + support::token(&self.syntax, T![continue]) + } + pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) } +} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ReturnStmt { + pub(crate) syntax: SyntaxNode, +} +impl ast::AttrsOwner for ReturnStmt {} +impl ReturnStmt { + pub fn return_token(&self) -> Option { support::token(&self.syntax, T![return]) } + pub fn value(&self) -> Option { support::child(&self.syntax) } + pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, T![;]) } +} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Assign { pub(crate) syntax: SyntaxNode, } @@ -611,6 +641,9 @@ pub enum Stmt { CaseStmt(CaseStmt), EventStmt(EventStmt), BlockStmt(BlockStmt), + BreakStmt(BreakStmt), + ContinueStmt(ContinueStmt), + ReturnStmt(ReturnStmt), } impl ast::AttrsOwner for Stmt {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -829,6 +862,39 @@ impl AstNode for BlockStmt { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for BreakStmt { + fn can_cast(kind: SyntaxKind) -> bool { kind == BREAK_STMT } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl AstNode for ContinueStmt { + fn can_cast(kind: SyntaxKind) -> bool { kind == CONTINUE_STMT } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl AstNode for ReturnStmt { + fn can_cast(kind: SyntaxKind) -> bool { kind == RETURN_STMT } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for Assign { fn can_cast(kind: SyntaxKind) -> bool { kind == ASSIGN } fn cast(syntax: SyntaxNode) -> Option { @@ -1386,11 +1452,20 @@ impl From for Stmt { impl From for Stmt { fn from(node: BlockStmt) -> Stmt { Stmt::BlockStmt(node) } } +impl From for Stmt { + fn from(node: BreakStmt) -> Stmt { Stmt::BreakStmt(node) } +} +impl From for Stmt { + fn from(node: ContinueStmt) -> Stmt { Stmt::ContinueStmt(node) } +} +impl From for Stmt { + fn from(node: ReturnStmt) -> Stmt { Stmt::ReturnStmt(node) } +} impl AstNode for Stmt { fn can_cast(kind: SyntaxKind) -> bool { match kind { EMPTY_STMT | ASSIGN_STMT | EXPR_STMT | IF_STMT | WHILE_STMT | FOR_STMT | CASE_STMT - | EVENT_STMT | BLOCK_STMT => true, + | EVENT_STMT | BLOCK_STMT | BREAK_STMT | CONTINUE_STMT | RETURN_STMT => true, _ => false, } } @@ -1405,6 +1480,9 @@ impl AstNode for Stmt { CASE_STMT => Stmt::CaseStmt(CaseStmt { syntax }), EVENT_STMT => Stmt::EventStmt(EventStmt { syntax }), BLOCK_STMT => Stmt::BlockStmt(BlockStmt { syntax }), + BREAK_STMT => Stmt::BreakStmt(BreakStmt { syntax }), + CONTINUE_STMT => Stmt::ContinueStmt(ContinueStmt { syntax }), + RETURN_STMT => Stmt::ReturnStmt(ReturnStmt { syntax }), _ => return None, }; Some(res) @@ -1420,6 +1498,9 @@ impl AstNode for Stmt { Stmt::CaseStmt(it) => &it.syntax, Stmt::EventStmt(it) => &it.syntax, Stmt::BlockStmt(it) => &it.syntax, + Stmt::BreakStmt(it) => &it.syntax, + Stmt::ContinueStmt(it) => &it.syntax, + Stmt::ReturnStmt(it) => &it.syntax, } } } @@ -1803,6 +1884,21 @@ impl std::fmt::Display for BlockStmt { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for BreakStmt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for ContinueStmt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for ReturnStmt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for Assign { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/openvaf/syntax/src/name.rs b/openvaf/syntax/src/name.rs index 4ee57422..fa20899f 100644 --- a/openvaf/syntax/src/name.rs +++ b/openvaf/syntax/src/name.rs @@ -153,10 +153,16 @@ macro_rules! keywords { #[allow(bad_style, dead_code)] pub const use_:&str = "use"; + #[allow(bad_style, dead_code)] + pub const break_: &str = "break"; + #[allow(bad_style, dead_code)] + pub const continue_: &str = "continue"; + #[allow(bad_style, dead_code)] + pub const return_: &str = "return"; } pub fn is_reserved(name: &str) -> bool{ - matches!(name,$(stringify!($ident) |)* "use") + matches!(name, $(stringify!($ident) |)* "use" | "break" | "continue" | "return") } }; } @@ -165,6 +171,12 @@ pub mod kw { #[allow(bad_style, dead_code)] pub const use_: super::Name = super::Name::new_inline("use"); #[allow(bad_style, dead_code)] + pub const break_: super::Name = super::Name::new_inline("break"); + #[allow(bad_style, dead_code)] + pub const continue_: super::Name = super::Name::new_inline("continue"); + #[allow(bad_style, dead_code)] + pub const return_: super::Name = super::Name::new_inline("return"); + #[allow(bad_style, dead_code)] pub const root: super::Name = super::Name::new_inline("$root"); // VAMS-2023 reserves expm1/ln1p, but compact models written against older // revisions define their own functions with these names (e.g. HiSIMSOTB). diff --git a/openvaf/syntax/veriloga.ungram b/openvaf/syntax/veriloga.ungram index cc50fd56..87c8883a 100644 --- a/openvaf/syntax/veriloga.ungram +++ b/openvaf/syntax/veriloga.ungram @@ -51,6 +51,9 @@ EmptyStmt | CaseStmt | EventStmt | BlockStmt +| BreakStmt +| ContinueStmt +| ReturnStmt EmptyStmt = AttrList* ';' @@ -97,6 +100,15 @@ BlockStmt = items: BlockItem* 'end' +BreakStmt = + AttrList* 'break' ';' + +ContinueStmt = + AttrList* 'continue' ';' + +ReturnStmt = + AttrList* 'return' value: Expr? ';' + BlockScope = ':' Name BlockItem = diff --git a/openvaf/test_data/mir/vams2023_jumps.mir b/openvaf/test_data/mir/vams2023_jumps.mir new file mode 100644 index 00000000..39679567 --- /dev/null +++ b/openvaf/test_data/mir/vams2023_jumps.mir @@ -0,0 +1,86 @@ +function %(v16, v17, v19, v51) { + v3 = fconst 0.0 + v4 = iconst 0 + v5 = iconst 1 + v23 = iconst 2 + v28 = iconst 10 + v44 = fconst 0x1.8000000000000p0 + block0: + jmp block2 + + block2: +@0018 v35 = phi [v4, block0], [v35, block5], [v41, block11] +@0018 v18 = phi [v4, block0], [v22, block5], [v38, block11] +@0007 v20 = ilt v18, v19 + br v20, block3[loop], block4 + + block3: +@000b v22 = iadd v18, v5 +@000e v24 = ieq v22, v23 + br v24, block5, block6 + + block5: + jmp block2 + + block8: + jmp block7 + + block6: + jmp block7 + + block7: + v32 = phi [v35, block6], [v0, block8] +@0011 v25 = phi [v22, block6], [v0, block8] + v29 = igt v25, v28 + br v29, block9, block10 + + block9: + jmp block4 + + block12: + jmp block11 + + block10: +@0015 jmp block11 + + block11: +@0018 v38 = phi [v25, block10], [v0, block12] +@0003 v30 = phi [v32, block10], [v0, block12] +@0018 v41 = iadd v30, v38 +@0018 jmp block2 + + block4: + v63 = phi [v35, block2], [v32, block9] + v54 = phi [v18, block2], [v25, block9] +@0018 v45 = flt v44, v3 + br v45, block14, block15 + + block14: + jmp block13 + + block17: + jmp block16 + + block15: + jmp block16 + + block16: + v65 = phi [v63, block15], [v0, block17] + v56 = phi [v54, block15], [v0, block17] + v46 = phi [v44, block15], [v0, block17] + jmp block13 + + block18: + jmp block13 + + block13: + v61 = phi [v63, block14], [v65, block16], [v0, block18] + v52 = phi [v54, block14], [v56, block16], [v0, block18] + v49 = phi [v3, block14], [v46, block16], [v0, block18] + v60 = optbarrier v52 + v69 = optbarrier v61 + v70 = optbarrier v49 + jmp block1 + + block1: +} diff --git a/openvaf/test_data/mir/vams2023_jumps.va b/openvaf/test_data/mir/vams2023_jumps.va new file mode 100644 index 00000000..fff914b9 --- /dev/null +++ b/openvaf/test_data/mir/vams2023_jumps.va @@ -0,0 +1,34 @@ +// VAMS-2023 §5.11 jump statements: break/continue in while, return in UDF. +module test; + parameter integer n = 3; + integer i; + integer acc; + real y; + + analog function real clamp_pos; + input x; + real x; + begin + if (x < 0.0) begin + return 0.0; + end + return x; + end + endfunction + + analog begin + i = 0; + acc = 0; + while (i < n) begin + i = i + 1; + if (i == 2) begin + continue; + end + if (i > 10) begin + break; + end + acc = acc + i; + end + y = clamp_pos(1.5); + end +endmodule diff --git a/openvaf/test_data/ui/vams2023_jumps.log b/openvaf/test_data/ui/vams2023_jumps.log new file mode 100644 index 00000000..eb6f8c00 --- /dev/null +++ b/openvaf/test_data/ui/vams2023_jumps.log @@ -0,0 +1,40 @@ +error: 'break' can only be used inside a loop + --> /vams2023_jumps.va:16:9 + | +16 | break; // outside loop + | ^^^^^^ not inside a loop + +error: 'continue' can only be used inside a loop + --> /vams2023_jumps.va:17:9 + | +17 | continue; // outside loop + | ^^^^^^^^^ not inside a loop + +error: 'return' can only be used in an analog user-defined function + --> /vams2023_jumps.va:18:9 + | +18 | return 1.0; // outside function + | ^^^^^^^^^^^ not inside a function + +error: 'break' cannot be used inside an analog for loop + --> /vams2023_jumps.va:21:13 + | +21 | break; // forbidden in analog for + | ^^^^^^ not allowed in analog for + | + = help: use a while loop if you need break/continue + +error: 'continue' cannot be used inside an analog for loop + --> /vams2023_jumps.va:22:13 + | +22 | continue; // forbidden in analog for + | ^^^^^^^^^ not allowed in analog for + | + = help: use a while loop if you need break/continue + +error: 'return' requires an expression of the function return type + --> /vams2023_jumps.va:11:13 + | +11 | return; // missing value + | ^^^^^^^ missing return value + diff --git a/openvaf/test_data/ui/vams2023_jumps.va b/openvaf/test_data/ui/vams2023_jumps.va new file mode 100644 index 00000000..c7b09929 --- /dev/null +++ b/openvaf/test_data/ui/vams2023_jumps.va @@ -0,0 +1,27 @@ +// Negative cases for VAMS-2023 §5.11 jump statements. +module test; + parameter integer n = 3; + integer i; + real y; + + analog function real bad_return; + input x; + real x; + begin + return; // missing value + end + endfunction + + analog begin + break; // outside loop + continue; // outside loop + return 1.0; // outside function + + for (i = 0; i < n; i = i + 1) begin + break; // forbidden in analog for + continue; // forbidden in analog for + end + + y = bad_return(0.0); + end +endmodule diff --git a/openvaf/tokens/src/parser/generated.rs b/openvaf/tokens/src/parser/generated.rs index f230276e..ebc0c6ee 100644 --- a/openvaf/tokens/src/parser/generated.rs +++ b/openvaf/tokens/src/parser/generated.rs @@ -57,7 +57,9 @@ pub enum SyntaxKind { ANALOG_KW, BEGIN_KW, BRANCH_KW, + BREAK_KW, CASE_KW, + CONTINUE_KW, DEFAULT_KW, DISABLE_KW, DISCIPLINE_KW, @@ -84,6 +86,7 @@ pub enum SyntaxKind { PARAMETER_KW, LOCALPARAM_KW, REAL_KW, + RETURN_KW, STRING_KW, WHILE_KW, ROOT_KW, @@ -118,10 +121,12 @@ pub enum SyntaxKind { BLOCK_SCOPE, BLOCK_STMT, BRANCH_DECL, + BREAK_STMT, CALL, CASE, CASE_STMT, CONSTRAINT, + CONTINUE_STMT, DIRECTION, DISCIPLINE_DECL, DISCIPLINE_ATTR, @@ -154,6 +159,7 @@ pub enum SyntaxKind { PORTS, PREFIX_EXPR, RANGE, + RETURN_STMT, SELECT_EXPR, TYPE, VAR, @@ -170,13 +176,13 @@ use self::SyntaxKind::*; impl SyntaxKind { pub fn is_keyword(self) -> bool { match self { - ANALOG_KW | BEGIN_KW | BRANCH_KW | CASE_KW | DEFAULT_KW | DISABLE_KW - | DISCIPLINE_KW | ELSE_KW | END_KW | ENDCASE_KW | ENDDISCIPLINE_KW | ENDFUNCTION_KW - | ENDMODULE_KW | ENDNATURE_KW | EXCLUDE_KW | FOR_KW | FROM_KW | FUNCTION_KW - | GENVAR_KW | IF_KW | INF_KW | INOUT_KW | INPUT_KW | INTEGER_KW | MODULE_KW - | NATURE_KW | OUTPUT_KW | PARAMETER_KW | LOCALPARAM_KW | REAL_KW | STRING_KW - | WHILE_KW | ROOT_KW | INITIAL_STEP_KW | INITIAL_KW | FINAL_STEP_KW | FINAL_KW - | ALIASPARAM_KW => true, + ANALOG_KW | BEGIN_KW | BRANCH_KW | BREAK_KW | CASE_KW | CONTINUE_KW | DEFAULT_KW + | DISABLE_KW | DISCIPLINE_KW | ELSE_KW | END_KW | ENDCASE_KW | ENDDISCIPLINE_KW + | ENDFUNCTION_KW | ENDMODULE_KW | ENDNATURE_KW | EXCLUDE_KW | FOR_KW | FROM_KW + | FUNCTION_KW | GENVAR_KW | IF_KW | INF_KW | INOUT_KW | INPUT_KW | INTEGER_KW + | MODULE_KW | NATURE_KW | OUTPUT_KW | PARAMETER_KW | LOCALPARAM_KW | REAL_KW + | RETURN_KW | STRING_KW | WHILE_KW | ROOT_KW | INITIAL_STEP_KW | INITIAL_KW + | FINAL_STEP_KW | FINAL_KW | ALIASPARAM_KW => true, _ => false, } } @@ -201,7 +207,9 @@ impl SyntaxKind { "analog" => ANALOG_KW, "begin" => BEGIN_KW, "branch" => BRANCH_KW, + "break" => BREAK_KW, "case" => CASE_KW, + "continue" => CONTINUE_KW, "default" => DEFAULT_KW, "disable" => DISABLE_KW, "discipline" => DISCIPLINE_KW, @@ -228,6 +236,7 @@ impl SyntaxKind { "parameter" => PARAMETER_KW, "localparam" => LOCALPARAM_KW, "real" => REAL_KW, + "return" => RETURN_KW, "string" => STRING_KW, "while" => WHILE_KW, "root" => ROOT_KW, @@ -327,7 +336,9 @@ impl std::fmt::Display for SyntaxKind { Self::ANALOG_KW => "'analog'", Self::BEGIN_KW => "'begin'", Self::BRANCH_KW => "'branch'", + Self::BREAK_KW => "'break'", Self::CASE_KW => "'case'", + Self::CONTINUE_KW => "'continue'", Self::DEFAULT_KW => "'default'", Self::DISABLE_KW => "'disable'", Self::DISCIPLINE_KW => "'discipline'", @@ -354,6 +365,7 @@ impl std::fmt::Display for SyntaxKind { Self::PARAMETER_KW => "'parameter'", Self::LOCALPARAM_KW => "'localparam'", Self::REAL_KW => "'real'", + Self::RETURN_KW => "'return'", Self::STRING_KW => "'string'", Self::WHILE_KW => "'while'", Self::ROOT_KW => "'root'", @@ -380,4 +392,4 @@ impl std::fmt::Display for SyntaxKind { } } #[macro_export] -macro_rules ! T { [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; ["_"] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<<] => { $ crate :: SyntaxKind :: ASHL } ; [>>>] => { $ crate :: SyntaxKind :: ASHR } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; ["(*"] => { $ crate :: SyntaxKind :: L_ATTR_PAREN } ; ["*)"] => { $ crate :: SyntaxKind :: R_ATTR_PAREN } ; ["'{"] => { $ crate :: SyntaxKind :: ARR_START } ; [<+] => { $ crate :: SyntaxKind :: CONTR } ; [**] => { $ crate :: SyntaxKind :: POW } ; [~^] => { $ crate :: SyntaxKind :: L_NXOR } ; [^~] => { $ crate :: SyntaxKind :: R_NXOR } ; [analog] => { $ crate :: SyntaxKind :: ANALOG_KW } ; [begin] => { $ crate :: SyntaxKind :: BEGIN_KW } ; [branch] => { $ crate :: SyntaxKind :: BRANCH_KW } ; [case] => { $ crate :: SyntaxKind :: CASE_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [disable] => { $ crate :: SyntaxKind :: DISABLE_KW } ; [discipline] => { $ crate :: SyntaxKind :: DISCIPLINE_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [end] => { $ crate :: SyntaxKind :: END_KW } ; [endcase] => { $ crate :: SyntaxKind :: ENDCASE_KW } ; [enddiscipline] => { $ crate :: SyntaxKind :: ENDDISCIPLINE_KW } ; [endfunction] => { $ crate :: SyntaxKind :: ENDFUNCTION_KW } ; [endmodule] => { $ crate :: SyntaxKind :: ENDMODULE_KW } ; [endnature] => { $ crate :: SyntaxKind :: ENDNATURE_KW } ; [exclude] => { $ crate :: SyntaxKind :: EXCLUDE_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [from] => { $ crate :: SyntaxKind :: FROM_KW } ; [function] => { $ crate :: SyntaxKind :: FUNCTION_KW } ; [genvar] => { $ crate :: SyntaxKind :: GENVAR_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [inf] => { $ crate :: SyntaxKind :: INF_KW } ; [inout] => { $ crate :: SyntaxKind :: INOUT_KW } ; [input] => { $ crate :: SyntaxKind :: INPUT_KW } ; [integer] => { $ crate :: SyntaxKind :: INTEGER_KW } ; [module] => { $ crate :: SyntaxKind :: MODULE_KW } ; [nature] => { $ crate :: SyntaxKind :: NATURE_KW } ; [output] => { $ crate :: SyntaxKind :: OUTPUT_KW } ; [parameter] => { $ crate :: SyntaxKind :: PARAMETER_KW } ; [localparam] => { $ crate :: SyntaxKind :: LOCALPARAM_KW } ; [real] => { $ crate :: SyntaxKind :: REAL_KW } ; [string] => { $ crate :: SyntaxKind :: STRING_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [root] => { $ crate :: SyntaxKind :: ROOT_KW } ; [initial_step] => { $ crate :: SyntaxKind :: INITIAL_STEP_KW } ; [initial] => { $ crate :: SyntaxKind :: INITIAL_KW } ; [final_step] => { $ crate :: SyntaxKind :: FINAL_STEP_KW } ; [final] => { $ crate :: SyntaxKind :: FINAL_KW } ; [aliasparam] => { $ crate :: SyntaxKind :: ALIASPARAM_KW } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [net_type] => { $ crate :: SyntaxKind :: NET_TYPE } ; [sysfun] => { $ crate :: SyntaxKind :: SYSFUN } ; } +macro_rules ! T { [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; ["_"] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<<] => { $ crate :: SyntaxKind :: ASHL } ; [>>>] => { $ crate :: SyntaxKind :: ASHR } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; ["(*"] => { $ crate :: SyntaxKind :: L_ATTR_PAREN } ; ["*)"] => { $ crate :: SyntaxKind :: R_ATTR_PAREN } ; ["'{"] => { $ crate :: SyntaxKind :: ARR_START } ; [<+] => { $ crate :: SyntaxKind :: CONTR } ; [**] => { $ crate :: SyntaxKind :: POW } ; [~^] => { $ crate :: SyntaxKind :: L_NXOR } ; [^~] => { $ crate :: SyntaxKind :: R_NXOR } ; [analog] => { $ crate :: SyntaxKind :: ANALOG_KW } ; [begin] => { $ crate :: SyntaxKind :: BEGIN_KW } ; [branch] => { $ crate :: SyntaxKind :: BRANCH_KW } ; [break] => { $ crate :: SyntaxKind :: BREAK_KW } ; [case] => { $ crate :: SyntaxKind :: CASE_KW } ; [continue] => { $ crate :: SyntaxKind :: CONTINUE_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [disable] => { $ crate :: SyntaxKind :: DISABLE_KW } ; [discipline] => { $ crate :: SyntaxKind :: DISCIPLINE_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [end] => { $ crate :: SyntaxKind :: END_KW } ; [endcase] => { $ crate :: SyntaxKind :: ENDCASE_KW } ; [enddiscipline] => { $ crate :: SyntaxKind :: ENDDISCIPLINE_KW } ; [endfunction] => { $ crate :: SyntaxKind :: ENDFUNCTION_KW } ; [endmodule] => { $ crate :: SyntaxKind :: ENDMODULE_KW } ; [endnature] => { $ crate :: SyntaxKind :: ENDNATURE_KW } ; [exclude] => { $ crate :: SyntaxKind :: EXCLUDE_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [from] => { $ crate :: SyntaxKind :: FROM_KW } ; [function] => { $ crate :: SyntaxKind :: FUNCTION_KW } ; [genvar] => { $ crate :: SyntaxKind :: GENVAR_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [inf] => { $ crate :: SyntaxKind :: INF_KW } ; [inout] => { $ crate :: SyntaxKind :: INOUT_KW } ; [input] => { $ crate :: SyntaxKind :: INPUT_KW } ; [integer] => { $ crate :: SyntaxKind :: INTEGER_KW } ; [module] => { $ crate :: SyntaxKind :: MODULE_KW } ; [nature] => { $ crate :: SyntaxKind :: NATURE_KW } ; [output] => { $ crate :: SyntaxKind :: OUTPUT_KW } ; [parameter] => { $ crate :: SyntaxKind :: PARAMETER_KW } ; [localparam] => { $ crate :: SyntaxKind :: LOCALPARAM_KW } ; [real] => { $ crate :: SyntaxKind :: REAL_KW } ; [return] => { $ crate :: SyntaxKind :: RETURN_KW } ; [string] => { $ crate :: SyntaxKind :: STRING_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [root] => { $ crate :: SyntaxKind :: ROOT_KW } ; [initial_step] => { $ crate :: SyntaxKind :: INITIAL_STEP_KW } ; [initial] => { $ crate :: SyntaxKind :: INITIAL_KW } ; [final_step] => { $ crate :: SyntaxKind :: FINAL_STEP_KW } ; [final] => { $ crate :: SyntaxKind :: FINAL_KW } ; [aliasparam] => { $ crate :: SyntaxKind :: ALIASPARAM_KW } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [net_type] => { $ crate :: SyntaxKind :: NET_TYPE } ; [sysfun] => { $ crate :: SyntaxKind :: SYSFUN } ; } diff --git a/sourcegen/src/ast/src.rs b/sourcegen/src/ast/src.rs index 5b233fa6..6a823536 100644 --- a/sourcegen/src/ast/src.rs +++ b/sourcegen/src/ast/src.rs @@ -62,7 +62,9 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc { "analog", "begin", "branch", + "break", "case", + "continue", "default", "disable", "discipline", @@ -89,6 +91,7 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc { "parameter", "localparam", "real", + "return", "string", "while", "root", @@ -117,10 +120,12 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc { "BLOCK_SCOPE", "BLOCK_STMT", "BRANCH_DECL", + "BREAK_STMT", "CALL", "CASE", "CASE_STMT", "CONSTRAINT", + "CONTINUE_STMT", "DIRECTION", "DISCIPLINE_DECL", "DISCIPLINE_ATTR", @@ -153,6 +158,7 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc { "PORTS", "PREFIX_EXPR", "RANGE", + "RETURN_STMT", "SELECT_EXPR", "TYPE", "VAR",