From b00666df22f8c6cf5fa7d4686193009d18d6afb6 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sat, 4 Jul 2020 23:47:23 +0900 Subject: [PATCH] shader: Refactoring: Split files --- internal/shader/expr.go | 420 ++++++++++++++++++++++++++ internal/shader/shader.go | 612 +------------------------------------- internal/shader/stmt.go | 243 +++++++++++++++ 3 files changed, 664 insertions(+), 611 deletions(-) create mode 100644 internal/shader/expr.go create mode 100644 internal/shader/stmt.go diff --git a/internal/shader/expr.go b/internal/shader/expr.go new file mode 100644 index 000000000..1dd5cada7 --- /dev/null +++ b/internal/shader/expr.go @@ -0,0 +1,420 @@ +// Copyright 2020 The Ebiten Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shader + +import ( + "fmt" + "go/ast" + gconstant "go/constant" + "go/token" + + "github.com/hajimehoshi/ebiten/internal/shaderir" +) + +func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr, []shaderir.Type, []shaderir.Stmt, bool) { + switch e := expr.(type) { + case *ast.BasicLit: + switch e.Kind { + case token.INT: + return []shaderir.Expr{ + { + Type: shaderir.NumberExpr, + Const: gconstant.MakeFromLiteral(e.Value, e.Kind, 0), + }, + }, []shaderir.Type{{Main: shaderir.Int}}, nil, true + case token.FLOAT: + return []shaderir.Expr{ + { + Type: shaderir.NumberExpr, + Const: gconstant.MakeFromLiteral(e.Value, e.Kind, 0), + }, + }, []shaderir.Type{{Main: shaderir.Float}}, nil, true + default: + cs.addError(e.Pos(), fmt.Sprintf("literal not implemented: %#v", e)) + } + case *ast.BinaryExpr: + var stmts []shaderir.Stmt + + // Prase LHS first for the order of the statements. + lhs, ts, ss, ok := cs.parseExpr(block, e.X) + if !ok { + return nil, nil, nil, false + } + if len(lhs) != 1 { + cs.addError(e.Pos(), fmt.Sprintf("multiple-value context is not available at a binary operator: %s", e.X)) + return nil, nil, nil, false + } + stmts = append(stmts, ss...) + lhst := ts[0] + + rhs, ts, ss, ok := cs.parseExpr(block, e.Y) + if !ok { + return nil, nil, nil, false + } + if len(rhs) != 1 { + cs.addError(e.Pos(), fmt.Sprintf("multiple-value context is not available at a binary operator: %s", e.Y)) + return nil, nil, nil, false + } + stmts = append(stmts, ss...) + rhst := ts[0] + + if lhs[0].Type == shaderir.NumberExpr && rhs[0].Type == shaderir.NumberExpr { + op := e.Op + // https://golang.org/pkg/go/constant/#BinaryOp + // "To force integer division of Int operands, use op == token.QUO_ASSIGN instead of + // token.QUO; the result is guaranteed to be Int in this case." + if op == token.QUO && lhs[0].Const.Kind() == gconstant.Int && rhs[0].Const.Kind() == gconstant.Int { + op = token.QUO_ASSIGN + } + v := gconstant.BinaryOp(lhs[0].Const, op, rhs[0].Const) + t := shaderir.Type{Main: shaderir.Int} + if v.Kind() == gconstant.Float { + t = shaderir.Type{Main: shaderir.Float} + } + return []shaderir.Expr{ + { + Type: shaderir.NumberExpr, + Const: v, + }, + }, []shaderir.Type{t}, stmts, true + } + + var t shaderir.Type + switch { + case lhst.Equal(&rhst): + t = lhst + case lhst.Main == shaderir.Float || lhst.Main == shaderir.Int: + switch rhst.Main { + case shaderir.Int: + t = lhst + case shaderir.Float, shaderir.Vec2, shaderir.Vec3, shaderir.Vec4, shaderir.Mat2, shaderir.Mat3, shaderir.Mat4: + t = rhst + default: + cs.addError(e.Pos(), fmt.Sprintf("types don't match: %s %s %s", lhst.String(), e.Op, rhst.String())) + return nil, nil, nil, false + } + case rhst.Main == shaderir.Float || rhst.Main == shaderir.Int: + switch lhst.Main { + case shaderir.Int: + t = rhst + case shaderir.Float, shaderir.Vec2, shaderir.Vec3, shaderir.Vec4, shaderir.Mat2, shaderir.Mat3, shaderir.Mat4: + t = lhst + default: + cs.addError(e.Pos(), fmt.Sprintf("types don't match: %s %s %s", lhst.String(), e.Op, rhst.String())) + return nil, nil, nil, false + } + case lhst.Main == shaderir.Vec2 && rhst.Main == shaderir.Mat2 || + lhst.Main == shaderir.Mat2 && rhst.Main == shaderir.Vec2: + t = shaderir.Type{Main: shaderir.Vec2} + case lhst.Main == shaderir.Vec3 && rhst.Main == shaderir.Mat3 || + lhst.Main == shaderir.Mat3 && rhst.Main == shaderir.Vec3: + t = shaderir.Type{Main: shaderir.Vec3} + case lhst.Main == shaderir.Vec4 && rhst.Main == shaderir.Mat4 || + lhst.Main == shaderir.Mat4 && rhst.Main == shaderir.Vec4: + t = shaderir.Type{Main: shaderir.Vec4} + default: + cs.addError(e.Pos(), fmt.Sprintf("invalid expression: %s %s %s", lhst.String(), e.Op, rhst.String())) + return nil, nil, nil, false + } + + op, ok := shaderir.OpFromToken(e.Op) + if !ok { + cs.addError(e.Pos(), fmt.Sprintf("unexpected operator: %s", e.Op)) + return nil, nil, nil, false + } + + return []shaderir.Expr{ + { + Type: shaderir.Binary, + Op: op, + Exprs: []shaderir.Expr{lhs[0], rhs[0]}, + }, + }, []shaderir.Type{t}, stmts, true + case *ast.CallExpr: + var ( + callee shaderir.Expr + args []shaderir.Expr + argts []shaderir.Type + stmts []shaderir.Stmt + ) + + // Parse the argument first for the order of the statements. + for _, a := range e.Args { + es, ts, ss, ok := cs.parseExpr(block, a) + if !ok { + return nil, nil, nil, false + } + if len(es) > 1 && len(e.Args) > 1 { + cs.addError(e.Pos(), fmt.Sprintf("single-value context and multiple-value context cannot be mixed: %s", e.Fun)) + return nil, nil, nil, false + } + args = append(args, es...) + argts = append(argts, ts...) + stmts = append(stmts, ss...) + } + + // TODO: When len(ss) is not 0? + es, _, ss, ok := cs.parseExpr(block, e.Fun) + if !ok { + return nil, nil, nil, false + } + if len(es) != 1 { + cs.addError(e.Pos(), fmt.Sprintf("multiple-value context is not available at a callee: %s", e.Fun)) + return nil, nil, nil, false + } + callee = es[0] + stmts = append(stmts, ss...) + + // For built-in functions, we can call this in this position. Return an expression for the function + // call. + if callee.Type == shaderir.BuiltinFuncExpr { + var t shaderir.Type + switch callee.BuiltinFunc { + case shaderir.Vec2F: + t = shaderir.Type{Main: shaderir.Vec2} + case shaderir.Vec3F: + t = shaderir.Type{Main: shaderir.Vec3} + case shaderir.Vec4F: + t = shaderir.Type{Main: shaderir.Vec4} + case shaderir.Mat2F: + t = shaderir.Type{Main: shaderir.Mat2} + case shaderir.Mat3F: + t = shaderir.Type{Main: shaderir.Mat3} + case shaderir.Mat4F: + t = shaderir.Type{Main: shaderir.Mat4} + case shaderir.Step: + t = argts[1] + case shaderir.Smoothstep: + t = argts[2] + case shaderir.Length, shaderir.Distance, shaderir.Dot: + t = shaderir.Type{Main: shaderir.Float} + case shaderir.Cross: + t = shaderir.Type{Main: shaderir.Vec3} + case shaderir.Texture2DF: + t = shaderir.Type{Main: shaderir.Vec4} + default: + t = argts[0] + } + return []shaderir.Expr{ + { + Type: shaderir.Call, + Exprs: append([]shaderir.Expr{callee}, args...), + }, + }, []shaderir.Type{t}, stmts, true + } + + if callee.Type != shaderir.FunctionExpr { + cs.addError(e.Pos(), fmt.Sprintf("function callee must be a funciton name but %s", e.Fun)) + return nil, nil, nil, false + } + + f := cs.funcs[callee.Index] + + var outParams []int + for _, p := range f.ir.OutParams { + idx := len(block.vars) + block.vars = append(block.vars, variable{ + typ: p, + }) + args = append(args, shaderir.Expr{ + Type: shaderir.LocalVariable, + Index: idx, + }) + outParams = append(outParams, idx) + } + + if t := f.ir.Return; t.Main != shaderir.None { + if len(outParams) != 0 { + cs.addError(e.Pos(), fmt.Sprintf("a function returning value cannot have out-params so far: %s", e.Fun)) + return nil, nil, nil, false + } + + idx := len(block.vars) + block.vars = append(block.vars, variable{ + typ: t, + }) + + // Calling the function should be done eariler to treat out-params correctly. + stmts = append(stmts, shaderir.Stmt{ + Type: shaderir.Assign, + Exprs: []shaderir.Expr{ + { + Type: shaderir.LocalVariable, + Index: idx, + }, + { + Type: shaderir.Call, + Exprs: append([]shaderir.Expr{callee}, args...), + }, + }, + }) + + // The actual expression here is just a local variable that includes the result of the + // function call. + return []shaderir.Expr{ + { + Type: shaderir.LocalVariable, + Index: idx, + }, + }, []shaderir.Type{t}, stmts, true + } + + // Even if the function doesn't return anything, calling the function should be done eariler to keep + // the evaluation order. + stmts = append(stmts, shaderir.Stmt{ + Type: shaderir.ExprStmt, + Exprs: []shaderir.Expr{ + { + Type: shaderir.Call, + Exprs: append([]shaderir.Expr{callee}, args...), + }, + }, + }) + + if len(outParams) == 0 { + // TODO: Is this an error? + } + + var exprs []shaderir.Expr + for _, p := range outParams { + exprs = append(exprs, shaderir.Expr{ + Type: shaderir.LocalVariable, + Index: p, + }) + } + return exprs, f.ir.OutParams, stmts, true + + case *ast.Ident: + if i, t, ok := block.findLocalVariable(e.Name); ok { + return []shaderir.Expr{ + { + Type: shaderir.LocalVariable, + Index: i, + }, + }, []shaderir.Type{t}, nil, true + } + if i, ok := cs.findFunction(e.Name); ok { + return []shaderir.Expr{ + { + Type: shaderir.FunctionExpr, + Index: i, + }, + }, nil, nil, true + } + if i, ok := cs.findUniformVariable(e.Name); ok { + return []shaderir.Expr{ + { + Type: shaderir.UniformVariable, + Index: i, + }, + }, []shaderir.Type{cs.ir.Uniforms[i]}, nil, true + } + if f, ok := shaderir.ParseBuiltinFunc(e.Name); ok { + return []shaderir.Expr{ + { + Type: shaderir.BuiltinFuncExpr, + BuiltinFunc: f, + }, + }, nil, nil, true + } + cs.addError(e.Pos(), fmt.Sprintf("unexpected identifier: %s", e.Name)) + + case *ast.ParenExpr: + return cs.parseExpr(block, e.X) + + case *ast.SelectorExpr: + exprs, _, stmts, ok := cs.parseExpr(block, e.X) + if !ok { + return nil, nil, nil, false + } + if len(exprs) != 1 { + cs.addError(e.Pos(), fmt.Sprintf("multiple-value context is not available at a selector: %s", e.X)) + return nil, nil, nil, false + } + var t shaderir.Type + switch len(e.Sel.Name) { + case 1: + t.Main = shaderir.Float + case 2: + t.Main = shaderir.Vec2 + case 3: + t.Main = shaderir.Vec3 + case 4: + t.Main = shaderir.Vec4 + default: + cs.addError(e.Pos(), fmt.Sprintf("unexpected swizzling: %s", e.Sel.Name)) + return nil, nil, nil, false + } + return []shaderir.Expr{ + { + Type: shaderir.FieldSelector, + Exprs: []shaderir.Expr{ + exprs[0], + { + Type: shaderir.SwizzlingExpr, + Swizzling: e.Sel.Name, + }, + }, + }, + }, []shaderir.Type{t}, stmts, true + + case *ast.UnaryExpr: + exprs, t, stmts, ok := cs.parseExpr(block, e.X) + if !ok { + return nil, nil, nil, false + } + if len(exprs) != 1 { + cs.addError(e.Pos(), fmt.Sprintf("multiple-value context is not available at a unary operator: %s", e.X)) + return nil, nil, nil, false + } + + if exprs[0].Type == shaderir.NumberExpr { + v := gconstant.UnaryOp(e.Op, exprs[0].Const, 0) + t := shaderir.Type{Main: shaderir.Int} + if v.Kind() == gconstant.Float { + t = shaderir.Type{Main: shaderir.Float} + } + return []shaderir.Expr{ + { + Type: shaderir.NumberExpr, + Const: v, + }, + }, []shaderir.Type{t}, stmts, true + } + + var op shaderir.Op + switch e.Op { + case token.ADD: + op = shaderir.Add + case token.SUB: + op = shaderir.Sub + case token.NOT: + op = shaderir.NotOp + default: + cs.addError(e.Pos(), fmt.Sprintf("unexpected operator: %s", e.Op)) + return nil, nil, nil, false + } + return []shaderir.Expr{ + { + Type: shaderir.Unary, + Op: op, + Exprs: exprs, + }, + }, t, stmts, true + default: + cs.addError(e.Pos(), fmt.Sprintf("expression not implemented: %#v", e)) + } + return nil, nil, nil, false +} diff --git a/internal/shader/shader.go b/internal/shader/shader.go index db86d8171..a8403a347 100644 --- a/internal/shader/shader.go +++ b/internal/shader/shader.go @@ -17,7 +17,6 @@ package shader import ( "fmt" "go/ast" - gconstant "go/constant" "go/token" "strings" @@ -622,619 +621,10 @@ func (cs *compileState) parseBlock(outer *block, b *ast.BlockStmt, inParams, out }() for _, l := range b.List { - switch l := l.(type) { - case *ast.AssignStmt: - switch l.Tok { - case token.DEFINE: - if len(l.Lhs) != len(l.Rhs) && len(l.Rhs) != 1 { - cs.addError(l.Pos(), fmt.Sprintf("single-value context and multiple-value context cannot be mixed")) - return nil, false - } - - if !cs.assign(block, l.Pos(), l.Lhs, l.Rhs, true) { - return nil, false - } - case token.ASSIGN: - // TODO: What about the statement `a,b = b,a?` - if len(l.Lhs) != len(l.Rhs) && len(l.Rhs) != 1 { - cs.addError(l.Pos(), fmt.Sprintf("single-value context and multiple-value context cannot be mixed")) - return nil, false - } - if !cs.assign(block, l.Pos(), l.Lhs, l.Rhs, false) { - return nil, false - } - case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN: - var op shaderir.Op - switch l.Tok { - case token.ADD_ASSIGN: - op = shaderir.Add - case token.SUB_ASSIGN: - op = shaderir.Sub - case token.MUL_ASSIGN: - op = shaderir.Mul - case token.QUO_ASSIGN: - op = shaderir.Div - case token.REM_ASSIGN: - op = shaderir.ModOp - } - rhs, _, stmts, ok := cs.parseExpr(block, l.Rhs[0]) - if !ok { - return nil, false - } - block.ir.Stmts = append(block.ir.Stmts, stmts...) - lhs, _, stmts, ok := cs.parseExpr(block, l.Lhs[0]) - if !ok { - return nil, false - } - block.ir.Stmts = append(block.ir.Stmts, stmts...) - block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ - Type: shaderir.Assign, - Exprs: []shaderir.Expr{ - lhs[0], - { - Type: shaderir.Binary, - Op: op, - Exprs: []shaderir.Expr{ - lhs[0], - rhs[0], - }, - }, - }, - }) - default: - cs.addError(l.Pos(), fmt.Sprintf("unexpected token: %s", l.Tok)) - } - case *ast.BlockStmt: - b, ok := cs.parseBlock(block, l, nil, nil) - if !ok { - return nil, false - } - block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ - Type: shaderir.BlockStmt, - Blocks: []shaderir.Block{ - b.ir, - }, - }) - case *ast.DeclStmt: - if !cs.parseDecl(block, l.Decl) { - return nil, false - } - case *ast.ReturnStmt: - for i, r := range l.Results { - exprs, _, stmts, ok := cs.parseExpr(block, r) - if !ok { - return nil, false - } - block.ir.Stmts = append(block.ir.Stmts, stmts...) - if len(exprs) == 0 { - continue - } - if len(exprs) > 1 { - cs.addError(r.Pos(), "multiple-context with return is not implemented yet") - continue - } - block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ - Type: shaderir.Assign, - Exprs: []shaderir.Expr{ - { - Type: shaderir.LocalVariable, - Index: len(inParams) + i, - }, - exprs[0], - }, - }) - } - block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ - Type: shaderir.Return, - }) - case *ast.ExprStmt: - exprs, _, stmts, ok := cs.parseExpr(block, l.X) - if !ok { - return nil, false - } - block.ir.Stmts = append(block.ir.Stmts, stmts...) - for _, expr := range exprs { - if expr.Type != shaderir.Call { - continue - } - block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ - Type: shaderir.ExprStmt, - Exprs: []shaderir.Expr{expr}, - }) - } - default: - cs.addError(l.Pos(), fmt.Sprintf("unexpected statement: %#v", l)) + if !cs.parseStmt(block, l, inParams) { return nil, false } } return block, true } - -func (cs *compileState) assign(block *block, pos token.Pos, lhs, rhs []ast.Expr, define bool) bool { - var rhsExprs []shaderir.Expr - var rhsTypes []shaderir.Type - - for i, e := range lhs { - if len(lhs) == len(rhs) { - // Prase RHS first for the order of the statements. - r, origts, stmts, ok := cs.parseExpr(block, rhs[i]) - if !ok { - return false - } - block.ir.Stmts = append(block.ir.Stmts, stmts...) - - if define { - v := variable{ - name: e.(*ast.Ident).Name, - } - ts, ok := cs.functionReturnTypes(block, rhs[i]) - if !ok { - ts = origts - } - if len(ts) > 1 { - cs.addError(pos, fmt.Sprintf("single-value context and multiple-value context cannot be mixed")) - return false - } - if len(ts) == 1 { - v.typ = ts[0] - } - block.vars = append(block.vars, v) - } - - if len(r) > 1 { - cs.addError(pos, fmt.Sprintf("single-value context and multiple-value context cannot be mixed")) - return false - } - - l, _, stmts, ok := cs.parseExpr(block, lhs[i]) - if !ok { - return false - } - block.ir.Stmts = append(block.ir.Stmts, stmts...) - - if r[0].Type == shaderir.NumberExpr { - switch block.vars[l[0].Index].typ.Main { - case shaderir.Int: - r[0].ConstType = shaderir.ConstTypeInt - case shaderir.Float: - r[0].ConstType = shaderir.ConstTypeFloat - } - } - - block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ - Type: shaderir.Assign, - Exprs: []shaderir.Expr{l[0], r[0]}, - }) - } else { - if i == 0 { - var stmts []shaderir.Stmt - var ok bool - rhsExprs, rhsTypes, stmts, ok = cs.parseExpr(block, rhs[0]) - if !ok { - return false - } - if len(rhsExprs) != len(lhs) { - cs.addError(pos, fmt.Sprintf("single-value context and multiple-value context cannot be mixed")) - } - block.ir.Stmts = append(block.ir.Stmts, stmts...) - } - - if define { - v := variable{ - name: e.(*ast.Ident).Name, - } - v.typ = rhsTypes[i] - block.vars = append(block.vars, v) - } - - l, _, stmts, ok := cs.parseExpr(block, lhs[i]) - if !ok { - return false - } - block.ir.Stmts = append(block.ir.Stmts, stmts...) - - block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ - Type: shaderir.Assign, - Exprs: []shaderir.Expr{l[0], rhsExprs[i]}, - }) - } - } - return true -} - -func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr, []shaderir.Type, []shaderir.Stmt, bool) { - switch e := expr.(type) { - case *ast.BasicLit: - switch e.Kind { - case token.INT: - return []shaderir.Expr{ - { - Type: shaderir.NumberExpr, - Const: gconstant.MakeFromLiteral(e.Value, e.Kind, 0), - }, - }, []shaderir.Type{{Main: shaderir.Int}}, nil, true - case token.FLOAT: - return []shaderir.Expr{ - { - Type: shaderir.NumberExpr, - Const: gconstant.MakeFromLiteral(e.Value, e.Kind, 0), - }, - }, []shaderir.Type{{Main: shaderir.Float}}, nil, true - default: - cs.addError(e.Pos(), fmt.Sprintf("literal not implemented: %#v", e)) - } - case *ast.BinaryExpr: - var stmts []shaderir.Stmt - - // Prase LHS first for the order of the statements. - lhs, ts, ss, ok := cs.parseExpr(block, e.X) - if !ok { - return nil, nil, nil, false - } - if len(lhs) != 1 { - cs.addError(e.Pos(), fmt.Sprintf("multiple-value context is not available at a binary operator: %s", e.X)) - return nil, nil, nil, false - } - stmts = append(stmts, ss...) - lhst := ts[0] - - rhs, ts, ss, ok := cs.parseExpr(block, e.Y) - if !ok { - return nil, nil, nil, false - } - if len(rhs) != 1 { - cs.addError(e.Pos(), fmt.Sprintf("multiple-value context is not available at a binary operator: %s", e.Y)) - return nil, nil, nil, false - } - stmts = append(stmts, ss...) - rhst := ts[0] - - if lhs[0].Type == shaderir.NumberExpr && rhs[0].Type == shaderir.NumberExpr { - op := e.Op - // https://golang.org/pkg/go/constant/#BinaryOp - // "To force integer division of Int operands, use op == token.QUO_ASSIGN instead of - // token.QUO; the result is guaranteed to be Int in this case." - if op == token.QUO && lhs[0].Const.Kind() == gconstant.Int && rhs[0].Const.Kind() == gconstant.Int { - op = token.QUO_ASSIGN - } - v := gconstant.BinaryOp(lhs[0].Const, op, rhs[0].Const) - t := shaderir.Type{Main: shaderir.Int} - if v.Kind() == gconstant.Float { - t = shaderir.Type{Main: shaderir.Float} - } - return []shaderir.Expr{ - { - Type: shaderir.NumberExpr, - Const: v, - }, - }, []shaderir.Type{t}, stmts, true - } - - var t shaderir.Type - switch { - case lhst.Equal(&rhst): - t = lhst - case lhst.Main == shaderir.Float || lhst.Main == shaderir.Int: - switch rhst.Main { - case shaderir.Int: - t = lhst - case shaderir.Float, shaderir.Vec2, shaderir.Vec3, shaderir.Vec4, shaderir.Mat2, shaderir.Mat3, shaderir.Mat4: - t = rhst - default: - cs.addError(e.Pos(), fmt.Sprintf("types don't match: %s %s %s", lhst.String(), e.Op, rhst.String())) - return nil, nil, nil, false - } - case rhst.Main == shaderir.Float || rhst.Main == shaderir.Int: - switch lhst.Main { - case shaderir.Int: - t = rhst - case shaderir.Float, shaderir.Vec2, shaderir.Vec3, shaderir.Vec4, shaderir.Mat2, shaderir.Mat3, shaderir.Mat4: - t = lhst - default: - cs.addError(e.Pos(), fmt.Sprintf("types don't match: %s %s %s", lhst.String(), e.Op, rhst.String())) - return nil, nil, nil, false - } - case lhst.Main == shaderir.Vec2 && rhst.Main == shaderir.Mat2 || - lhst.Main == shaderir.Mat2 && rhst.Main == shaderir.Vec2: - t = shaderir.Type{Main: shaderir.Vec2} - case lhst.Main == shaderir.Vec3 && rhst.Main == shaderir.Mat3 || - lhst.Main == shaderir.Mat3 && rhst.Main == shaderir.Vec3: - t = shaderir.Type{Main: shaderir.Vec3} - case lhst.Main == shaderir.Vec4 && rhst.Main == shaderir.Mat4 || - lhst.Main == shaderir.Mat4 && rhst.Main == shaderir.Vec4: - t = shaderir.Type{Main: shaderir.Vec4} - default: - cs.addError(e.Pos(), fmt.Sprintf("invalid expression: %s %s %s", lhst.String(), e.Op, rhst.String())) - return nil, nil, nil, false - } - - op, ok := shaderir.OpFromToken(e.Op) - if !ok { - cs.addError(e.Pos(), fmt.Sprintf("unexpected operator: %s", e.Op)) - return nil, nil, nil, false - } - - return []shaderir.Expr{ - { - Type: shaderir.Binary, - Op: op, - Exprs: []shaderir.Expr{lhs[0], rhs[0]}, - }, - }, []shaderir.Type{t}, stmts, true - case *ast.CallExpr: - var ( - callee shaderir.Expr - args []shaderir.Expr - argts []shaderir.Type - stmts []shaderir.Stmt - ) - - // Parse the argument first for the order of the statements. - for _, a := range e.Args { - es, ts, ss, ok := cs.parseExpr(block, a) - if !ok { - return nil, nil, nil, false - } - if len(es) > 1 && len(e.Args) > 1 { - cs.addError(e.Pos(), fmt.Sprintf("single-value context and multiple-value context cannot be mixed: %s", e.Fun)) - return nil, nil, nil, false - } - args = append(args, es...) - argts = append(argts, ts...) - stmts = append(stmts, ss...) - } - - // TODO: When len(ss) is not 0? - es, _, ss, ok := cs.parseExpr(block, e.Fun) - if !ok { - return nil, nil, nil, false - } - if len(es) != 1 { - cs.addError(e.Pos(), fmt.Sprintf("multiple-value context is not available at a callee: %s", e.Fun)) - return nil, nil, nil, false - } - callee = es[0] - stmts = append(stmts, ss...) - - // For built-in functions, we can call this in this position. Return an expression for the function - // call. - if callee.Type == shaderir.BuiltinFuncExpr { - var t shaderir.Type - switch callee.BuiltinFunc { - case shaderir.Vec2F: - t = shaderir.Type{Main: shaderir.Vec2} - case shaderir.Vec3F: - t = shaderir.Type{Main: shaderir.Vec3} - case shaderir.Vec4F: - t = shaderir.Type{Main: shaderir.Vec4} - case shaderir.Mat2F: - t = shaderir.Type{Main: shaderir.Mat2} - case shaderir.Mat3F: - t = shaderir.Type{Main: shaderir.Mat3} - case shaderir.Mat4F: - t = shaderir.Type{Main: shaderir.Mat4} - case shaderir.Step: - t = argts[1] - case shaderir.Smoothstep: - t = argts[2] - case shaderir.Length, shaderir.Distance, shaderir.Dot: - t = shaderir.Type{Main: shaderir.Float} - case shaderir.Cross: - t = shaderir.Type{Main: shaderir.Vec3} - case shaderir.Texture2DF: - t = shaderir.Type{Main: shaderir.Vec4} - default: - t = argts[0] - } - return []shaderir.Expr{ - { - Type: shaderir.Call, - Exprs: append([]shaderir.Expr{callee}, args...), - }, - }, []shaderir.Type{t}, stmts, true - } - - if callee.Type != shaderir.FunctionExpr { - cs.addError(e.Pos(), fmt.Sprintf("function callee must be a funciton name but %s", e.Fun)) - return nil, nil, nil, false - } - - f := cs.funcs[callee.Index] - - var outParams []int - for _, p := range f.ir.OutParams { - idx := len(block.vars) - block.vars = append(block.vars, variable{ - typ: p, - }) - args = append(args, shaderir.Expr{ - Type: shaderir.LocalVariable, - Index: idx, - }) - outParams = append(outParams, idx) - } - - if t := f.ir.Return; t.Main != shaderir.None { - if len(outParams) != 0 { - cs.addError(e.Pos(), fmt.Sprintf("a function returning value cannot have out-params so far: %s", e.Fun)) - return nil, nil, nil, false - } - - idx := len(block.vars) - block.vars = append(block.vars, variable{ - typ: t, - }) - - // Calling the function should be done eariler to treat out-params correctly. - stmts = append(stmts, shaderir.Stmt{ - Type: shaderir.Assign, - Exprs: []shaderir.Expr{ - { - Type: shaderir.LocalVariable, - Index: idx, - }, - { - Type: shaderir.Call, - Exprs: append([]shaderir.Expr{callee}, args...), - }, - }, - }) - - // The actual expression here is just a local variable that includes the result of the - // function call. - return []shaderir.Expr{ - { - Type: shaderir.LocalVariable, - Index: idx, - }, - }, []shaderir.Type{t}, stmts, true - } - - // Even if the function doesn't return anything, calling the function should be done eariler to keep - // the evaluation order. - stmts = append(stmts, shaderir.Stmt{ - Type: shaderir.ExprStmt, - Exprs: []shaderir.Expr{ - { - Type: shaderir.Call, - Exprs: append([]shaderir.Expr{callee}, args...), - }, - }, - }) - - if len(outParams) == 0 { - // TODO: Is this an error? - } - - var exprs []shaderir.Expr - for _, p := range outParams { - exprs = append(exprs, shaderir.Expr{ - Type: shaderir.LocalVariable, - Index: p, - }) - } - return exprs, f.ir.OutParams, stmts, true - - case *ast.Ident: - if i, t, ok := block.findLocalVariable(e.Name); ok { - return []shaderir.Expr{ - { - Type: shaderir.LocalVariable, - Index: i, - }, - }, []shaderir.Type{t}, nil, true - } - if i, ok := cs.findFunction(e.Name); ok { - return []shaderir.Expr{ - { - Type: shaderir.FunctionExpr, - Index: i, - }, - }, nil, nil, true - } - if i, ok := cs.findUniformVariable(e.Name); ok { - return []shaderir.Expr{ - { - Type: shaderir.UniformVariable, - Index: i, - }, - }, []shaderir.Type{cs.ir.Uniforms[i]}, nil, true - } - if f, ok := shaderir.ParseBuiltinFunc(e.Name); ok { - return []shaderir.Expr{ - { - Type: shaderir.BuiltinFuncExpr, - BuiltinFunc: f, - }, - }, nil, nil, true - } - cs.addError(e.Pos(), fmt.Sprintf("unexpected identifier: %s", e.Name)) - - case *ast.ParenExpr: - return cs.parseExpr(block, e.X) - - case *ast.SelectorExpr: - exprs, _, stmts, ok := cs.parseExpr(block, e.X) - if !ok { - return nil, nil, nil, false - } - if len(exprs) != 1 { - cs.addError(e.Pos(), fmt.Sprintf("multiple-value context is not available at a selector: %s", e.X)) - return nil, nil, nil, false - } - var t shaderir.Type - switch len(e.Sel.Name) { - case 1: - t.Main = shaderir.Float - case 2: - t.Main = shaderir.Vec2 - case 3: - t.Main = shaderir.Vec3 - case 4: - t.Main = shaderir.Vec4 - default: - cs.addError(e.Pos(), fmt.Sprintf("unexpected swizzling: %s", e.Sel.Name)) - return nil, nil, nil, false - } - return []shaderir.Expr{ - { - Type: shaderir.FieldSelector, - Exprs: []shaderir.Expr{ - exprs[0], - { - Type: shaderir.SwizzlingExpr, - Swizzling: e.Sel.Name, - }, - }, - }, - }, []shaderir.Type{t}, stmts, true - - case *ast.UnaryExpr: - exprs, t, stmts, ok := cs.parseExpr(block, e.X) - if !ok { - return nil, nil, nil, false - } - if len(exprs) != 1 { - cs.addError(e.Pos(), fmt.Sprintf("multiple-value context is not available at a unary operator: %s", e.X)) - return nil, nil, nil, false - } - - if exprs[0].Type == shaderir.NumberExpr { - v := gconstant.UnaryOp(e.Op, exprs[0].Const, 0) - t := shaderir.Type{Main: shaderir.Int} - if v.Kind() == gconstant.Float { - t = shaderir.Type{Main: shaderir.Float} - } - return []shaderir.Expr{ - { - Type: shaderir.NumberExpr, - Const: v, - }, - }, []shaderir.Type{t}, stmts, true - } - - var op shaderir.Op - switch e.Op { - case token.ADD: - op = shaderir.Add - case token.SUB: - op = shaderir.Sub - case token.NOT: - op = shaderir.NotOp - default: - cs.addError(e.Pos(), fmt.Sprintf("unexpected operator: %s", e.Op)) - return nil, nil, nil, false - } - return []shaderir.Expr{ - { - Type: shaderir.Unary, - Op: op, - Exprs: exprs, - }, - }, t, stmts, true - default: - cs.addError(e.Pos(), fmt.Sprintf("expression not implemented: %#v", e)) - } - return nil, nil, nil, false -} diff --git a/internal/shader/stmt.go b/internal/shader/stmt.go new file mode 100644 index 000000000..73b9f279d --- /dev/null +++ b/internal/shader/stmt.go @@ -0,0 +1,243 @@ +// Copyright 2020 The Ebiten Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shader + +import ( + "fmt" + "go/ast" + "go/token" + + "github.com/hajimehoshi/ebiten/internal/shaderir" +) + +func (cs *compileState) parseStmt(block *block, stmt ast.Stmt, inParams []variable) bool { + switch stmt := stmt.(type) { + case *ast.AssignStmt: + switch stmt.Tok { + case token.DEFINE: + if len(stmt.Lhs) != len(stmt.Rhs) && len(stmt.Rhs) != 1 { + cs.addError(stmt.Pos(), fmt.Sprintf("single-value context and multiple-value context cannot be mixed")) + return false + } + + if !cs.assign(block, stmt.Pos(), stmt.Lhs, stmt.Rhs, true) { + return false + } + case token.ASSIGN: + // TODO: What about the statement `a,b = b,a?` + if len(stmt.Lhs) != len(stmt.Rhs) && len(stmt.Rhs) != 1 { + cs.addError(stmt.Pos(), fmt.Sprintf("single-value context and multiple-value context cannot be mixed")) + return false + } + if !cs.assign(block, stmt.Pos(), stmt.Lhs, stmt.Rhs, false) { + return false + } + case token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN: + var op shaderir.Op + switch stmt.Tok { + case token.ADD_ASSIGN: + op = shaderir.Add + case token.SUB_ASSIGN: + op = shaderir.Sub + case token.MUL_ASSIGN: + op = shaderir.Mul + case token.QUO_ASSIGN: + op = shaderir.Div + case token.REM_ASSIGN: + op = shaderir.ModOp + } + rhs, _, stmts, ok := cs.parseExpr(block, stmt.Rhs[0]) + if !ok { + return false + } + block.ir.Stmts = append(block.ir.Stmts, stmts...) + lhs, _, stmts, ok := cs.parseExpr(block, stmt.Lhs[0]) + if !ok { + return false + } + block.ir.Stmts = append(block.ir.Stmts, stmts...) + block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ + Type: shaderir.Assign, + Exprs: []shaderir.Expr{ + lhs[0], + { + Type: shaderir.Binary, + Op: op, + Exprs: []shaderir.Expr{ + lhs[0], + rhs[0], + }, + }, + }, + }) + default: + cs.addError(stmt.Pos(), fmt.Sprintf("unexpected token: %s", stmt.Tok)) + } + case *ast.BlockStmt: + b, ok := cs.parseBlock(block, stmt, nil, nil) + if !ok { + return false + } + block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ + Type: shaderir.BlockStmt, + Blocks: []shaderir.Block{ + b.ir, + }, + }) + case *ast.DeclStmt: + if !cs.parseDecl(block, stmt.Decl) { + return false + } + case *ast.ReturnStmt: + for i, r := range stmt.Results { + exprs, _, stmts, ok := cs.parseExpr(block, r) + if !ok { + return false + } + block.ir.Stmts = append(block.ir.Stmts, stmts...) + if len(exprs) == 0 { + continue + } + if len(exprs) > 1 { + cs.addError(r.Pos(), "multiple-context with return is not implemented yet") + continue + } + block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ + Type: shaderir.Assign, + Exprs: []shaderir.Expr{ + { + Type: shaderir.LocalVariable, + Index: len(inParams) + i, + }, + exprs[0], + }, + }) + } + block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ + Type: shaderir.Return, + }) + case *ast.ExprStmt: + exprs, _, stmts, ok := cs.parseExpr(block, stmt.X) + if !ok { + return false + } + block.ir.Stmts = append(block.ir.Stmts, stmts...) + for _, expr := range exprs { + if expr.Type != shaderir.Call { + continue + } + block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ + Type: shaderir.ExprStmt, + Exprs: []shaderir.Expr{expr}, + }) + } + default: + cs.addError(stmt.Pos(), fmt.Sprintf("unexpected statement: %#v", stmt)) + return false + } + return true +} + +func (cs *compileState) assign(block *block, pos token.Pos, lhs, rhs []ast.Expr, define bool) bool { + var rhsExprs []shaderir.Expr + var rhsTypes []shaderir.Type + + for i, e := range lhs { + if len(lhs) == len(rhs) { + // Prase RHS first for the order of the statements. + r, origts, stmts, ok := cs.parseExpr(block, rhs[i]) + if !ok { + return false + } + block.ir.Stmts = append(block.ir.Stmts, stmts...) + + if define { + v := variable{ + name: e.(*ast.Ident).Name, + } + ts, ok := cs.functionReturnTypes(block, rhs[i]) + if !ok { + ts = origts + } + if len(ts) > 1 { + cs.addError(pos, fmt.Sprintf("single-value context and multiple-value context cannot be mixed")) + return false + } + if len(ts) == 1 { + v.typ = ts[0] + } + block.vars = append(block.vars, v) + } + + if len(r) > 1 { + cs.addError(pos, fmt.Sprintf("single-value context and multiple-value context cannot be mixed")) + return false + } + + l, _, stmts, ok := cs.parseExpr(block, lhs[i]) + if !ok { + return false + } + block.ir.Stmts = append(block.ir.Stmts, stmts...) + + if r[0].Type == shaderir.NumberExpr { + switch block.vars[l[0].Index].typ.Main { + case shaderir.Int: + r[0].ConstType = shaderir.ConstTypeInt + case shaderir.Float: + r[0].ConstType = shaderir.ConstTypeFloat + } + } + + block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ + Type: shaderir.Assign, + Exprs: []shaderir.Expr{l[0], r[0]}, + }) + } else { + if i == 0 { + var stmts []shaderir.Stmt + var ok bool + rhsExprs, rhsTypes, stmts, ok = cs.parseExpr(block, rhs[0]) + if !ok { + return false + } + if len(rhsExprs) != len(lhs) { + cs.addError(pos, fmt.Sprintf("single-value context and multiple-value context cannot be mixed")) + } + block.ir.Stmts = append(block.ir.Stmts, stmts...) + } + + if define { + v := variable{ + name: e.(*ast.Ident).Name, + } + v.typ = rhsTypes[i] + block.vars = append(block.vars, v) + } + + l, _, stmts, ok := cs.parseExpr(block, lhs[i]) + if !ok { + return false + } + block.ir.Stmts = append(block.ir.Stmts, stmts...) + + block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ + Type: shaderir.Assign, + Exprs: []shaderir.Expr{l[0], rhsExprs[i]}, + }) + } + } + return true +}