Skip to content

Commit

Permalink
Support switch statements
Browse files Browse the repository at this point in the history
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 laurentlb#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 laurentlb#18.
  • Loading branch information
jwatzman committed Apr 27, 2022
1 parent d658c1c commit 2ff4145
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 2 deletions.
13 changes: 13 additions & 0 deletions src/ast.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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*)
Expand Down Expand Up @@ -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

Expand Down
14 changes: 13 additions & 1 deletion src/parse.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ open Options.Globals
module private ParseImpl =

// TODO: true, false
// TODO: switch case

open FParsec.Primitives
open FParsec.CharParsers
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -316,6 +327,7 @@ module private ParseImpl =
ifStatement
whileLoop
doWhileLoop
switch
verbatim |>> Ast.Verbatim
macro |>> Ast.Verbatim
attribute |>> Ast.Verbatim
Expand Down
9 changes: 9 additions & 0 deletions src/printer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions src/renamer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
env
renExpr env e
renList env renCase cl |> ignore<Env>
env

let rec renTopLevelName env = function
| TLDecl d -> renDecl true env d
Expand Down
2 changes: 1 addition & 1 deletion src/rewriter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions tests/commands.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
45 changes: 45 additions & 0 deletions tests/unit/switch.expected
Original file line number Diff line number Diff line change
@@ -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.;
}
}
42 changes: 42 additions & 0 deletions tests/unit/switch.frag
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit 2ff4145

Please sign in to comment.