/****************************************************************************
|
**
|
** Copyright (C) 2018 The Qt Company Ltd.
|
** Contact: https://www.qt.io/licensing/
|
**
|
** This file is part of the Qt VS Tools.
|
**
|
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
** Commercial License Usage
|
** Licensees holding valid commercial Qt licenses may use this file in
|
** accordance with the commercial license agreement provided with the
|
** Software or, alternatively, in accordance with the terms contained in
|
** a written agreement between you and The Qt Company. For licensing terms
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
** information use the contact form at https://www.qt.io/contact-us.
|
**
|
** GNU General Public License Usage
|
** Alternatively, this file may be used under the terms of the GNU
|
** General Public License version 3 as published by the Free Software
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
** included in the packaging of this file. Please review the following
|
** information to ensure the GNU General Public License requirements will
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
**
|
** $QT_END_LICENSE$
|
**
|
****************************************************************************/
|
|
/// This file contains the integration with the Qt Declarative parser.
|
|
using System;
|
using System.Collections.Generic;
|
using System.IO;
|
using System.Linq;
|
using System.Linq.Expressions;
|
using System.Reflection;
|
using System.Runtime.InteropServices;
|
using System.Text;
|
|
namespace QtVsTools.Qml
|
{
|
using Syntax;
|
|
/// <summary>
|
/// Implements the integration with the Qt declarative parser, including:
|
/// * Managed-unmanaged interop with the vsqml DLL;
|
/// * Unmarshaling of syntax element data types (e.g. AST nodes);
|
/// * Visitor role for AST traversal.
|
/// </summary>
|
class Parser : IDisposable
|
{
|
internal static class NativeMethods
|
{
|
[DllImport("vsqml",
|
CallingConvention = CallingConvention.Cdecl,
|
EntryPoint = "qmlGetTokens")]
|
internal static extern bool GetTokens(
|
IntPtr qmlText,
|
int qmlTextLength,
|
ref IntPtr tokens,
|
ref int tokensLength);
|
|
[DllImport("vsqml",
|
CallingConvention = CallingConvention.Cdecl,
|
EntryPoint = "qmlFreeTokens")]
|
internal static extern bool FreeTokens(IntPtr tokens);
|
|
[DllImport("vsqml",
|
CallingConvention = CallingConvention.Cdecl,
|
EntryPoint = "qmlParse")]
|
internal static extern bool Parse(
|
IntPtr qmlText,
|
int qmlTextLength,
|
ref IntPtr parser,
|
ref bool parsedCorrectly,
|
ref IntPtr diagnosticMessages,
|
ref int diagnosticMessagesLength,
|
ref IntPtr comments,
|
ref int commentsLength);
|
|
[DllImport("vsqml",
|
CallingConvention = CallingConvention.Cdecl,
|
EntryPoint = "qmlFreeParser")]
|
internal static extern bool FreeParser(IntPtr parser);
|
|
[DllImport("vsqml",
|
CallingConvention = CallingConvention.Cdecl,
|
EntryPoint = "qmlFreeDiagnosticMessages")]
|
internal static extern bool FreeDiagnosticMessages(IntPtr diagnosticMessages);
|
|
[DllImport("vsqml",
|
CallingConvention = CallingConvention.Cdecl,
|
EntryPoint = "qmlFreeComments")]
|
internal static extern bool FreeComments(IntPtr comments);
|
|
[DllImport("vsqml",
|
CallingConvention = CallingConvention.Cdecl,
|
EntryPoint = "qmlGetAstVisitor")]
|
internal static extern IntPtr GetAstVisitor();
|
|
[DllImport("vsqml",
|
CallingConvention = CallingConvention.Cdecl,
|
EntryPoint = "qmlFreeAstVisitor")]
|
internal static extern bool FreeAstVisitor(IntPtr astVisitor);
|
|
internal delegate bool Callback(
|
IntPtr astVisitor,
|
int nodeKind,
|
IntPtr node,
|
bool beginVisit,
|
IntPtr nodeData,
|
int nodeDataLength);
|
|
[DllImport("vsqml",
|
CallingConvention = CallingConvention.Cdecl,
|
EntryPoint = "qmlSetAstVisitorCallback")]
|
internal static extern bool SetAstVisitorCallback(
|
IntPtr astVisitor,
|
int nodeKindFilter,
|
Callback visitCallback);
|
|
[DllImport("vsqml",
|
CallingConvention = CallingConvention.Cdecl,
|
EntryPoint = "qmlAcceptAstVisitor")]
|
internal static extern bool AcceptAstVisitor(
|
IntPtr parser,
|
IntPtr node,
|
IntPtr astVisitor);
|
}
|
|
/// <summary>
|
/// List of "interesting" AST node types. To optimize the managed-unmanaged interop,
|
/// during AST traversal, only these node types will be reported.
|
/// </summary>
|
static readonly List<AstNodeKind> CallbackFilters =
|
new List<AstNodeKind> {
|
AstNodeKind.UiImport,
|
AstNodeKind.UiQualifiedId,
|
AstNodeKind.UiObjectDefinition,
|
AstNodeKind.UiObjectBinding,
|
AstNodeKind.UiObjectInitializer,
|
AstNodeKind.UiScriptBinding,
|
AstNodeKind.UiArrayBinding,
|
AstNodeKind.UiPublicMember,
|
AstNodeKind.FieldMemberExpression,
|
AstNodeKind.IdentifierExpression
|
};
|
|
IntPtr qmlTextPtr = IntPtr.Zero;
|
IntPtr qmlParserPtr = IntPtr.Zero;
|
|
List<Token> tokens;
|
public IEnumerable<Token> Tokens
|
{
|
get { return tokens; }
|
}
|
|
List<DiagnosticMessage> diagnosticMessages;
|
public IEnumerable<DiagnosticMessage> DiagnosticMessages
|
{
|
get { return diagnosticMessages; }
|
}
|
|
public int FirstErrorOffset { get; private set; }
|
|
List<AstNode> visitedNodes;
|
public IEnumerable<AstNode> AstNodes { get { return visitedNodes; } }
|
|
public bool ParsedCorrectly { get; private set; }
|
|
Dictionary<IntPtr, AstNode> nodesBytPtr;
|
Dictionary<IntPtr, List<KeyValuePair<AstNode, PropertyInfo>>> pendingDereferences;
|
|
Parser()
|
{
|
tokens = new List<Token>();
|
diagnosticMessages = new List<DiagnosticMessage>();
|
nodesBytPtr = new Dictionary<IntPtr, AstNode>();
|
pendingDereferences = new Dictionary<IntPtr,
|
List<KeyValuePair<AstNode, PropertyInfo>>>();
|
|
visitedNodes = new List<AstNode>();
|
}
|
|
public static Parser Parse(string qmlText)
|
{
|
var parser = new Parser();
|
parser.Work(qmlText);
|
return parser;
|
}
|
|
void Work(string qmlText)
|
{
|
// The Qt Declarative parser ignores CR's. However, the Visual Studio editor does not.
|
// To ensure that offsets are compatible, CR's are replaced with spaces.
|
string qmlTextNormalized = qmlText.Replace('\r', ' ');
|
var qmlTextData = Encoding.UTF8.GetBytes(qmlTextNormalized);
|
|
qmlTextPtr = Marshal.AllocHGlobal(qmlTextData.Length);
|
Marshal.Copy(qmlTextData, 0, qmlTextPtr, qmlTextData.Length);
|
|
IntPtr tokensPtr = IntPtr.Zero;
|
int tokensLength = 0;
|
|
NativeMethods.GetTokens(qmlTextPtr, qmlTextData.Length,
|
ref tokensPtr, ref tokensLength);
|
|
if (tokensPtr != IntPtr.Zero) {
|
var tokensData = new byte[tokensLength];
|
Marshal.Copy(tokensPtr, tokensData, 0, tokensLength);
|
|
using (var rdr = new BinaryReader(new MemoryStream(tokensData))) {
|
while (rdr.BaseStream.Position + (3 * sizeof(int)) <= tokensLength) {
|
int kind = rdr.ReadInt32();
|
int offset = rdr.ReadInt32();
|
int length = rdr.ReadInt32();
|
tokens.Add(Token.Create((TokenKind)kind, offset, length));
|
}
|
}
|
NativeMethods.FreeTokens(tokensPtr);
|
}
|
|
bool parsedCorrectly = false;
|
IntPtr diagnosticMessagesPtr = IntPtr.Zero;
|
int diagnosticMessagesLength = 0;
|
IntPtr commentsPtr = IntPtr.Zero;
|
int commentsLength = 0;
|
|
NativeMethods.Parse(qmlTextPtr, qmlTextData.Length,
|
ref qmlParserPtr, ref parsedCorrectly,
|
ref diagnosticMessagesPtr, ref diagnosticMessagesLength,
|
ref commentsPtr, ref commentsLength);
|
|
ParsedCorrectly = parsedCorrectly;
|
|
if (diagnosticMessagesPtr != IntPtr.Zero) {
|
var diagnosticMessagesData = new byte[diagnosticMessagesLength];
|
Marshal.Copy(
|
diagnosticMessagesPtr,
|
diagnosticMessagesData, 0, diagnosticMessagesLength);
|
|
FirstErrorOffset = qmlTextNormalized.Length + 1;
|
using (var rdr = new BinaryReader(new MemoryStream(diagnosticMessagesData))) {
|
while (rdr.BaseStream.Position + (3 * sizeof(int)) <= diagnosticMessagesLength) {
|
var kind = (DiagnosticMessageKind)rdr.ReadInt32();
|
int offset = rdr.ReadInt32();
|
int length = rdr.ReadInt32();
|
diagnosticMessages.Add(new DiagnosticMessage(kind, offset, length));
|
if (kind == DiagnosticMessageKind.Error && offset < FirstErrorOffset)
|
FirstErrorOffset = offset;
|
}
|
}
|
NativeMethods.FreeDiagnosticMessages(diagnosticMessagesPtr);
|
}
|
|
if (commentsPtr != IntPtr.Zero) {
|
var commentsData = new byte[commentsLength];
|
Marshal.Copy(commentsPtr, commentsData, 0, commentsLength);
|
|
using (var rdr = new BinaryReader(new MemoryStream(commentsData))) {
|
while (rdr.BaseStream.Position + (2 * sizeof(int)) <= commentsLength) {
|
int offset = rdr.ReadInt32();
|
int length = rdr.ReadInt32();
|
tokens.Add(Token.Create(TokenKind.T_COMMENT, offset, length));
|
}
|
}
|
NativeMethods.FreeComments(commentsPtr);
|
}
|
|
var astVisitor = NativeMethods.GetAstVisitor();
|
var callback = new NativeMethods.Callback(VisitorCallback);
|
|
foreach (var callbackFilter in CallbackFilters)
|
NativeMethods.SetAstVisitorCallback(astVisitor, (int)callbackFilter, callback);
|
|
NativeMethods.AcceptAstVisitor(qmlParserPtr, IntPtr.Zero, astVisitor);
|
|
while (pendingDereferences.Count > 0) {
|
var deref = pendingDereferences.First();
|
NativeMethods.AcceptAstVisitor(qmlParserPtr, deref.Key, astVisitor);
|
pendingDereferences.Remove(deref.Key);
|
}
|
|
GC.KeepAlive(callback);
|
NativeMethods.FreeAstVisitor(astVisitor);
|
}
|
|
SourceLocation UnmarshalLocation(BinaryReader nodeData)
|
{
|
try {
|
return new SourceLocation
|
{
|
Offset = nodeData.ReadInt32(),
|
Length = nodeData.ReadInt32(),
|
};
|
} catch (Exception) {
|
return new SourceLocation();
|
}
|
}
|
|
void UnmarshalPointer(BinaryReader nodeData, AstNode node, PropertyInfo nodeProperty)
|
{
|
if (nodeData == null || node == null || nodeProperty == null)
|
return;
|
|
IntPtr ptrRef;
|
try {
|
long ptrHi = nodeData.ReadInt32();
|
long ptrLo = nodeData.ReadInt32();
|
ptrRef = new IntPtr((ptrHi << 32) | (ptrLo & 0xFFFFFFFFL));
|
} catch (Exception) {
|
return;
|
}
|
|
if (ptrRef == IntPtr.Zero)
|
return;
|
|
AstNode nodeRef;
|
if (nodesBytPtr.TryGetValue(ptrRef, out nodeRef)) {
|
nodeProperty.SetValue(node, nodeRef);
|
} else {
|
List<KeyValuePair<AstNode, PropertyInfo>> pendingRefList;
|
if (!pendingDereferences.TryGetValue(ptrRef, out pendingRefList)) {
|
pendingDereferences[ptrRef] = pendingRefList =
|
new List<KeyValuePair<AstNode, PropertyInfo>>();
|
}
|
pendingRefList.Add(new KeyValuePair<AstNode, PropertyInfo>(node, nodeProperty));
|
}
|
}
|
|
void UnmarshalNode(BinaryReader nodeData, AstNode node)
|
{
|
node.FirstSourceLocation = UnmarshalLocation(nodeData);
|
node.LastSourceLocation = UnmarshalLocation(nodeData);
|
}
|
|
AstNode UnmarshalNode(BinaryReader nodeData, AstNodeKind nodeKind)
|
{
|
var node = new AstNode(nodeKind);
|
UnmarshalNode(nodeData, node);
|
return node;
|
}
|
|
UiImport UnmarshalUiImport(BinaryReader nodeData)
|
{
|
var node = new UiImport();
|
UnmarshalNode(nodeData, node);
|
node.ImportToken = UnmarshalLocation(nodeData);
|
node.FileNameToken = UnmarshalLocation(nodeData);
|
node.VersionToken = UnmarshalLocation(nodeData);
|
node.AsToken = UnmarshalLocation(nodeData);
|
node.ImportIdToken = UnmarshalLocation(nodeData);
|
node.SemicolonToken = UnmarshalLocation(nodeData);
|
return node;
|
}
|
|
UiQualifiedId UnmarshalUiQualifiedId(BinaryReader nodeData)
|
{
|
var node = new UiQualifiedId();
|
UnmarshalNode(nodeData, node);
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.Next));
|
node.IdentifierToken = UnmarshalLocation(nodeData);
|
return node;
|
}
|
|
UiObjectDefinition UnmarshalUiObjectDefinition(BinaryReader nodeData)
|
{
|
var node = new UiObjectDefinition();
|
UnmarshalNode(nodeData, node);
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.QualifiedTypeNameId));
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.Initializer));
|
return node;
|
}
|
|
UiObjectBinding UnmarshalUiObjectBinding(BinaryReader nodeData)
|
{
|
var node = new UiObjectBinding();
|
UnmarshalNode(nodeData, node);
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.QualifiedId));
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.QualifiedTypeNameId));
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.Initializer));
|
node.ColonToken = UnmarshalLocation(nodeData);
|
return node;
|
}
|
|
UiScriptBinding UnmarshalUiScriptBinding(BinaryReader nodeData)
|
{
|
var node = new UiScriptBinding();
|
UnmarshalNode(nodeData, node);
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.QualifiedId));
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.Statement));
|
node.ColonToken = UnmarshalLocation(nodeData);
|
return node;
|
}
|
|
UiArrayBinding UnmarshalUiArrayBinding(BinaryReader nodeData)
|
{
|
var node = new UiArrayBinding();
|
UnmarshalNode(nodeData, node);
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.QualifiedId));
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.Members));
|
node.ColonToken = UnmarshalLocation(nodeData);
|
node.LBracketToken = UnmarshalLocation(nodeData);
|
node.RBracketToken = UnmarshalLocation(nodeData);
|
return node;
|
}
|
|
UiPublicMember UnmarshalUiPublicMember(BinaryReader nodeData)
|
{
|
var node = new UiPublicMember();
|
UnmarshalNode(nodeData, node);
|
node.Type = (UiPublicMemberType)nodeData.ReadInt32();
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.MemberType));
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.Statement));
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.Binding));
|
node.IsDefaultMember = (nodeData.ReadInt32() != 0);
|
node.IsReadonlyMember = (nodeData.ReadInt32() != 0);
|
UnmarshalPointer(nodeData, node, GetPropertyRef(() => node.Parameters));
|
node.DefaultToken = UnmarshalLocation(nodeData);
|
node.ReadonlyToken = UnmarshalLocation(nodeData);
|
node.PropertyToken = UnmarshalLocation(nodeData);
|
node.TypeModifierToken = UnmarshalLocation(nodeData);
|
node.TypeToken = UnmarshalLocation(nodeData);
|
node.IdentifierToken = UnmarshalLocation(nodeData);
|
node.ColonToken = UnmarshalLocation(nodeData);
|
node.SemicolonToken = UnmarshalLocation(nodeData);
|
return node;
|
}
|
|
/// <summary>
|
/// This delegate method is called during the AST traversal when entering/exiting a node.
|
/// </summary>
|
/// <param name="astVisitor">Unmanaged AST visitor object ref</param>
|
/// <param name="nodeKind">Type of node being visited</param>
|
/// <param name="nodePtr">Node object ref</param>
|
/// <param name="beginVisit">"true" when entering node, "false" when exiting</param>
|
/// <param name="nodeDataPtr">Serialized content of AST node</param>
|
/// <param name="nodeDataLength">Length in bytes of serialized content</param>
|
/// <returns></returns>
|
bool VisitorCallback(
|
IntPtr astVisitor,
|
int nodeKind,
|
IntPtr nodePtr,
|
bool beginVisit,
|
IntPtr nodeDataPtr,
|
int nodeDataLength)
|
{
|
if (!beginVisit)
|
return true;
|
|
AstNode node = null;
|
var nodeData = new byte[nodeDataLength];
|
Marshal.Copy(nodeDataPtr, nodeData, 0, nodeDataLength);
|
using (var rdr = new BinaryReader(new MemoryStream(nodeData))) {
|
switch ((AstNodeKind)nodeKind) {
|
case AstNodeKind.UiImport:
|
node = UnmarshalUiImport(rdr);
|
break;
|
case AstNodeKind.UiQualifiedId:
|
node = UnmarshalUiQualifiedId(rdr);
|
break;
|
case AstNodeKind.UiObjectDefinition:
|
node = UnmarshalUiObjectDefinition(rdr);
|
break;
|
case AstNodeKind.UiObjectBinding:
|
node = UnmarshalUiObjectBinding(rdr);
|
break;
|
case AstNodeKind.UiScriptBinding:
|
node = UnmarshalUiScriptBinding(rdr);
|
break;
|
case AstNodeKind.UiArrayBinding:
|
node = UnmarshalUiArrayBinding(rdr);
|
break;
|
case AstNodeKind.UiPublicMember:
|
node = UnmarshalUiPublicMember(rdr);
|
break;
|
default:
|
node = UnmarshalNode(rdr, (AstNodeKind)nodeKind);
|
break;
|
}
|
}
|
if (node == null)
|
return true;
|
|
visitedNodes.Add(node);
|
nodesBytPtr[nodePtr] = node;
|
|
List<KeyValuePair<AstNode, PropertyInfo>> derefs;
|
if (pendingDereferences.TryGetValue(nodePtr, out derefs)) {
|
foreach (var deref in derefs) {
|
try {
|
deref.Value.SetValue(deref.Key, node);
|
} catch (Exception) { }
|
}
|
pendingDereferences.Remove(nodePtr);
|
}
|
|
return true;
|
}
|
|
void FreeManaged()
|
{
|
}
|
|
void FreeUnmanaged()
|
{
|
if (qmlParserPtr != IntPtr.Zero) {
|
NativeMethods.FreeParser(qmlParserPtr);
|
qmlParserPtr = IntPtr.Zero;
|
}
|
if (qmlTextPtr != IntPtr.Zero) {
|
Marshal.FreeHGlobal(qmlTextPtr);
|
qmlTextPtr = IntPtr.Zero;
|
}
|
}
|
|
#region IDisposable
|
bool disposed = false;
|
|
public void Dispose()
|
{
|
Dispose(true);
|
GC.SuppressFinalize(this);
|
}
|
|
protected virtual void Dispose(bool disposing)
|
{
|
if (disposed)
|
return;
|
|
if (disposing)
|
FreeManaged();
|
|
FreeUnmanaged();
|
|
disposed = true;
|
}
|
|
~Parser()
|
{
|
Dispose(false);
|
}
|
#endregion
|
|
/// <summary>
|
/// Get a reference to a static or instance class member from a member access lambda.
|
/// Adapted from:
|
/// https://stackoverflow.com/questions/2820660/get-name-of-property-as-a-string
|
/// </summary>
|
/// <param name="memberLambda">
|
/// Lambda expression of the form: '() => Class.Member'or '() => object.Member'
|
/// </param>
|
/// <returns>Reference to the class member</returns>
|
public static MemberInfo GetMemberRef<T>(Expression<Func<T>> memberLambda)
|
{
|
var me = memberLambda.Body as MemberExpression;
|
if (me == null)
|
return null;
|
return me.Member;
|
}
|
|
public static PropertyInfo GetPropertyRef<T>(Expression<Func<T>> memberLambda)
|
{
|
return GetMemberRef(memberLambda) as PropertyInfo;
|
}
|
}
|
}
|