private static void CompileShader(ShaderGroup output, ZScriptTokenizer t) { t.SkipWhitespace(); ZScriptToken token = t.ExpectToken(ZScriptTokenType.Identifier); if (!(token?.IsValid ?? true)) { throw new ShaderCompileException("Expected shader identifier, got {0}", token?.ToString() ?? "<EOF>"); } t.SkipWhitespace(); Shader s = new Shader(output); s.Name = token.Value; output.Shaders.Add(s); token = t.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.OpenCurly); if (!(token?.IsValid ?? true)) { throw new ShaderCompileException("Expected parent identifier or shader block, got {0}", token?.ToString() ?? "<EOF>"); } // has parent shader id if (token.Type == ZScriptTokenType.Identifier) { if (token.Value != "extends") { throw new ShaderCompileException("Expected 'extends', got {0}", token.ToString()); } t.SkipWhitespace(); token = t.ExpectToken(ZScriptTokenType.Identifier); if (!(token?.IsValid ?? true)) { throw new ShaderCompileException("Expected parent identifier, got {0}", token?.ToString() ?? "<EOF>"); } s.ParentName = token.Value; t.SkipWhitespace(); token = t.ExpectToken(ZScriptTokenType.OpenCurly); if (!(token?.IsValid ?? true)) { throw new ShaderCompileException("Expected shader block, got {0}", token?.ToString() ?? "<EOF>"); } } s.CodeLine = t.PositionToLine(token.Position); while (true) { t.SkipWhitespace(); token = t.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.CloseCurly); if (!(token?.IsValid ?? true)) { throw new ShaderCompileException("Expected shader sub-block or end of block, got {0}", token?.ToString() ?? "<EOF>"); } if (token.Type == ZScriptTokenType.CloseCurly) { break; } switch (token.Value) { case "in": s.In = CompileShaderDataBlock(t); break; case "out": s.Out = CompileShaderDataBlock(t); break; case "v2f": s.V2F = CompileShaderDataBlock(t); break; case "functions": CompileShaderFunctions(s, t); break; case "vertex": s.SourceVertex = CompileShaderSource(t); if (s.SourceVertex != null && s.SourceVertex.Count > 0) { s.SourceVertexLine = t.PositionToLine(s.SourceVertex[0].Position); } break; case "fragment": s.SourceFragment = CompileShaderSource(t); if (s.SourceFragment != null && s.SourceFragment.Count > 0) { s.SourceFragmentLine = t.PositionToLine(s.SourceFragment[0].Position); } break; default: throw new ShaderCompileException("Expected shader sub-block, got {0}", token.ToString()); } } }
private static void CompileShaderFunctions(Shader output, ZScriptTokenizer t) { t.SkipWhitespace(); ZScriptToken token; token = t.ExpectToken(ZScriptTokenType.OpenCurly); if (!(token?.IsValid ?? true)) { throw new ShaderCompileException("Expected functions block, got {0}", token?.ToString() ?? "<EOF>"); } while (true) { t.SkipWhitespace(); token = t.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.CloseCurly); if (!(token?.IsValid ?? true)) { throw new ShaderCompileException("Expected function or end of block, got {0}", token?.ToString() ?? "<EOF>"); } if (token.Type == ZScriptTokenType.CloseCurly) { break; // done reading functions } bool isoverride = false; if (token.Value == "override") { isoverride = true; t.SkipWhitespace(); token = t.ExpectToken(ZScriptTokenType.Identifier); if (!(token?.IsValid ?? true)) { throw new ShaderCompileException("Expected function return type, got {0}", token?.ToString() ?? "<EOF>"); } } // <return value> <name> (<arguments>) { <code> } ShaderFunction func = new ShaderFunction(); func.Line = t.PositionToLine(token.Position); func.ReturnValue = token.Value; func.Override = isoverride; CompileShaderFunction(func, t); // check if function with such name already exists in the shader // delete it. // overloading is not supported for now if (!isoverride) { ShaderFunction existingFunc = output.Group.GetFunction(func.Name); if (existingFunc != null) { throw new ShaderCompileException("Function {0} is double-defined without 'override' keyword! (previous declaration at line {1})", func.Name, existingFunc.Line); } } for (int i = 0; i < output.Functions.Count; i++) { if (output.Functions[i].Name == func.Name) { if (!isoverride) { throw new ShaderCompileException("Function {0} is double-defined without 'override' keyword! (previous declaration at line {1})", func.Name, output.Functions[i].Line); } output.Functions.RemoveAt(i); i--; continue; } } output.Functions.Add(func); } }
// this is to implement partial parsing. it counts {}, (), and []. it stops parsing at the specified type, if outside of nesting. // also if last block token equals to the type, it will stop at the outer level. (i.e. last }) private static List <ZScriptToken> ReadEverythingUntil(ZScriptTokenizer t, ZScriptTokenType type, bool eofIsOk, bool skipWhitespace) { List <ZScriptToken> tokens = new List <ZScriptToken>(); int levelCurly = 0; int levelSquare = 0; int levelParen = 0; while (true) { if (skipWhitespace) { t.SkipWhitespace(); } long cpos = t.Reader.BaseStream.Position; ZScriptToken token = t.ReadToken(); if (token == null) { if (!eofIsOk) { throw new ShaderCompileException("Expected {0} or token, got <EOF>", type); } break; } // if this is the end token, don't check anything -- just return if (levelCurly == 0 && levelSquare == 0 && levelParen == 0 && token.Type == type) { // rewind and return token list t.Reader.BaseStream.Position = cpos; break; } switch (token.Type) { case ZScriptTokenType.OpenCurly: levelCurly++; break; case ZScriptTokenType.CloseCurly: levelCurly--; break; case ZScriptTokenType.OpenParen: levelParen++; break; case ZScriptTokenType.CloseParen: levelParen--; break; case ZScriptTokenType.OpenSquare: levelSquare++; break; case ZScriptTokenType.CloseSquare: levelSquare--; break; } tokens.Add(token); } return(tokens); }
private static void CompileShaderField(ShaderField field, ZScriptTokenizer t) { ZScriptToken token; // read name and array dimensions while (true) { t.SkipWhitespace(); token = t.ExpectToken(ZScriptTokenType.OpenSquare, ZScriptTokenType.Identifier); if (!(token?.IsValid ?? true)) { throw new ShaderCompileException("Expected array dimensions or field name, got {0}", token?.ToString() ?? "<EOF>"); } // array finished if (token.Type == ZScriptTokenType.Identifier) { field.Name = token.Value; break; } // read array List <ZScriptToken> arrayDimTokens = ReadEverythingUntil(t, ZScriptTokenType.CloseSquare, false, false); if (field.ArrayDimensions == null) { field.ArrayDimensions = new List <List <ZScriptToken> >(); } field.ArrayDimensions.Add(arrayDimTokens); token = t.ExpectToken(ZScriptTokenType.CloseSquare); if (!(token?.IsValid ?? true)) { throw new ShaderCompileException("Expected closing square brace, got {0}", token?.ToString() ?? "<EOF>"); } } // read additional array dimensions if present, and initializer. or end parsing while (true) { t.SkipWhitespace(); token = t.ExpectToken(ZScriptTokenType.OpenSquare, ZScriptTokenType.OpAssign, ZScriptTokenType.Semicolon); if (!(token?.IsValid ?? true)) { throw new ShaderCompileException("Expected array dimensions, initializer or semicolon, got {0}", token?.ToString() ?? "<EOF>"); } // field is done if (token.Type == ZScriptTokenType.Semicolon) { break; } // has initializer if (token.Type == ZScriptTokenType.OpAssign) { field.Initializer = ReadEverythingUntil(t, ZScriptTokenType.Semicolon, false, false); token = t.ExpectToken(ZScriptTokenType.Semicolon); if (!(token?.IsValid ?? true)) { throw new ShaderCompileException("Expected semicolon, got {0}", token?.ToString() ?? "<EOF>"); } break; } // read array List <ZScriptToken> arrayDimTokens = ReadEverythingUntil(t, ZScriptTokenType.CloseSquare, false, false); if (field.ArrayDimensions == null) { field.ArrayDimensions = new List <List <ZScriptToken> >(); } field.ArrayDimensions.Add(arrayDimTokens); token = t.ExpectToken(ZScriptTokenType.CloseSquare); if (!(token?.IsValid ?? true)) { throw new ShaderCompileException("Expected closing square brace, got {0}", token?.ToString() ?? "<EOF>"); } } }
public static ShaderGroup Compile(string src) { ShaderGroup output = new ShaderGroup(); using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(src))) using (BinaryReader br = new BinaryReader(ms)) { ZScriptTokenizer t = new ZScriptTokenizer(br); // main cycle // in the root scope, we allow three blocks: // - uniforms{} // - functions{} // - shader <name> {} // everything else is a syntax error. while (true) { t.SkipWhitespace(); ZScriptToken token = t.ExpectToken(ZScriptTokenType.Identifier); if (token == null) { break; } if (!token.IsValid) { throw new ShaderCompileException("Expected 'uniforms', 'functions', or 'shader'; got {0}", token.ToString()); } switch (token.Value) { case "uniforms": CompileUniforms(output, t); break; case "functions": CompileFunctions(output, t); break; case "shader": CompileShader(output, t); break; default: throw new ShaderCompileException("Expected 'uniforms', 'functions', or 'shader'; got {0}", token.ToString()); } } // done parsing, postprocess - apply parents foreach (Shader s in output.Shaders) { List <string> parents = new List <string>(); parents.Add(s.Name); Shader p = s; while (p.ParentName != null && p.ParentName != "") { string parentName = p.ParentName; if (parents.Contains(parentName)) { throw new ShaderCompileException("Recursive parent shader {0} found", parentName); } parents.Add(parentName); p = output.GetShader(parentName); if (p == null) { throw new ShaderCompileException("Parent shader {0} not found", parentName); } if (s.In == null) { s.In = p.In; } if (s.Out == null) { s.Out = p.Out; } if (s.V2F == null) { s.V2F = p.V2F; } if (s.SourceFragment == null) { s.SourceFragment = p.SourceFragment; s.SourceFragmentLine = p.SourceFragmentLine; } if (s.SourceVertex == null) { s.SourceVertex = p.SourceVertex; s.SourceVertexLine = p.SourceVertexLine; } // add functions from parent foreach (ShaderFunction func in p.Functions) { if (s.GetFunction(func.Name) == null) { s.Functions.Add(func); } } } } return(output); } }