From 7e76de70662abacb6a5836ffe1043b728eab6640 Mon Sep 17 00:00:00 2001 From: Paulo Vitor Date: Thu, 16 Apr 2026 23:27:33 -0300 Subject: [PATCH 1/7] [improvement] Implement minic wrapper and direnv configuration - Add scripts/minic wrapper script for convenient access to mini_c binary - Add .envrc configuration for automatic direnv activation - Allows using 'minic' command as documented in README --- .envrc | 4 ++++ scripts/minic | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 .envrc create mode 100755 scripts/minic diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..c9e7713 --- /dev/null +++ b/.envrc @@ -0,0 +1,4 @@ +use flake + +# Add scripts directory to PATH for convenient access to minic wrapper +PATH_add "$(pwd)/scripts" diff --git a/scripts/minic b/scripts/minic new file mode 100755 index 0000000..4933564 --- /dev/null +++ b/scripts/minic @@ -0,0 +1,2 @@ +#!/bin/bash +exec "$(dirname "$0")/../target/debug/mini_c" "$@" From a91b35647aae74e1c1353747ac11d0ed4bc38900 Mon Sep 17 00:00:00 2001 From: Paulo Vitor Date: Mon, 20 Apr 2026 01:57:42 -0300 Subject: [PATCH 2/7] feat(ast): add Switch node to Statement enum Introduce the variant in to represent switch-case control flow, including a target expression, literal cases with their bodies, and a default branch. --- src/ir/ast.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ir/ast.rs b/src/ir/ast.rs index 5f57b24..89da162 100644 --- a/src/ir/ast.rs +++ b/src/ir/ast.rs @@ -9,7 +9,7 @@ //! * [`Literal`] — a constant value written directly in source code. //! * [`Expr`] / [`ExprD`] — expressions (arithmetic, comparisons, calls, …). //! * [`Statement`] / [`StatementD`] — statements (declarations, assignments, -//! `if`, `while`, `return`, blocks). +//! `if`, `while`, `switch`, `return`, blocks). //! * [`FunDecl`] — a single function declaration with its body. //! * [`Program`] — the top-level container: a list of function declarations. //! @@ -149,6 +149,11 @@ pub enum Statement { cond: Box>, body: Box>, }, + Switch { + target: Box>, + cases: Vec<(Literal, Vec>)>, + default: Vec>, + }, /// Return statement: `return [expr]`. Return(Option>>), } From bd62b21c01e8cf7e40fcf81793e8df8ca0de4f8d Mon Sep 17 00:00:00 2001 From: Paulo Vitor Date: Mon, 20 Apr 2026 02:06:29 -0300 Subject: [PATCH 3/7] feat(parser): add switch statement parser Parse switch statements in statements.rs, including the target expression, one or more case branches (integer and boolean literals), and a mandatory default block. Ensure each case and default contains at least one statement. --- src/parser/statements.rs | 56 +++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/src/parser/statements.rs b/src/parser/statements.rs index 9dcfef5..c4c3882 100644 --- a/src/parser/statements.rs +++ b/src/parser/statements.rs @@ -5,7 +5,7 @@ //! Exposes two public functions: //! //! * [`statement`] — the top-level entry point; tries each statement form in -//! order: `return`, `if`, `while`, call-statement, block, declaration, +//! order: `return`, `if`, `while`, `switch`, call-statement, block, declaration, //! assignment. //! * [`assignment`] — parses `lvalue = expression ;`; exported separately //! because the test suite uses it directly. @@ -13,15 +13,16 @@ //! # Grammar //! //! ```text -//! statement := block | if_stmt | while_stmt | simple ';' +//! statement := block | if_stmt | while_stmt | switch_stmt | simple ';' //! block := '{' statement* '}' //! if_stmt := 'if' expr block ['else' block] //! while_stmt := 'while' expr block +//! switch (expr) { case literal: statement+; … default: statement+ } //! simple := return | decl | call | assign //! ``` //! //! Every simple statement is terminated by `;`. -//! Compound statements (`if`, `while`, block) end with `}` and need no `;`. +//! Compound statements (`if`, `while`, `switch`, block) end with `}` and need no `;`. //! //! # Design Decisions //! @@ -40,16 +41,17 @@ //! suffixes in a loop using the same pattern as the `primary` parser in //! `expressions.rs`, producing a left-associative `Index` chain. -use crate::ir::ast::{Expr, ExprD, Statement, StatementD, UncheckedExpr, UncheckedStmt}; +use crate::ir::ast::{Literal, Expr, ExprD, Statement, StatementD, UncheckedExpr, UncheckedStmt}; use crate::parser::expressions::{expression, parse_call}; use crate::parser::functions::type_name; use crate::parser::identifiers::identifier; +use crate::parser::literals::{integer_literal, boolean_literal}; use nom::{ branch::alt, bytes::complete::tag, character::complete::{char, multispace0}, combinator::{map, opt}, - multi::many0, + multi::{many0, many1}, sequence::{delimited, preceded, tuple}, IResult, }; @@ -58,7 +60,7 @@ fn wrap(s: Statement<()>) -> UncheckedStmt { StatementD { stmt: s, ty: () } } -/// Parse any statement: block | if | while | return | decl | call | assignment. +/// Parse any statement: block | if | while | switch | return | decl | call | assignment. pub fn statement(input: &str) -> IResult<&str, UncheckedStmt> { preceded( multispace0, @@ -66,6 +68,7 @@ pub fn statement(input: &str) -> IResult<&str, UncheckedStmt> { block_statement, if_statement, while_statement, + switch_statement, return_statement, decl_statement, call_statement, @@ -160,6 +163,47 @@ fn while_statement(input: &str) -> IResult<&str, UncheckedStmt> { )) } +/// Parse a switch statement: `switch (expr) { case literal: statement+; … default: statement+ }`. +fn switch_statement(input: &str) -> IResult<&str, UncheckedStmt> { + let mut case_parse = |i| { + let (i, _) = preceded(multispace0, tag("case"))(i)?; + let (i, lit) = preceded( + multispace0, + alt(( + map(integer_literal, |n| Literal::Int(n)), + map(boolean_literal, |b| Literal::Bool(b)) + )) + )(i)?; + let (i, _) = preceded(multispace0, char(':'))(i)?; + let (i, stmts) = many1(preceded(multispace0, statement))(i)?; + Ok((i, (lit, stmts))) + }; + + let mut default_parse = |i| { + let (i, _) = preceded(multispace0, tag("default"))(i)?; + let (i, _) = preceded(multispace0, char(':'))(i)?; + let (i, stmts) = many1(preceded(multispace0, statement))(i)?; + Ok((i, stmts)) + }; + + let (rest, _) = preceded(multispace0, tag("switch"))(input)?; + let (rest, target) = preceded(multispace0, expression)(rest)?; + let (rest, _) = preceded(multispace0, char('{'))(rest)?; + let (rest, cases) = many1(preceded(multispace0, &mut case_parse))(rest)?; + let (rest, default) = preceded(multispace0, &mut default_parse)(rest)?; + + let (rest, _) = preceded(multispace0, char('}'))(rest)?; + + Ok(( + rest, + wrap(Statement::Switch { + target: Box::new(target), + cases, + default, + }), + )) +} + /// Parse an lvalue: identifier followed by zero or more `[ expr ]` suffixes. fn lvalue(input: &str) -> IResult<&str, UncheckedExpr> { let (mut rest, id) = preceded(multispace0, identifier)(input)?; From 930c8bf556e4b7b0f3dd200cb6920cbe89bd6ab3 Mon Sep 17 00:00:00 2001 From: Paulo Vitor Date: Mon, 20 Apr 2026 02:14:29 -0300 Subject: [PATCH 4/7] chore: add stubs for switch statement in type checker and interpreter Add todo!() stubs for Statement::Switch in both type checking and evaluation phases to satisfy exhaustive pattern matching and keep the project compiling. --- src/interpreter/exec_stmt.rs | 3 +++ src/semantic/type_checker.rs | 1 + 2 files changed, 4 insertions(+) diff --git a/src/interpreter/exec_stmt.rs b/src/interpreter/exec_stmt.rs index ceeda2c..319c3d5 100644 --- a/src/interpreter/exec_stmt.rs +++ b/src/interpreter/exec_stmt.rs @@ -111,6 +111,9 @@ pub fn exec_stmt(stmt: &CheckedStmt, env: &mut Environment) -> ExecResult } } }, + + // --- Switch --- + Statement::Switch { .. } => todo!("switch statement interpreter not implemented yet"), // --- Return --- Statement::Return(Some(expr)) => { diff --git a/src/semantic/type_checker.rs b/src/semantic/type_checker.rs index 46681cf..5fe2e01 100644 --- a/src/semantic/type_checker.rs +++ b/src/semantic/type_checker.rs @@ -232,6 +232,7 @@ fn type_check_stmt( body: Box::new(body_checked), } } + Statement::Switch { .. } => todo!("switch statement type checker not implemented yet"), Statement::Return(expr) => match expr { None => { if *expected_return != Type::Unit { From 73f0ec1abba41714b7c2b4fbb21cb7eba2a1b921 Mon Sep 17 00:00:00 2001 From: Paulo Vitor Date: Mon, 20 Apr 2026 12:22:21 -0300 Subject: [PATCH 5/7] refactor(parser): inline case and default parsers using nom combinators Replaced the mutable closures case_parse and default_parse with declarative nom combinators (tuple and map) inside the switch parser. This removes the need for mutable state in sub-parsers and makes the parsing logic more idiomatic and cohesive. --- src/parser/statements.rs | 48 +++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/parser/statements.rs b/src/parser/statements.rs index c4c3882..4785479 100644 --- a/src/parser/statements.rs +++ b/src/parser/statements.rs @@ -165,32 +165,34 @@ fn while_statement(input: &str) -> IResult<&str, UncheckedStmt> { /// Parse a switch statement: `switch (expr) { case literal: statement+; … default: statement+ }`. fn switch_statement(input: &str) -> IResult<&str, UncheckedStmt> { - let mut case_parse = |i| { - let (i, _) = preceded(multispace0, tag("case"))(i)?; - let (i, lit) = preceded( - multispace0, - alt(( - map(integer_literal, |n| Literal::Int(n)), - map(boolean_literal, |b| Literal::Bool(b)) - )) - )(i)?; - let (i, _) = preceded(multispace0, char(':'))(i)?; - let (i, stmts) = many1(preceded(multispace0, statement))(i)?; - Ok((i, (lit, stmts))) - }; - - let mut default_parse = |i| { - let (i, _) = preceded(multispace0, tag("default"))(i)?; - let (i, _) = preceded(multispace0, char(':'))(i)?; - let (i, stmts) = many1(preceded(multispace0, statement))(i)?; - Ok((i, stmts)) - }; - let (rest, _) = preceded(multispace0, tag("switch"))(input)?; let (rest, target) = preceded(multispace0, expression)(rest)?; let (rest, _) = preceded(multispace0, char('{'))(rest)?; - let (rest, cases) = many1(preceded(multispace0, &mut case_parse))(rest)?; - let (rest, default) = preceded(multispace0, &mut default_parse)(rest)?; + + let (rest, cases) = many1(map( + tuple(( + preceded(multispace0, tag("case")), + preceded( + multispace0, + alt(( + map(integer_literal, |i| Literal::Int(i)), + map(boolean_literal, |b| Literal::Bool(b)) + )) + ), + preceded(multispace0, char(':')), + many1(preceded(multispace0, statement)), + )), + |(_, literal, _, statements)| (literal, statements), + ))(rest)?; + + let (rest, default) = preceded(multispace0, map( + tuple(( + preceded(multispace0, tag("default")), + preceded(multispace0, char(':')), + many1(preceded(multispace0, statement)), + )), + |(_, _, statements)| statements, + ))(rest)?; let (rest, _) = preceded(multispace0, char('}'))(rest)?; From 37b205727892a1ab494dff589045803c43e0253a Mon Sep 17 00:00:00 2001 From: Paulo Vitor Date: Mon, 20 Apr 2026 12:41:33 -0300 Subject: [PATCH 6/7] test(parser): add tests for switch statement --- tests/parser.rs | 192 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/tests/parser.rs b/tests/parser.rs index eca6640..c044d88 100644 --- a/tests/parser.rs +++ b/tests/parser.rs @@ -672,3 +672,195 @@ fn test_array_in_expression() { assert!(matches!(result.exp, Expr::Index { ref base, ref index } if matches!(base.exp, Expr::ArrayLit(_)) && index.exp == Expr::Literal(Literal::Int(0)))); } + +// --- Switch --- + +#[test] +fn test_switch_statement() { + let result = statement("switch x { case 1: y = 1; default: y = 0; }").unwrap().1; + assert!(matches!(result.stmt, Statement::Switch { ref target, ref cases, ref default } + if matches!(target.exp, Expr::Ident(ref s) if s == "x") && cases.len() == 1 && default.len() == 1)); + if let Statement::Switch { ref cases, ref default, .. } = result.stmt { + assert!(matches!(cases[0].0, Literal::Int(1))); + assert!(matches!(cases[0].1.len(), 1)); + if let Statement::Assign { ref target, ref value } = cases[0].1[0].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "y")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(1))); + } + assert!(matches!(default.len(), 1)); + if let Statement::Assign { ref target, ref value } = default[0].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "y")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(0))); + } + } +} + +#[test] +fn test_switch_multiple_cases() { + let result = statement("switch x { case 1: y = 1; case 2: y = 2; default: y = 0; }").unwrap().1; + assert!(matches!(result.stmt, Statement::Switch { ref target, ref cases, ref default } + if matches!(target.exp, Expr::Ident(ref s) if s == "x") && cases.len() == 2 && default.len() == 1)); + if let Statement::Switch { ref cases, ref default, .. } = result.stmt { + assert!(matches!(cases[0].0, Literal::Int(1))); + assert!(matches!(cases[0].1.len(), 1)); + if let Statement::Assign { ref target, ref value } = cases[0].1[0].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "y")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(1))); + } + assert!(matches!(cases[1].0, Literal::Int(2))); + assert!(matches!(cases[1].1.len(), 1)); + if let Statement::Assign { ref target, ref value } = cases[1].1[0].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "y")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(2))); + } + assert!(matches!(default.len(), 1)); + if let Statement::Assign { ref target, ref value } = default[0].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "y")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(0))); + } + } +} + +#[test] +fn test_switch_multiple_statements() { + let result = statement("switch x { case 1: y = 1; z = 2; default: y = 0; z = 1; }").unwrap().1; + assert!(matches!(result.stmt, Statement::Switch { ref target, ref cases, ref default } + if matches!(target.exp, Expr::Ident(ref s) if s == "x") && cases.len() == 1 && default.len() == 2)); + if let Statement::Switch { ref cases, ref default, .. } = result.stmt { + assert!(matches!(cases[0].0, Literal::Int(1))); + assert!(matches!(cases[0].1.len(), 2)); + if let Statement::Assign { ref target, ref value } = cases[0].1[0].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "y")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(1))); + } + if let Statement::Assign { ref target, ref value } = cases[0].1[1].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "z")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(2))); + } + assert!(matches!(default.len(), 2)); + if let Statement::Assign { ref target, ref value } = default[0].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "y")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(0))); + } + if let Statement::Assign { ref target, ref value } = default[1].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "z")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(1))); + } + } +} + +#[test] +fn test_switch_multiple_cases_and_statements() { + let result = statement("switch x { case 1: y = 1; z = 2; case 2: y = 2; z = 3; default: y = 0; z = 1; }").unwrap().1; + assert!(matches!(result.stmt, Statement::Switch { ref target, ref cases, ref default } + if matches!(target.exp, Expr::Ident(ref s) if s == "x") && cases.len() == 2 && default.len() == 2)); + if let Statement::Switch { ref cases, ref default, .. } = result.stmt { + assert!(matches!(cases[0].0, Literal::Int(1))); + assert!(matches!(cases[0].1.len(), 2)); + if let Statement::Assign { ref target, ref value } = cases[0].1[0].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "y")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(1))); + } + if let Statement::Assign { ref target, ref value } = cases[0].1[1].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "z")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(2))); + } + assert!(matches!(cases[1].0, Literal::Int(2))); + assert!(matches!(cases[1].1.len(), 2)); + if let Statement::Assign { ref target, ref value } = cases[1].1[0].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "y")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(2))); + } + if let Statement::Assign { ref target, ref value } = cases[1].1[1].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "z")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(3))); + } + assert!(matches!(default.len(), 2)); + if let Statement::Assign { ref target, ref value } = default[0].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "y")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(0))); + } + if let Statement::Assign { ref target, ref value } = default[1].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "z")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(1))); + } + } +} + +#[test] +fn test_switch_multiple_defaults_err() { + assert!(statement("switch x { case 1: y = 1; default: y = 0; default: z = 2; }").is_err()); +} + +#[test] +fn test_switch_no_default_err() { + assert!(statement("switch x { case 1: y = 1; }").is_err()); +} + +#[test] +fn test_switch_no_cases_err() { + assert!(statement("switch x { default: y = 0; }").is_err()); +} + +#[test] +fn test_switch_non_expression_err() { + assert!(statement("switch { case 1: y = 1; default: y = 0; }").is_err()); +} + +#[test] +fn test_switch_invalid_case_literal() { + assert!(statement("switch x { case y: y = 1; default: y = 0; }").is_err()); + assert!(statement("switch x { case 1.5: y = 1; default: y = 0; }").is_err()); + assert!(statement("switch x { case \"str\": y = 1; default: y = 0; }").is_err()); +} + +#[test] +fn test_switch_invalid_syntax() { + assert!(statement("switch x { case 1 y = 1; default: y = 0; }").is_err()); + assert!(statement("switch x { case 1: y = 1; default y = 0; }").is_err()); + assert!(statement("switch x { case 1: y = 1; default: y = 0 ").is_err()); + assert!(statement("switch x case 1: y = 1; default: y = 0; }").is_err()); + assert!(statement("switch x { case 1: y = 1; default: y = 0;").is_err()); +} + +#[test] +fn test_switch_whitespace() { + assert!(statement("switch x { case 1: y = 1; default: y = 0; }").is_ok()); + assert!(statement("switch x { case 1 : y = 1 ; default : y = 0 ; }").is_ok()); +} + +#[test] +fn test_switch_in_function() { + let result = fun_decl("void foo() { switch x { case 1: y = 1; default: y = 0; } }").unwrap().1; + assert!(matches!(result.body.stmt, Statement::Block { ref seq } if seq.len() == 1)); + if let Statement::Block { ref seq } = result.body.stmt { + assert!(matches!(seq[0].stmt, Statement::Switch { ref target, ref cases, ref default } + if matches!(target.exp, Expr::Ident(ref s) if s == "x") && cases.len() == 1 && default.len() == 1)); + } +} + +#[test] +fn test_switch_in_if() { + let result = statement("if x { switch y { case 1: z = 1; default: z = 0; } }").unwrap().1; + assert!(matches!(result.stmt, Statement::If { .. })); + if let Statement::If { ref then_branch, .. } = &result.stmt { + assert!(matches!(then_branch.stmt, Statement::Block { ref seq } if seq.len() == 1)); + if let Statement::Block { ref seq } = &then_branch.stmt { + assert!(matches!(seq[0].stmt, Statement::Switch { ref target, ref cases, ref default } + if matches!(target.exp, Expr::Ident(ref s) if s == "y") && cases.len() == 1 && default.len() == 1)); + } + } +} + +#[test] +fn test_switch_in_while() { + let result = statement("while x { switch y { case 1: z = 1; default: z = 0; } }").unwrap().1; + assert!(matches!(result.stmt, Statement::While { .. })); + if let Statement::While { ref body, .. } = &result.stmt { + assert!(matches!(body.stmt, Statement::Block { ref seq } if seq.len() == 1)); + if let Statement::Block { ref seq } = &body.stmt { + assert!(matches!(seq[0].stmt, Statement::Switch { ref target, ref cases, ref default } + if matches!(target.exp, Expr::Ident(ref s) if s == "y") && cases.len() == 1 && default.len() == 1)); + } + } +} From dad35a785f4e56c60129f61a02b8c6880d6737d1 Mon Sep 17 00:00:00 2001 From: Paulo Vitor Date: Mon, 20 Apr 2026 13:05:26 -0300 Subject: [PATCH 7/7] test(parser): add test for cases with boolean literal --- tests/parser.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/parser.rs b/tests/parser.rs index c044d88..b207d51 100644 --- a/tests/parser.rs +++ b/tests/parser.rs @@ -787,6 +787,32 @@ fn test_switch_multiple_cases_and_statements() { } } +#[test] +fn test_switch_boolean_cases() { + let result = statement("switch x { case true: y = 1; case false: y = 0; default: y = 3; }").unwrap().1; + assert!(matches!(result.stmt, Statement::Switch { ref target, ref cases, ref default } + if matches!(target.exp, Expr::Ident(ref s) if s == "x") && cases.len() == 2 && default.len() == 1)); + if let Statement::Switch { ref cases, ref default, .. } = result.stmt { + assert!(matches!(cases[0].0, Literal::Bool(true))); + assert!(matches!(cases[0].1.len(), 1)); + if let Statement::Assign { ref target, ref value } = cases[0].1[0].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "y")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(1))); + } + assert!(matches!(cases[1].0, Literal::Bool(false))); + assert!(matches!(cases[1].1.len(), 1)); + if let Statement::Assign { ref target, ref value } = cases[1].1[0].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "y")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(0))); + } + assert!(matches!(default.len(), 1)); + if let Statement::Assign { ref target, ref value } = default[0].stmt { + assert!(matches!(target.exp, Expr::Ident(ref s) if s == "y")); + assert_eq!(value.exp, Expr::Literal(Literal::Int(3))); + } + } +} + #[test] fn test_switch_multiple_defaults_err() { assert!(statement("switch x { case 1: y = 1; default: y = 0; default: z = 2; }").is_err());