/// <see cref="Translator.Translate"/> public override void Translate(ScriptCompiler compiler, AbstractNode node) { throw new NotImplementedException(); #if UNREACHABLE_CODE ObjectAbstractNode obj = (ObjectAbstractNode)node; // It has to have one value identifying the texture source name if (obj.Values.Count == 0) { compiler.AddError(CompileErrorCode.StringExpected, node.File, node.Line, "texture_source requires a type value"); return; } // Set the value of the source //TODO: ExternalTextureSourceManager::getSingleton().setCurrentPlugIn(obj->values.front()->getValue()); // Set up the technique, pass, and texunit levels if (true /*TODO: ExternalTextureSourceManager::getSingleton().getCurrentPlugIn() != 0*/) { TextureUnitState texunit = (TextureUnitState)obj.Parent.Context; Pass pass = texunit.Parent; Technique technique = pass.Parent; Material material = technique.Parent; ushort techniqueIndex = 0, passIndex = 0, texUnitIndex = 0; for (ushort i = 0; i < material.TechniqueCount; i++) { if (material.GetTechnique(i) == technique) { techniqueIndex = i; break; } } for (ushort i = 0; i < technique.PassCount; i++) { if (technique.GetPass(i) == pass) { passIndex = i; break; } } for (ushort i = 0; i < pass.TextureUnitStageCount; i++) { if (pass.GetTextureUnitState(i) == texunit) { texUnitIndex = i; break; } } string tps = string.Format("{0} {1} {2}", techniqueIndex, passIndex, texUnitIndex); //TODO: ExternalTextureSourceManager::getSingleton().getCurrentPlugIn()->setParameter( "set_T_P_S", tps ); foreach (AbstractNode i in obj.Children) { if (i is PropertyAbstractNode) { PropertyAbstractNode prop = (PropertyAbstractNode)i; // Glob the property values all together string str = string.Empty; foreach (AbstractNode j in prop.Values) { if (j != prop.Values[0]) { str += " "; } str = str + j.Value; } //TODO: ExternalTextureSourceManager::getSingleton().getCurrentPlugIn()->setParameter(prop->name, str); } else if (i is ObjectAbstractNode) { _processNode(compiler, i); } } //TODO: ExternalTextureSourceManager::getSingleton().getCurrentPlugIn()->createDefinedTexture(material->getName(), material->getGroup()); } #endif }
private void visit(ConcreteNode node) { AbstractNode asn = null; // Import = "import" >> 2 children, _current == null if (node.Type == ConcreteNodeType.Import && this._current == null) { if (node.Children.Count > 2) { this._compiler.AddError(CompileErrorCode.FewerParametersExpected, node.File, node.Line); return; } if (node.Children.Count < 2) { this._compiler.AddError(CompileErrorCode.StringExpected, node.File, node.Line); return; } var impl = new ImportAbstractNode(); impl.Line = node.Line; impl.File = node.File; impl.Target = node.Children[0].Token; impl.Source = node.Children[1].Token; asn = impl; } // variable set = "set" >> 2 children, children[0] == variable else if (node.Type == ConcreteNodeType.VariableAssignment) { if (node.Children.Count > 2) { this._compiler.AddError(CompileErrorCode.FewerParametersExpected, node.File, node.Line); return; } if (node.Children.Count < 2) { this._compiler.AddError(CompileErrorCode.StringExpected, node.File, node.Line); return; } if (node.Children[0].Type != ConcreteNodeType.Variable) { this._compiler.AddError(CompileErrorCode.VariableExpected, node.Children[0].File, node.Children[0].Line); return; } var name = node.Children[0].Token; var value = node.Children[1].Token; if (this._current != null && this._current is ObjectAbstractNode) { var ptr = (ObjectAbstractNode)this._current; ptr.SetVariable(name, value); } else { this._compiler.Environment.Add(name, value); } } // variable = $*, no children else if (node.Type == ConcreteNodeType.Variable) { if (node.Children.Count != 0) { this._compiler.AddError(CompileErrorCode.FewerParametersExpected, node.File, node.Line); return; } var impl = new VariableGetAbstractNode(this._current); impl.Line = node.Line; impl.File = node.File; impl.Name = node.Token; asn = impl; } // Handle properties and objects here else if (node.Children.Count != 0) { // Grab the last two nodes ConcreteNode temp1 = null, temp2 = null; if (node.Children.Count >= 1) { temp1 = node.Children[node.Children.Count - 1]; } if (node.Children.Count >= 2) { temp2 = node.Children[node.Children.Count - 2]; } // object = last 2 children == { and } if (temp1 != null && temp2 != null && temp1.Type == ConcreteNodeType.RightBrace && temp2.Type == ConcreteNodeType.LeftBrace) { if (node.Children.Count < 2) { this._compiler.AddError(CompileErrorCode.StringExpected, node.File, node.Line); return; } var impl = new ObjectAbstractNode(this._current); impl.Line = node.Line; impl.File = node.File; impl.IsAbstract = false; // Create a temporary detail list var temp = new List <ConcreteNode>(); if (node.Token == "abstract") { impl.IsAbstract = true; } else { temp.Add(node); } temp.AddRange(node.Children); // Get the type of object IEnumerator <ConcreteNode> iter = temp.GetEnumerator(); iter.MoveNext(); impl.Cls = iter.Current.Token; var validNode = iter.MoveNext(); // Get the name // Unless the type is in the exclusion list if (validNode && (iter.Current.Type == ConcreteNodeType.Word || iter.Current.Type == ConcreteNodeType.Quote) && !this._compiler._isNameExcluded(impl.Cls, this._current)) { impl.Name = iter.Current.Token; validNode = iter.MoveNext(); } // Everything up until the colon is a "value" of this object while (validNode && iter.Current.Type != ConcreteNodeType.Colon && iter.Current.Type != ConcreteNodeType.LeftBrace) { if (iter.Current.Type == ConcreteNodeType.Variable) { var var = new VariableGetAbstractNode(impl); var.File = iter.Current.File; var.Line = iter.Current.Line; var.Name = iter.Current.Token; impl.Values.Add(var); } else { var atom = new AtomAbstractNode(impl); atom.File = iter.Current.File; atom.Line = iter.Current.Line; atom.Value = iter.Current.Token; impl.Values.Add(atom); } validNode = iter.MoveNext(); } // Find the base if (validNode && iter.Current.Type == ConcreteNodeType.Colon) { // Children of the ':' are bases foreach (var j in iter.Current.Children) { impl.Bases.Add(j.Token); } validNode = iter.MoveNext(); } // Finally try to map the cls to an id if (this._compiler.KeywordMap.ContainsKey(impl.Cls)) { impl.Id = this._compiler.KeywordMap[impl.Cls]; } asn = (AbstractNode)impl; this._current = impl; // Visit the children of the { AbstractTreeBuilder.Visit(this, temp2.Children); // Go back up the stack this._current = impl.Parent; } // Otherwise, it is a property else { var impl = new PropertyAbstractNode(this._current); impl.Line = node.Line; impl.File = node.File; impl.Name = node.Token; if (this._compiler.KeywordMap.ContainsKey(impl.Name)) { impl.Id = this._compiler.KeywordMap[impl.Name]; } asn = (AbstractNode)impl; this._current = impl; // Visit the children of the { AbstractTreeBuilder.Visit(this, node.Children); // Go back up the stack this._current = impl.Parent; } } // Otherwise, it is a standard atom else { var impl = new AtomAbstractNode(this._current); impl.Line = node.Line; impl.File = node.File; impl.Value = node.Token; if (this._compiler.KeywordMap.ContainsKey(impl.Value)) { impl.Id = this._compiler.KeywordMap[impl.Value]; } asn = impl; } if (asn != null) { if (this._current != null) { if (this._current is PropertyAbstractNode) { var impl = (PropertyAbstractNode)this._current; impl.Values.Add(asn); } else { var impl = (ObjectAbstractNode)this._current; impl.Children.Add(asn); } } else { this._nodes.Add(asn); } } }
protected void _translateUnifiedGpuProgram(ScriptCompiler compiler, ObjectAbstractNode obj) { var customParameters = new NameValuePairList(); AbstractNode parameters = null; foreach (var i in obj.Children) { if (i is PropertyAbstractNode) { var prop = (PropertyAbstractNode)i; if (prop.Name == "delegate") { var value = string.Empty; if (prop.Values.Count != 0 && prop.Values[0] is AtomAbstractNode) { value = ((AtomAbstractNode)prop.Values[0]).Value; } ScriptCompilerEvent evt = new ProcessResourceNameScriptCompilerEvent(ProcessResourceNameScriptCompilerEvent.ResourceType.GpuProgram, value); compiler._fireEvent(ref evt); customParameters["delegate"] = ((ProcessResourceNameScriptCompilerEvent)evt).Name; } else { var name = prop.Name; var value = string.Empty; var first = true; foreach (var it in prop.Values) { if (it is AtomAbstractNode) { if (!first) { value += " "; } else { first = false; } value += ((AtomAbstractNode)it).Value; } } customParameters.Add(name, value); } } else if (i is ObjectAbstractNode) { if (((ObjectAbstractNode)i).Id == (uint)Keywords.ID_DEFAULT_PARAMS) { parameters = i; } else { processNode(compiler, i); } } } // Allocate the program Object progObj; HighLevelGpuProgram prog = null; ScriptCompilerEvent evnt = new CreateHighLevelGpuProgramScriptCompilerEvent(obj.File, obj.Name, compiler.ResourceGroup, string.Empty, "unified", _translateIDToGpuProgramType(obj.Id)); var processed = compiler._fireEvent(ref evnt, out progObj); if (!processed) { prog = (HighLevelGpuProgram) (HighLevelGpuProgramManager.Instance.CreateProgram(obj.Name, compiler.ResourceGroup, "unified", _translateIDToGpuProgramType(obj.Id))); } else { prog = (HighLevelGpuProgram)progObj; } // Check that allocation worked if (prog == null) { compiler.AddError(CompileErrorCode.ObjectAllocationError, obj.File, obj.Line, "gpu program \"" + obj.Name + "\" could not be created"); return; } obj.Context = prog; prog.IsMorphAnimationIncluded = false; prog.PoseAnimationCount = 0; prog.IsSkeletalAnimationIncluded = false; prog.IsVertexTextureFetchRequired = false; prog.Origin = obj.File; // Set the custom parameters prog.SetParameters(customParameters); // Set up default parameters if (prog.IsSupported && parameters != null) { var ptr = prog.DefaultParameters; GpuProgramTranslator.TranslateProgramParameters(compiler, ptr, (ObjectAbstractNode)parameters); } }
protected void _translateGpuProgram(ScriptCompiler compiler, ObjectAbstractNode obj) { var customParameters = new NameValuePairList(); string syntax = string.Empty, source = string.Empty; AbstractNode parameters = null; foreach (var i in obj.Children) { if (i is PropertyAbstractNode) { var prop = (PropertyAbstractNode)i; if (prop.Id == (uint)Keywords.ID_SOURCE) { if (prop.Values.Count != 0) { if (prop.Values[0] is AtomAbstractNode) { source = ((AtomAbstractNode)prop.Values[0]).Value; } else { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "source file expected"); } } else { compiler.AddError(CompileErrorCode.StringExpected, prop.File, prop.Line, "source file expected"); } } else if (prop.Id == (uint)Keywords.ID_SYNTAX) { if (prop.Values.Count != 0) { if (prop.Values[0] is AtomAbstractNode) { syntax = ((AtomAbstractNode)prop.Values[0]).Value; } else { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "syntax string expected"); } } else { compiler.AddError(CompileErrorCode.StringExpected, prop.File, prop.Line, "syntax string expected"); } } else { string name = prop.Name, value = string.Empty; var first = true; foreach (var it in prop.Values) { if (it is AtomAbstractNode) { if (!first) { value += " "; } else { first = false; } value += ((AtomAbstractNode)it).Value; } } customParameters.Add(name, value); } } else if (i is ObjectAbstractNode) { if (((ObjectAbstractNode)i).Id == (uint)Keywords.ID_DEFAULT_PARAMS) { parameters = i; } else { processNode(compiler, i); } } } if (!GpuProgramManager.Instance.IsSyntaxSupported(syntax)) { compiler.AddError(CompileErrorCode.UnsupportedByRenderSystem, obj.File, obj.Line); //Register the unsupported program so that materials that use it know that //it exists but is unsupported var unsupportedProg = GpuProgramManager.Instance.Create(obj.Name, compiler.ResourceGroup, _translateIDToGpuProgramType(obj.Id), syntax); return; } // Allocate the program object progObj; GpuProgram prog = null; ScriptCompilerEvent evt = new CreateGpuProgramScriptCompilerEvent(obj.File, obj.Name, compiler.ResourceGroup, source, syntax, _translateIDToGpuProgramType(obj.Id)); var processed = compiler._fireEvent(ref evt, out progObj); if (!processed) { prog = (GpuProgram) GpuProgramManager.Instance.CreateProgram(obj.Name, compiler.ResourceGroup, source, _translateIDToGpuProgramType(obj.Id), syntax); } else { prog = (GpuProgram)progObj; } // Check that allocation worked if (prog == null) { compiler.AddError(CompileErrorCode.ObjectAllocationError, obj.File, obj.Line, "gpu program \"" + obj.Name + "\" could not be created"); return; } obj.Context = prog; prog.IsMorphAnimationIncluded = false; prog.PoseAnimationCount = 0; prog.IsSkeletalAnimationIncluded = false; prog.IsVertexTextureFetchRequired = false; prog.Origin = obj.File; // Set the custom parameters prog.SetParameters(customParameters); // Set up default parameters if (prog.IsSupported && parameters != null) { var ptr = prog.DefaultParameters; GpuProgramTranslator.TranslateProgramParameters(compiler, ptr, (ObjectAbstractNode)parameters); } }
public static void TranslateProgramParameters(ScriptCompiler compiler, GpuProgramParameters parameters, ObjectAbstractNode obj) { var animParametricsCount = 0; foreach (var i in obj.Children) { if (!(i is PropertyAbstractNode)) { continue; } var prop = (PropertyAbstractNode)i; switch ((Keywords)prop.Id) { #region ID_SHARED_PARAMS_REF case Keywords.ID_SHARED_PARAMS_REF: { if (prop.Values.Count != 1) { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "shared_params_ref requires a single parameter"); continue; } var i0 = getNodeAt(prop.Values, 0); if (!(i0 is AtomAbstractNode)) { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "shared parameter set name expected"); continue; } var atom0 = (AtomAbstractNode)i0; try { parameters.AddSharedParameters(atom0.Value); } catch (AxiomException e) { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, e.Message); } } break; #endregion ID_SHARED_PARAMS_REF #region ID_PARAM_INDEXED || ID_PARAM_NAMED case Keywords.ID_PARAM_INDEXED: case Keywords.ID_PARAM_NAMED: { if (prop.Values.Count >= 3) { var named = (prop.Id == (uint)Keywords.ID_PARAM_NAMED); var i0 = getNodeAt(prop.Values, 0); var i1 = getNodeAt(prop.Values, 1); var k = getNodeAt(prop.Values, 2); if (!(i0 is AtomAbstractNode) || !(i1 is AtomAbstractNode)) { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "name or index and parameter type expected"); return; } var atom0 = (AtomAbstractNode)i0; var atom1 = (AtomAbstractNode)i1; if (!named && !atom0.IsNumber) { compiler.AddError(CompileErrorCode.NumberExpected, prop.File, prop.Line, "parameter index expected"); return; } var name = string.Empty; var index = 0; // Assign the name/index if (named) { name = atom0.Value; } else { index = (int)atom0.Number; } // Determine the type if (atom1.Value == "matrix4x4") { Matrix4 m; if (getMatrix4(prop.Values, 2, out m)) { try { if (named) { parameters.SetNamedConstant(name, m); } else { parameters.SetConstant(index, m); } } catch { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "setting matrix4x4 parameter failed"); } } else { compiler.AddError(CompileErrorCode.NumberExpected, prop.File, prop.Line, "incorrect matrix4x4 declaration"); } } else { // Find the number of parameters var isValid = true; var type = GpuProgramParameters.ElementType.Real; var count = 0; if (atom1.Value.Contains("float")) { type = GpuProgramParameters.ElementType.Real; if (atom1.Value.Length >= 6) { count = int.Parse(atom1.Value.Substring(5)); } else { count = 1; } } else if (atom1.Value.Contains("int")) { type = GpuProgramParameters.ElementType.Int; if (atom1.Value.Length >= 4) { count = int.Parse(atom1.Value.Substring(3)); } else { count = 1; } } else { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "incorrect type specified; only variants of int and float allowed"); isValid = false; } if (isValid) { // First, clear out any offending auto constants if (named) { parameters.ClearNamedAutoConstant(name); } else { parameters.ClearAutoConstant(index); } var roundedCount = count % 4 != 0 ? count + 4 - (count % 4) : count; if (type == GpuProgramParameters.ElementType.Int) { var vals = new int[roundedCount]; if (getInts(prop.Values, 2, out vals, roundedCount)) { try { if (named) { parameters.SetNamedConstant(name, vals, count, 1); } else { parameters.SetConstant(index, vals, roundedCount / 4); } } catch { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "setting of constant failed"); } } else { compiler.AddError(CompileErrorCode.NumberExpected, prop.File, prop.Line, "incorrect integer constant declaration"); } } else { var vals = new float[roundedCount]; if (getFloats(prop.Values, 2, out vals, roundedCount)) { try { if (named) { parameters.SetNamedConstant(name, vals, count, 1); } else { parameters.SetConstant(index, vals, roundedCount / 4); } } catch { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "setting of constant failed"); } } else { compiler.AddError(CompileErrorCode.NumberExpected, prop.File, prop.Line, "incorrect float constant declaration"); } } } } } else { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "param_named and param_indexed properties requires at least 3 arguments"); } } break; #endregion ID_PARAM_INDEXED || ID_PARAM_NAMED #region ID_PARAM_INDEXED_AUTO || ID_PARAM_NAMED_AUTO case Keywords.ID_PARAM_INDEXED_AUTO: case Keywords.ID_PARAM_NAMED_AUTO: { var named = (prop.Id == (uint)Keywords.ID_PARAM_NAMED_AUTO); var name = string.Empty; var index = 0; if (prop.Values.Count >= 2) { var i0 = getNodeAt(prop.Values, 0); var i1 = getNodeAt(prop.Values, 1); var i2 = getNodeAt(prop.Values, 2); var i3 = getNodeAt(prop.Values, 3); if (!(i0 is AtomAbstractNode) || !(i1 is AtomAbstractNode)) { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "name or index and auto constant type expected"); return; } var atom0 = (AtomAbstractNode)i0; var atom1 = (AtomAbstractNode)i1; if (!named && !atom0.IsNumber) { compiler.AddError(CompileErrorCode.NumberExpected, prop.File, prop.Line, "parameter index expected"); return; } if (named) { name = atom0.Value; } else { index = int.Parse(atom0.Value); } // Look up the auto constant atom1.Value = atom1.Value.ToLower(); GpuProgramParameters.AutoConstantDefinition def; var defFound = GpuProgramParameters.GetAutoConstantDefinition(atom1.Value, out def); if (defFound) { switch (def.DataType) { #region None case GpuProgramParameters.AutoConstantDataType.None: // Set the auto constant try { if (named) { parameters.SetNamedAutoConstant(name, def.AutoConstantType); } else { parameters.SetAutoConstant(index, def.AutoConstantType); } } catch { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "setting of constant failed"); } break; #endregion None #region Int case GpuProgramParameters.AutoConstantDataType.Int: if (def.AutoConstantType == GpuProgramParameters.AutoConstantType.AnimationParametric) { try { if (named) { parameters.SetNamedAutoConstant(name, def.AutoConstantType, animParametricsCount++); } else { parameters.SetAutoConstant(index, def.AutoConstantType, animParametricsCount++); } } catch { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "setting of constant failed"); } } else { // Only certain texture projection auto params will assume 0 // Otherwise we will expect that 3rd parameter if (i2 == null) { if (def.AutoConstantType == GpuProgramParameters.AutoConstantType.TextureViewProjMatrix || def.AutoConstantType == GpuProgramParameters.AutoConstantType.TextureWorldViewProjMatrix || def.AutoConstantType == GpuProgramParameters.AutoConstantType.SpotLightViewProjMatrix || def.AutoConstantType == GpuProgramParameters.AutoConstantType.SpotLightWorldViewProjMatrix) { try { if (named) { parameters.SetNamedAutoConstant(name, def.AutoConstantType, 0); } else { parameters.SetAutoConstant(index, def.AutoConstantType, 0); } } catch { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "setting of constant failed"); } } else { compiler.AddError(CompileErrorCode.NumberExpected, prop.File, prop.Line, "extra parameters required by constant definition " + atom1.Value); } } else { var success = false; var extraInfo = 0; if (i3 == null) { // Handle only one extra value if (getInt(i2, out extraInfo)) { success = true; } } else { // Handle two extra values var extraInfo1 = 0; var extraInfo2 = 0; if (getInt(i2, out extraInfo1) && getInt(i3, out extraInfo2)) { extraInfo = extraInfo1 | (extraInfo2 << 16); success = true; } } if (success) { try { if (named) { parameters.SetNamedAutoConstant(name, def.AutoConstantType, extraInfo); } else { parameters.SetAutoConstant(index, def.AutoConstantType, extraInfo); } } catch { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "setting of constant failed"); } } else { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "invalid auto constant extra info parameter"); } } } break; #endregion Int #region Real case GpuProgramParameters.AutoConstantDataType.Real: if (def.AutoConstantType == GpuProgramParameters.AutoConstantType.Time || def.AutoConstantType == GpuProgramParameters.AutoConstantType.FrameTime) { Real f = 1.0f; if (i2 != null) { getReal(i2, out f); } try { if (named) { parameters.SetNamedAutoConstantReal(name, def.AutoConstantType, f); } else { parameters.SetAutoConstantReal(index, def.AutoConstantType, f); } } catch { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "setting of constant failed"); } } else { if (i2 != null) { Real extraInfo = 0.0f; if (getReal(i2, out extraInfo)) { try { if (named) { parameters.SetNamedAutoConstantReal(name, def.AutoConstantType, extraInfo); } else { parameters.SetAutoConstantReal(index, def.AutoConstantType, extraInfo); } } catch { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "setting of constant failed"); } } else { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line, "incorrect float argument definition in extra parameters"); } } else { compiler.AddError(CompileErrorCode.NumberExpected, prop.File, prop.Line, "extra parameters required by constant definition " + atom1.Value); } } break; #endregion Real } } else { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line); } } else { compiler.AddError(CompileErrorCode.InvalidParameters, prop.File, prop.Line); } } break; #endregion ID_PARAM_INDEXED_AUTO || ID_PARAM_NAMED_AUTO default: compiler.AddError(CompileErrorCode.UnexpectedToken, prop.File, prop.Line, "token \"" + prop.Name + "\" is not recognized"); break; } } }
internal static global::System.Runtime.InteropServices.HandleRef getCPtr(ObjectAbstractNode obj) { return((obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr); }