public void AddMethod(int i, ScenarioTag.ScriptMethodDefinition scriptMethod) { var modifiers = SyntaxTokenList.Create(Token(SyntaxKind.PublicKeyword)) .Add(Token(SyntaxKind.AsyncKeyword)); TypeSyntax returnType = ParseTypeName("Task"); if (scriptMethod.ReturnType != ScriptDataType.Void) { returnType = GenericName("Task").AddTypeArgumentListArguments(SyntaxUtil.ScriptTypeSyntax(scriptMethod.ReturnType)); } var method = MethodDeclaration( returnType, SyntaxUtil.SanitizeIdentifier(scriptMethod.Description)) .WithModifiers(modifiers); // Push root method body as first scope var block = new StatementBlockContext(); var rootScope = new Scope(scriptMethod.ReturnType, block, block); var retScope = EvaluateNodes(scriptMethod.SyntaxNodeIndex, rootScope); Debug.Assert(rootScope == retScope, "Last scope wasn't the provided root"); methods.Add(method.WithBody(Block(block.GetInnerStatements())) .AddAttributeLists(AttributeList(SeparatedList(new[] { Attribute(IdentifierName(nameof(ScriptMethodAttribute).Replace("Attribute", ""))) .AddArgumentListArguments( AttributeArgument(SyntaxUtil.LiteralExpression(i)), AttributeArgument(MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName(nameof(Lifecycle)), IdentifierName(scriptMethod.Lifecycle.ToString())))) })))); }
public static ScriptTreeNode GetScriptTree(ScenarioTag tag, ScenarioTag.ScriptMethodDefinition method, int methodIndex) { var strings = (Span <byte>)tag.ScriptStrings; var root = new ScriptTreeNode() { Type = NodeType.MethodDecl, Value = method.Description, DataType = method.ReturnType, Index = methodIndex, Original = new ScenarioTag.ScriptSyntaxNode() { NextIndex = method.SyntaxNodeIndex, NextCheckval = method.ValueB, NodeType = NodeType.MethodDecl, NodeString = (ushort)FindStringIndex(method.Description, strings) } }; var childIndices = new Stack <(int, ScriptTreeNode)>(); childIndices.Push((method.SyntaxNodeIndex, root)); while (childIndices.Any()) { var(currentIndex, parent) = childIndices.Pop(); var node = tag.ScriptSyntaxNodes[currentIndex]; var current = new ScriptTreeNode(); object value = node.NodeData_32; if (node.NodeType == NodeType.Expression) { switch (node.DataType) { case ScriptDataType.Boolean: value = node.NodeData_B3 == 1; break; case ScriptDataType.Short: value = node.NodeData_H16; break; case ScriptDataType.Float: value = BitConverter.Int32BitsToSingle((int)node.NodeData_32); break; case ScriptDataType.String: case ScriptDataType.MethodOrOperator: case ScriptDataType.AI: case ScriptDataType.AIScript: case ScriptDataType.Sound: case ScriptDataType.Device: case ScriptDataType.EntityIdentifier: value = strings.ReadStringStarting(node.NodeString); break; case ScriptDataType.Entity: break; default: // TODO: hack until everything is tracked down, populating string as value if exists if (node.NodeString > 0 && node.NodeString < tag.ScriptStrings.Length && tag.ScriptStrings[node.NodeString - 1] == 0) { value = OpenBlam.Core.Extensions.SpanByteExtensions.ReadStringStarting(tag.ScriptStrings, node.NodeString); } break; } } else if (node.NodeType == NodeType.VariableAccess) { value = strings.ReadStringStarting(node.NodeString); } else if (node.NodeType == NodeType.ScriptInvocation) { value = tag.ScriptMethods[node.OperationId].Description; } current.Original = node; current.DataType = node.DataType; current.Type = node.NodeType; current.Value = value; current.Index = currentIndex; parent.Children.Add(current); var nextNodeParent = current; // Expression scope seems to use NodeData to specify what is inside the scope // and the Next value is used to specify the scope's next sibling instead // This is how the linear-ish node structure can expand into a more traditional AST if (node.NodeType == NodeType.BuiltinInvocation || node.NodeType == NodeType.ScriptInvocation) { // Use scope's parent as next node's parent instead of the scope // This makes the 'next' into a 'sibling' nextNodeParent = parent; Debug.Assert(tag.ScriptSyntaxNodes[node.NodeData_H16].Checkval == node.NodeData_L16, "Scope's next node checkval didn't match"); childIndices.Push((node.NodeData_H16, current)); } // Push NextIndex using the appropriate parent node if (node.NextIndex != ushort.MaxValue) { Debug.Assert(tag.ScriptSyntaxNodes[node.NextIndex].Checkval == node.NextCheckval, "Node's next checkval didn't match"); childIndices.Push((node.NextIndex, nextNodeParent)); } } return(root); }