From 342adde02620ec255f9792bb690fdcc9d8e8e07d Mon Sep 17 00:00:00 2001 From: Josh Watzman Date: Wed, 27 Apr 2022 17:14:28 +0100 Subject: [PATCH] Support switch statements (#132) This is relatively simple and straightforward. Note that this is much more permissive than GLSL compilers seem to be; for example I'm pretty sure this will allow much more in a case label than it should (I think a literal int must come after). But I don't think it will cause the minifier to emit invalid GLSL unless the input was invalid already. It's actually somewhat unclear what the right grammar even is here. Looking at the 4.5 spec linked in #18 -- section 9 (the normative grammar), page 205 says that a `case_label` is `CASE expression COLON`, but from testing with an actual compiler it looks like it needs to be a literal integer. Also the definition of `switch_statement_list` doesn't include `case_label`. So by my reading that's a bug in the spec (or at least a place that compilers are quite reasonably more restrictive) so even if we did want to be perfectly correct here I'm not sure what exactly that looks like :) Fixes #18. * Also fix newline handling in IndentedText output format This oversight was causing the format to print a literal '\n' instead of a newline. Add the correct pattern match, and also remove the wildcard match to prevent similar bugs in the future. --- src/ast.fs | 13 +++++++++++ src/parse.fs | 14 +++++++++++- src/printer.fs | 13 +++++++++-- src/renamer.fs | 11 ++++++++++ src/rewriter.fs | 2 +- tests/commands.txt | 1 + tests/unit/switch.expected | 45 ++++++++++++++++++++++++++++++++++++++ tests/unit/switch.frag | 42 +++++++++++++++++++++++++++++++++++ 8 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 tests/unit/switch.expected create mode 100644 tests/unit/switch.frag diff --git a/src/ast.fs b/src/ast.fs index df2053c4..22e84a33 100644 --- a/src/ast.fs +++ b/src/ast.fs @@ -82,6 +82,11 @@ and Stmt = | DoWhile of Expr * Stmt | Jump of JumpKeyword * Expr option (*break, continue, return (expr)?, discard*) | Verbatim of string + | Switch of Expr * (CaseLabel * Stmt list) list + +and CaseLabel = + | Case of Expr + | Default and FunctionType = { retType: Type (*return*) @@ -184,6 +189,14 @@ let rec mapStmt env i = | Jump(k, e) -> env, Jump (k, Option.map (mapExpr env) e) | Verbatim _ as v -> env, v + | Switch(e, cl) -> + let mapLabel = function + | Case e -> Case (mapExpr env e) + | Default -> Default + let mapCase (l, sl) = + let _, sl = foldList env mapStmt sl + (mapLabel l, sl) + env, Switch (mapExpr env e, List.map mapCase cl) let env, res = aux i env, env.fStmt res diff --git a/src/parse.fs b/src/parse.fs index 6a2e4aed..5ae8b8aa 100644 --- a/src/parse.fs +++ b/src/parse.fs @@ -5,7 +5,6 @@ open Options.Globals module private ParseImpl = // TODO: true, false - // TODO: switch case open FParsec.Primitives open FParsec.CharParsers @@ -280,6 +279,18 @@ module private ParseImpl = let list = many statement |>> Ast.Block between (ch '{') (ch '}') list + let caseLabel = + let keywCase = keyword "case" >>. expr |>> (fun e -> Ast.Case e) + let keywDefault = keyword "default" |>> (fun _ -> Ast.Default) + attempt keywCase <|> keywDefault .>> ch ':' + + let case = + pipe2 caseLabel (many (attempt statement)) (fun l sl -> (l, sl)) + + let switch = + let body = between (ch '{') (ch '}') (many case) + pipe2 (keyword "switch" >>. parenExp) body (fun e cl -> Ast.Switch(e, cl)) + let mutable private forbiddenNames = [] let macro = @@ -316,6 +327,7 @@ module private ParseImpl = ifStatement whileLoop doWhileLoop + switch verbatim |>> Ast.Verbatim macro |>> Ast.Verbatim attribute |>> Ast.Verbatim diff --git a/src/printer.fs b/src/printer.fs index becb73a3..c315a0e1 100644 --- a/src/printer.fs +++ b/src/printer.fs @@ -121,9 +121,9 @@ module private PrinterImpl = let backslashN() = match outputFormat with - | Options.Text | Options.JS -> "\n" + | Options.Text | Options.JS | Options.IndentedText -> "\n" | Options.Nasm -> "', 10, '" - | _ -> "\\n" + | Options.CHeader | Options.CList -> "\\n" // Print HLSL semantics let semToS sem = @@ -215,6 +215,15 @@ module private PrinterImpl = let s = if System.Char.IsLetterOrDigit s.[s.Length - 1] then s + " " else s if s <> "" && s.[0] = '#' then out "%s%s" (backslashN()) (escape s) else escape s + | Switch(e, cl) -> + let labelToS = function + | Case e -> out "case %s:" (exprToS e) + | Default -> out "default:" + let caseToS (l, sl) = + let stmts = List.map (stmtToS (indent+2)) sl |> String.concat "" + out "%s%s%s" (nl (indent+1)) (labelToS l) stmts + let body = List.map caseToS cl |> String.concat "" + out "%s(%s){%s%s}" "switch" (exprToS e) body (nl indent) and stmtToS indent i = out "%s%s" (nl indent) (stmtToS' indent i) diff --git a/src/renamer.fs b/src/renamer.fs index b13cf57e..5edb23ad 100644 --- a/src/renamer.fs +++ b/src/renamer.fs @@ -311,6 +311,17 @@ module private RenamerImpl = env | Jump(_, e) -> renOpt e; env | Verbatim _ -> env + | Switch(e, cl) -> + let renLabel = function + | Case e -> renExpr env e + | Default -> () + let renCase env (l, sl) = + renLabel l + renList env renStmt sl |> ignore + env + renExpr env e + renList env renCase cl |> ignore + env let rec renTopLevelName env = function | TLDecl d -> renDecl true env d diff --git a/src/rewriter.fs b/src/rewriter.fs index 37f7dd9b..bb79ab71 100644 --- a/src/rewriter.fs +++ b/src/rewriter.fs @@ -204,7 +204,7 @@ let findInlinable block = localDefs.[def.name.Name] <- (def.name, deps.Count > 0) | Expr e | Jump (_, Some e) -> localExpr <- e :: localExpr - | Verbatim _ | Jump (_, None) | Block _ | If _| ForE _ | ForD _ | While _ | DoWhile _ -> () + | Verbatim _ | Jump (_, None) | Block _ | If _| ForE _ | ForD _ | While _ | DoWhile _ | Switch _ -> () let localReferences = collectReferences (List.map Expr localExpr) let allReferences = collectReferences block diff --git a/tests/commands.txt b/tests/commands.txt index 175de79d..c334bd61 100644 --- a/tests/commands.txt +++ b/tests/commands.txt @@ -39,6 +39,7 @@ -o tests/unit/function_overload.expected tests/unit/function_overload.frag -o tests/unit/externals.expected tests/unit/externals.frag -o tests/unit/macros.expected --no-inlining tests/unit/macros.frag +--format indented -o tests/unit/switch.expected tests/unit/switch.frag # Optimization tests diff --git a/tests/unit/switch.expected b/tests/unit/switch.expected new file mode 100644 index 00000000..ac2e4b2b --- /dev/null +++ b/tests/unit/switch.expected @@ -0,0 +1,45 @@ +void h() +{ + switch(42){ + case 42: + break; + } +} + +#define GOOD 42 + +void i() +{ + switch(42){ + case GOOD: + break; + } +} +void h(in int i) +{ + switch(i){ + case 0: + break; + } +} +void i(in int i) +{ + switch(i+42){ + case 0: + break; + } +} +float G() +{ + switch(42){ + case 42: + float i=4.2; + float k=2.4; + return i+k; + case 43: + float i=4.3; + return i+3.4; + default: + return 0.; + } +} diff --git a/tests/unit/switch.frag b/tests/unit/switch.frag new file mode 100644 index 00000000..bae0a7bf --- /dev/null +++ b/tests/unit/switch.frag @@ -0,0 +1,42 @@ +void switchConst() { + switch (42) { + case 42: + break; + } +} + +#define GOOD 42 +void switchDefine() { + switch (42) { + case GOOD: + break; + } +} + +void switchArg(in int someArg) { + switch (someArg) { + case 0: + break; + } +} + +void switchExpr(in int someArg) { + switch (someArg + 42) { + case 0: + break; + } +} + +float switchMultiple() { + switch (42) { + case 42: + float someVar = 4.2; + float otherVar = 2.4; + return someVar + otherVar; + case 43: + float someVar = 4.3; + return someVar + 3.4; + default: + return 0.0; + } +}