From c22fd17789d61bab3e3d413baa3ebceedd365101 Mon Sep 17 00:00:00 2001 From: Ewan Croft Date: Mon, 6 Apr 2026 19:41:27 +0100 Subject: [PATCH] Add switch statement --- examples/switch.sel | 19 +++++++++++++++++++ selenium/ast.py | 17 +++++++++++++++-- selenium/codegen_c.py | 21 +++++++++++++++++++++ selenium/lexer.py | 3 +++ selenium/parser.py | 26 ++++++++++++++++++++++++++ selenium/sema.py | 26 ++++++++++++++++++++++++-- switch | Bin 0 -> 50008 bytes 7 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 examples/switch.sel create mode 100755 switch diff --git a/examples/switch.sel b/examples/switch.sel new file mode 100644 index 0000000..62ce057 --- /dev/null +++ b/examples/switch.sel @@ -0,0 +1,19 @@ +wax int x = 2; +switch (x) { + case 1: { + whisper "one"; + break; + }; + case 2: { + whisper "two"; + break; + }; + case 3: { + whisper "three"; + break; + }; + default: { + whisper "other"; + break; + }; +}; \ No newline at end of file diff --git a/selenium/ast.py b/selenium/ast.py index 9831671..daf4966 100644 --- a/selenium/ast.py +++ b/selenium/ast.py @@ -60,6 +60,19 @@ class WhileStmt: body: Block +@dataclass(slots=True) +class Case: + value: "Expr" + body: Block + + +@dataclass(slots=True) +class SwitchStmt: + expr: "Expr" + cases: List[Case] + default: Optional[Block] + + @dataclass(slots=True) class ForStmt: init: Optional["Stmt"] @@ -137,5 +150,5 @@ class Cast: Expr = Union[Literal, VarRef, Unary, Ternary, Binary, Call, Cast] -Stmt = Union[VarDecl, Assign, IfStmt, WhileStmt, ForStmt, ReturnStmt, BreakStmt, ContinueStmt, PrintStmt, ExprStmt, Block] -TopLevel = Union[VarDecl, FunctionDecl, Assign, IfStmt, WhileStmt, ForStmt, ReturnStmt, BreakStmt, ContinueStmt, PrintStmt, ExprStmt, Block] +Stmt = Union[VarDecl, Assign, IfStmt, WhileStmt, SwitchStmt, ForStmt, ReturnStmt, BreakStmt, ContinueStmt, PrintStmt, ExprStmt, Block] +TopLevel = Union[VarDecl, FunctionDecl, Assign, IfStmt, WhileStmt, SwitchStmt, ForStmt, ReturnStmt, BreakStmt, ContinueStmt, PrintStmt, ExprStmt, Block] diff --git a/selenium/codegen_c.py b/selenium/codegen_c.py index 3ea319a..0dc4c42 100644 --- a/selenium/codegen_c.py +++ b/selenium/codegen_c.py @@ -9,6 +9,7 @@ from .ast import ( Block, BreakStmt, Call, + Case, Cast, ContinueStmt, Expr, @@ -21,6 +22,7 @@ from .ast import ( Program, ReturnStmt, Stmt, + SwitchStmt, Ternary, TopLevel, Unary, @@ -106,6 +108,8 @@ class CCodeGenerator: self._emit_if(item) elif isinstance(item, WhileStmt): self._emit_while(item) + elif isinstance(item, SwitchStmt): + self._emit_switch(item) elif isinstance(item, ForStmt): self._emit_for(item) elif isinstance(item, ReturnStmt): @@ -159,6 +163,23 @@ class CCodeGenerator: self._emit_statements(stmt.body.statements) self.indent -= 1 self._writeline("}") + + def _emit_switch(self, stmt: SwitchStmt) -> None: + self._writeline(f"switch ({self._expr(stmt.expr)}) {{") + self.indent += 1 + for case in stmt.cases: + self._writeline(f"case {self._expr(case.value)}:") + self.indent += 1 + self._emit_statements(case.body.statements) + self.indent -= 1 + if stmt.default is not None: + self._writeline("default:") + self.indent += 1 + self._emit_statements(stmt.default.statements) + self.indent -= 1 + self.indent -= 1 + self._writeline("}") + def _emit_for(self, stmt: ForStmt) -> None: init_str = "" if stmt.init is not None: diff --git a/selenium/lexer.py b/selenium/lexer.py index 0900db8..816096d 100644 --- a/selenium/lexer.py +++ b/selenium/lexer.py @@ -24,6 +24,9 @@ KEYWORDS = { "shadow": "SHADOW", "tide": "TIDE", "orbit": "ORBIT", + "switch": "SWITCH", + "case": "CASE", + "default": "DEFAULT", "break": "BREAK", "continue": "CONTINUE", "whisper": "WHISPER", diff --git a/selenium/parser.py b/selenium/parser.py index 7c8d832..acb4dbb 100644 --- a/selenium/parser.py +++ b/selenium/parser.py @@ -8,6 +8,7 @@ from .ast import ( Block, BreakStmt, Call, + Case, Cast, ContinueStmt, Expr, @@ -21,6 +22,7 @@ from .ast import ( Program, ReturnStmt, Stmt, + SwitchStmt, Ternary, TopLevel, TypeRef, @@ -145,6 +147,30 @@ class Parser: self._consume(";", "Expected ';' after for statement") return ForStmt(init, cond, increment, body) + if self._match("SWITCH"): + self._consume("(", "Expected '(' after switch") + expr = self._expression() + self._consume(")", "Expected ')' after expression") + self._consume("{", "Expected '{' after switch") + cases: List[Case] = [] + default = None + while not self._check("}"): + if self._match("CASE"): + value = self._expression() + self._consume(":", "Expected ':' after case value") + body = self._block() + self._consume(";", "Expected ';' after case body") + cases.append(Case(value, body)) + elif self._match("DEFAULT"): + self._consume(":", "Expected ':' after default") + default = self._block() + self._consume(";", "Expected ';' after default body") + else: + raise ParseError("Expected case or default in switch") + self._consume("}", "Expected '}' after switch") + self._consume(";", "Expected ';' after switch statement") + return SwitchStmt(expr, cases, default) + if self._match("RETURN"): if self._check(";"): self._advance() diff --git a/selenium/sema.py b/selenium/sema.py index 079065e..4b64130 100644 --- a/selenium/sema.py +++ b/selenium/sema.py @@ -9,6 +9,7 @@ from .ast import ( Block, BreakStmt, Call, + Case, Cast, ContinueStmt, Expr, @@ -22,6 +23,7 @@ from .ast import ( Program, ReturnStmt, Stmt, + SwitchStmt, Ternary, TopLevel, TypeRef, @@ -94,6 +96,7 @@ class SemanticAnalyzer: self.current_return: Optional[TypeInfo] = None self.expr_types: Dict[int, TypeInfo] = {} self.loop_depth = 0 + self.switch_depth = 0 def analyze(self, program: Program) -> Program: for item in program.items: @@ -188,6 +191,25 @@ class SemanticAnalyzer: self.loop_depth -= 1 return + if isinstance(stmt, SwitchStmt): + expr_type = self._infer_expr(stmt.expr, scope) + self._require_type(expr_type, "int", "Switch expression must be int") + seen_values = set() + self.switch_depth += 1 + for case in stmt.cases: + case_type = self._infer_expr(case.value, scope) + self._require_type(case_type, "int", "Case value must be int") + if isinstance(case.value, Literal) and case.value.kind == "int": + val = case.value.value + if val in seen_values: + raise SemanticError(f"Duplicate case value: {val}") + seen_values.add(val) + self._analyze_block(case.body, scope, in_function) + if stmt.default is not None: + self._analyze_block(stmt.default, scope, in_function) + self.switch_depth -= 1 + return + if isinstance(stmt, ReturnStmt): if not in_function: raise SemanticError("Return is only allowed inside a function") @@ -201,8 +223,8 @@ class SemanticAnalyzer: return if isinstance(stmt, BreakStmt): - if self.loop_depth == 0: - raise SemanticError("Break is only allowed inside loops") + if self.loop_depth == 0 and self.switch_depth == 0: + raise SemanticError("Break is only allowed inside loops or switch") return if isinstance(stmt, ContinueStmt): diff --git a/switch b/switch new file mode 100755 index 0000000000000000000000000000000000000000..1445338d35c69a2a5b1090f58341bffeb7012f32 GIT binary patch literal 50008 zcmX^A>+L@t1_nk31_lNu1_lNp1_p*5EDQ{-3<@A221y161`h@XhWPjZM|amK*C2ne zFbVMp1th-!eUgQf>~>_Pb;GeH>SE|Bv0_>{zwL=X#tA?|{( z!6ei?Sb74PM<~1?>EFlG&)e0-6B1es3uKerFFmeB3`5hh%3==pQ9d^FpX4uKW%24RQ$PmKFz##I1+hHdMCmNp@!slgR z5Mf|on84s5K2eB)VG5idAU=_gfnf?KOg=$;A{PV06jm6&LVO|{1H%-M`3wvVL4T_q zSN?Bin0SCW{u;8o9le$AE!hf&i1lPX;FW zYx#Mp3?=3H3?&&wsi_S4B^jwj464Ok;JA{3h6XYX3Mx<>fwB__gUUCMG7uX?!!RhG zc^DWO1i&VNML|gr%ors`Ltr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ON zU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU z1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E1% z9s($P;;C->sDYy)Fd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF z0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71* zAut*OqaiRF0(e6JbpJD61*4LqAut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@? z8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O zqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8Umvs zFd71*Aut*OqaiRF0;3@?8Ul0)0R;vIh5!Zzh7bk@21Y1`SiZb&`(!er|@eq9=ab|`eoD3`s4Gl~T49xMl8S$wVsmY}!sqq=9i7BZ? z44k=%nRyJn6%0ZQKR9QAjAKw|U|`^5WMG)U3UM3A7=A_ph|vrT3}Orn4E&4?3_n;P z@-iSk8v_GF1B5RR@ekN6kR}!e1_lsTV2H=!fOxP2;wu@CrmFSU-foXho#m9u-@p30?c>xm=7l