]> git.tdb.fi Git - libs/gl.git/commitdiff
Implement the ternary operator in GLSL
authorMikko Rasa <tdb@tdb.fi>
Fri, 12 Mar 2021 18:27:42 +0000 (20:27 +0200)
committerMikko Rasa <tdb@tdb.fi>
Fri, 12 Mar 2021 19:11:27 +0000 (21:11 +0200)
18 files changed:
source/glsl/debug.cpp
source/glsl/debug.h
source/glsl/generate.cpp
source/glsl/generate.h
source/glsl/optimize.cpp
source/glsl/optimize.h
source/glsl/output.cpp
source/glsl/output.h
source/glsl/parser.cpp
source/glsl/parser.h
source/glsl/syntax.cpp
source/glsl/syntax.h
source/glsl/validate.cpp
source/glsl/validate.h
source/glsl/visitor.cpp
source/glsl/visitor.h
tests/glsl/ternary_operand_type_mismatch.glsl [new file with mode: 0644]
tests/glsl/ternary_operator.glsl [new file with mode: 0644]

index 68615ed6e06d67c7491a2e88ce10bce360b1685d..700acbf9cd50a66c8db4e98c54343d8696a332ce 100644 (file)
@@ -240,6 +240,17 @@ void DumpTree::visit(Assignment &assign)
        end_sub();
 }
 
+void DumpTree::visit(TernaryExpression &ternary)
+{
+       append(format("Ternary: %s -> %s", (ternary.oper->token[0]=='?' ? "?:" : ternary.oper->token), format_type(ternary.type)));
+       begin_sub();
+       ternary.condition->visit(*this);
+       ternary.true_expr->visit(*this);
+       last_branch();
+       ternary.false_expr->visit(*this);
+       end_sub();
+}
+
 void DumpTree::visit(FunctionCall &call)
 {
        string head = "Function call: ";
index 3837c569bb18e13aacf3d02a9d1d58eadecd5cb0..a5dae0905c3dec3b5d0b01f644a4da8a18eac4fc 100644 (file)
@@ -62,6 +62,7 @@ private:
        virtual void visit(UnaryExpression &);
        virtual void visit(BinaryExpression &);
        virtual void visit(Assignment &);
+       virtual void visit(TernaryExpression &);
        virtual void visit(FunctionCall &);
        virtual void visit(ExpressionStatement &);
        virtual void visit(Import &);
index ab487a44c470bca769af032b9ce2966c7a0df5d2..e5855e94cacb701c4ab96bec16324335fcccf910 100644 (file)
@@ -872,6 +872,36 @@ void ExpressionResolver::visit(Assignment &assign)
        resolve(assign, assign.left->type, true);
 }
 
+void ExpressionResolver::visit(TernaryExpression &ternary)
+{
+       TraversingVisitor::visit(ternary);
+
+       BasicTypeDeclaration *basic_cond = dynamic_cast<BasicTypeDeclaration *>(ternary.condition->type);
+       if(!basic_cond || basic_cond->kind!=BasicTypeDeclaration::BOOL)
+               return;
+
+       TypeDeclaration *type = 0;
+       if(ternary.true_expr->type==ternary.false_expr->type)
+               type = ternary.true_expr->type;
+       else
+       {
+               BasicTypeDeclaration *basic_true = dynamic_cast<BasicTypeDeclaration *>(ternary.true_expr->type);
+               BasicTypeDeclaration *basic_false = dynamic_cast<BasicTypeDeclaration *>(ternary.false_expr->type);
+               Compatibility compat = get_compatibility(*basic_true, *basic_false);
+               if(compat==NOT_COMPATIBLE)
+                       return;
+
+               type = (compat==LEFT_CONVERTIBLE ? basic_true : basic_false);
+
+               if(compat==LEFT_CONVERTIBLE)
+                       convert_to(ternary.true_expr, *basic_false);
+               else if(compat==RIGHT_CONVERTIBLE)
+                       convert_to(ternary.false_expr, *basic_true);
+       }
+
+       resolve(ternary, type, false);
+}
+
 void ExpressionResolver::visit(FunctionCall &call)
 {
        TraversingVisitor::visit(call);
index c7a162e5c6c7e81f81a30d104f0763e81ff8f0ee..0a940cea5599a8f1381aadf6b9051989ac607a29 100644 (file)
@@ -163,6 +163,7 @@ private:
        void visit(BinaryExpression &, bool);
        virtual void visit(BinaryExpression &);
        virtual void visit(Assignment &);
+       virtual void visit(TernaryExpression &);
        virtual void visit(FunctionCall &);
        virtual void visit(BasicTypeDeclaration &);
        virtual void visit(VariableDeclaration &);
index 9ab8ef70ef94dfc235e08ecee479792f9ae9cf2b..8f6b2745846b0a31e285a746dca714bfa3227332 100644 (file)
@@ -452,6 +452,15 @@ void ExpressionInliner::visit(Assignment &assign)
        r_trivial = false;
 }
 
+void ExpressionInliner::visit(TernaryExpression &ternary)
+{
+       visit_and_record(ternary.condition, ternary.oper, false);
+       visit_and_record(ternary.true_expr, ternary.oper, false);
+       visit_and_record(ternary.false_expr, ternary.oper, true);
+       r_oper = ternary.oper;
+       r_trivial = false;
+}
+
 void ExpressionInliner::visit(FunctionCall &call)
 {
        TraversingVisitor::visit(call);
@@ -581,6 +590,12 @@ void UnusedTypeRemover::visit(BinaryExpression &binary)
        TraversingVisitor::visit(binary);
 }
 
+void UnusedTypeRemover::visit(TernaryExpression &ternary)
+{
+       unused_nodes.erase(ternary.type);
+       TraversingVisitor::visit(ternary);
+}
+
 void UnusedTypeRemover::visit(FunctionCall &call)
 {
        unused_nodes.erase(call.type);
index 34c0b2455bd4e4ac60126b72e53586a016d16425..0604a39c1b24e9224dedffde7323ed3b0e5fa68f 100644 (file)
@@ -135,6 +135,7 @@ private:
        virtual void visit(UnaryExpression &);
        virtual void visit(BinaryExpression &);
        virtual void visit(Assignment &);
+       virtual void visit(TernaryExpression &);
        virtual void visit(FunctionCall &);
        virtual void visit(VariableDeclaration &);
        virtual void visit(Iteration &);
@@ -170,6 +171,7 @@ private:
        virtual void visit(Literal &);
        virtual void visit(UnaryExpression &);
        virtual void visit(BinaryExpression &);
+       virtual void visit(TernaryExpression &);
        virtual void visit(FunctionCall &);
        virtual void visit(BasicTypeDeclaration &);
        virtual void visit(ImageTypeDeclaration &);
index 718b1a8bcde406ec124f7959f314b606361c1d2a..364e5e4b23c02356a2c72743103ba212a32abd91 100644 (file)
@@ -167,6 +167,16 @@ void Formatter::visit(Assignment &assign)
        assign.right->visit(*this);
 }
 
+void Formatter::visit(TernaryExpression &ternary)
+{
+       ternary.condition->visit(*this);
+       append(ternary.oper->token);
+       ternary.true_expr->visit(*this);
+       if(ternary.oper->token[0]=='?')
+               append(':');
+       ternary.false_expr->visit(*this);
+}
+
 void Formatter::visit(FunctionCall &call)
 {
        append(format("%s(", call.name));
index 0a506a5d9e15b2080a392eb5ef628730683d9de7..d872dabe3433dc50b8ef1835e0777fe960f8a2df 100644 (file)
@@ -42,6 +42,7 @@ private:
        virtual void visit(UnaryExpression &);
        virtual void visit(BinaryExpression &);
        virtual void visit(Assignment &);
+       virtual void visit(TernaryExpression &);
        virtual void visit(FunctionCall &);
        virtual void visit(ExpressionStatement &);
        virtual void visit(Import &);
index b4631daf6d1c4e5b3cafa638400bd94e9d157074..0c3a5e35a57a35033024989aa96076b06bd141cd 100644 (file)
@@ -449,7 +449,7 @@ RefPtr<Expression> Parser::parse_expression(const Operator *outer_oper)
                                oper = i;
 
                bool lower_precedence = (oper && oper->type!=Operator::PREFIX && oper->precedence>=outer_precedence);
-               if(token==";" || token==")" || token=="]" || token=="," || lower_precedence)
+               if(token==";" || token==")" || token=="]" || token=="," || token==":" || lower_precedence)
                {
                        if(left)
                                return left;
@@ -483,6 +483,8 @@ RefPtr<Expression> Parser::parse_expression(const Operator *outer_oper)
                        }
                        else if(oper && oper->type==Operator::BINARY)
                                left = parse_binary(left, *oper);
+                       else if(oper && oper->type==Operator::TERNARY)
+                               left = parse_ternary(left, *oper);
                        else
                                throw parse_error(tokenizer.get_location(), token, "an operator");
                        left_var = 0;
@@ -557,6 +559,18 @@ RefPtr<BinaryExpression> Parser::parse_binary(const RefPtr<Expression> &left, co
        return binary;
 }
 
+RefPtr<TernaryExpression> Parser::parse_ternary(const RefPtr<Expression> &cond, const Operator &oper)
+{
+       RefPtr<TernaryExpression> ternary = create_node<TernaryExpression>();
+       ternary->condition = cond;
+       ternary->oper = &oper;
+       tokenizer.expect("?");
+       ternary->true_expr = parse_expression(&oper);
+       tokenizer.expect(":");
+       ternary->false_expr = parse_expression(&oper);
+       return ternary;
+}
+
 RefPtr<FunctionCall> Parser::parse_function_call(const VariableReference &var)
 {
        RefPtr<FunctionCall> call = create_node<FunctionCall>();
index bd5641399557cc4655d818d88fd1e0d85be37a43..c98867a3791e866ecd31a9c7cc5076e7443482cf 100644 (file)
@@ -73,6 +73,7 @@ private:
        RefPtr<Expression> parse_expression(const Operator * = 0);
        RefPtr<Literal> parse_literal();
        RefPtr<BinaryExpression> parse_binary(const RefPtr<Expression> &, const Operator &);
+       RefPtr<TernaryExpression> parse_ternary(const RefPtr<Expression> &, const Operator &);
        RefPtr<FunctionCall> parse_function_call(const VariableReference &);
        RefPtr<TypeDeclaration> parse_type_declaration();
        RefPtr<BasicTypeDeclaration> parse_basic_type_declaration();
index cad7c9f4838962740c2036a6c91ab25517032e24..11a099b5ad3175e94b82f8a75b2877ec5e07c5a9 100644 (file)
@@ -40,8 +40,8 @@ const Operator Operator::operators[] =
        { "&&", 12, BINARY, ASSOCIATIVE },
        { "^^", 13, BINARY, ASSOCIATIVE },
        { "||", 14, BINARY, ASSOCIATIVE },
-       { "?", 15, BINARY, RIGHT_TO_LEFT },
-       { ":", 15, BINARY, RIGHT_TO_LEFT },
+       { "?", 15, TERNARY, RIGHT_TO_LEFT },
+       { ":", 15, TERNARY, RIGHT_TO_LEFT },
        { "=", 16, BINARY, RIGHT_TO_LEFT },
        { "+=", 16, BINARY, RIGHT_TO_LEFT },
        { "-=", 16, BINARY, RIGHT_TO_LEFT },
@@ -218,6 +218,12 @@ bool Assignment::Target::operator<(const Target &other) const
 }
 
 
+void TernaryExpression::visit(NodeVisitor &visitor)
+{
+       visitor.visit(*this);
+}
+
+
 FunctionCall::FunctionCall():
        constructor(false),
        declaration(0)
index 39bc8264e1b8d30ead80ad19ad78f9232a6cd3ae..86972869f2fe03ed2ebdce4bf47efe4e98d1aa5b 100644 (file)
@@ -27,7 +27,8 @@ struct Operator
                NO_OPERATOR,
                BINARY,
                PREFIX,
-               POSTFIX
+               POSTFIX,
+               TERNARY
        };
 
        enum Associativity
@@ -259,6 +260,16 @@ struct Assignment: BinaryExpression
        virtual void visit(NodeVisitor &);
 };
 
+struct TernaryExpression: Expression
+{
+       NodePtr<Expression> condition;
+       NodePtr<Expression> true_expr;
+       NodePtr<Expression> false_expr;
+
+       virtual TernaryExpression *clone() const { return new TernaryExpression(*this); }
+       virtual void visit(NodeVisitor &);
+};
+
 struct FunctionCall: Expression
 {
        std::string name;
index 8805d41fadf01655f7328b60e501ce8c3a8b73d4..6e90698c97b500c5dd2edcaebd7e3f8c70e90026 100644 (file)
@@ -365,6 +365,20 @@ void ExpressionValidator::visit(Assignment &assign)
        TraversingVisitor::visit(assign);
 }
 
+void ExpressionValidator::visit(TernaryExpression &ternary)
+{
+       if(ternary.condition->type)
+       {
+               BasicTypeDeclaration *basic_cond = dynamic_cast<BasicTypeDeclaration *>(ternary.condition->type);
+               if(!basic_cond || basic_cond->kind!=BasicTypeDeclaration::BOOL)
+                       error(ternary, "Ternary operator condition is not a boolean");
+               else if(!ternary.type && ternary.true_expr->type && ternary.false_expr->type)
+                       error(ternary, format("Ternary operator has incompatible types '%s' and '%s'",
+                               ternary.true_expr->type->name, ternary.false_expr->type->name));
+       }
+       TraversingVisitor::visit(ternary);
+}
+
 void ExpressionValidator::visit(VariableDeclaration &var)
 {
        if(var.init_expression && var.init_expression->type && var.type_declaration && var.init_expression->type!=var.type_declaration)
index 35cc049c8d0809edbb007cca63b35d42e560e483..bafa550bd480d2f32ccbac2652e57bbafaac2a80 100644 (file)
@@ -98,6 +98,7 @@ private:
        virtual void visit(UnaryExpression &);
        virtual void visit(BinaryExpression &);
        virtual void visit(Assignment &);
+       virtual void visit(TernaryExpression &);
        virtual void visit(VariableDeclaration &);
 };
 
index b1767d3f5748b2fcafb139e9de48098e92563a13..5da89f54e69b608d1202f13fd2bdbced0a787f72 100644 (file)
@@ -53,6 +53,13 @@ void TraversingVisitor::visit(Assignment &assign)
        visit(assign.right);
 }
 
+void TraversingVisitor::visit(TernaryExpression &ternary)
+{
+       visit(ternary.condition);
+       visit(ternary.true_expr);
+       visit(ternary.false_expr);
+}
+
 void TraversingVisitor::visit(FunctionCall &call)
 {
        for(NodeArray<Expression>::iterator i=call.arguments.begin(); i!=call.arguments.end(); ++i)
index 85f9e848c39b9b45b822c63408ceb6e44a0e5bb9..3b558bd195c5cf6a9f0e68d57127d1d8159aa9b0 100644 (file)
@@ -27,6 +27,7 @@ public:
        virtual void visit(UnaryExpression &) { }
        virtual void visit(BinaryExpression &) { }
        virtual void visit(Assignment &) { }
+       virtual void visit(TernaryExpression &) { }
        virtual void visit(FunctionCall &) { }
        virtual void visit(ExpressionStatement &) { }
        virtual void visit(Import &) { }
@@ -64,6 +65,7 @@ public:
        virtual void visit(UnaryExpression &);
        virtual void visit(BinaryExpression &);
        virtual void visit(Assignment &);
+       virtual void visit(TernaryExpression &);
        virtual void visit(FunctionCall &);
        virtual void visit(ExpressionStatement &);
        virtual void visit(InterfaceLayout &);
diff --git a/tests/glsl/ternary_operand_type_mismatch.glsl b/tests/glsl/ternary_operand_type_mismatch.glsl
new file mode 100644 (file)
index 0000000..5eae47d
--- /dev/null
@@ -0,0 +1,30 @@
+uniform Colors
+{
+       vec4 color;
+       float gray;
+};
+uniform sampler2D mask;
+uniform Transform
+{
+       mat4 mvp;
+} transform;
+
+#pragma MSP stage(vertex)
+layout(location=0) in vec4 position;
+layout(location=1) in vec2 texcoord;
+void main()
+{
+       passthrough;
+       gl_Position = transform.mvp*position;
+}
+
+#pragma MSP stage(fragment)
+layout(location=0) out vec4 frag_color;
+void main()
+{
+       frag_color = texture(mask, texcoord).r > 0.5 ? color : gray;
+}
+
+/* Expected error:
+<test>:25: Ternary operator has incompatible types 'vec4' and 'float'
+*/
diff --git a/tests/glsl/ternary_operator.glsl b/tests/glsl/ternary_operator.glsl
new file mode 100644 (file)
index 0000000..1a1a098
--- /dev/null
@@ -0,0 +1,56 @@
+uniform Colors
+{
+       vec4 color1;
+       vec4 color2;
+};
+uniform sampler2D mask;
+uniform Transform
+{
+       mat4 mvp;
+} transform;
+
+#pragma MSP stage(vertex)
+layout(location=0) in vec4 position;
+layout(location=1) in vec2 texcoord;
+void main()
+{
+       passthrough;
+       gl_Position = transform.mvp*position;
+}
+
+#pragma MSP stage(fragment)
+layout(location=0) out vec4 frag_color;
+void main()
+{
+       frag_color = texture(mask, texcoord).r > 0.5 ? color1 : color2;
+}
+
+/* Expected output: vertex
+uniform Transform
+{
+  mat4 mvp;
+} transform;
+layout(location=0) in vec4 position;
+layout(location=1) in vec2 texcoord;
+out vec2 _vs_out_texcoord;
+void main()
+{
+  _vs_out_texcoord = texcoord;
+  gl_Position = transform.mvp*position;
+}
+*/
+
+/* Expected output: fragment
+uniform Colors
+{
+  vec4 color1;
+  vec4 color2;
+};
+uniform sampler2D mask;
+layout(location=0) out vec4 frag_color;
+in vec2 _vs_out_texcoord;
+void main()
+{
+  frag_color = texture(mask, _vs_out_texcoord).r>0.5?color1:color2;
+}
+*/