DeveelDB  20151217
complete SQL database system, primarly developed for .NET/Mono frameworks
SqlDefaultParser.cs
Go to the documentation of this file.
1 //
2 // Copyright 2010-2015 Deveel
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 using System;
18 using System.Collections.Generic;
19 using System.Diagnostics;
20 using System.Linq;
21 using System.Text;
22 
23 using Irony;
24 using Irony.Ast;
25 using Irony.Parsing;
26 
27 namespace Deveel.Data.Sql.Parser {
29  private LanguageData languageData;
30  private Irony.Parsing.Parser parser;
31 
32  public SqlDefaultParser(SqlGrammarBase grammar) {
33  languageData = new LanguageData(grammar);
34  parser = new Irony.Parsing.Parser(languageData);
35 
36  if (!languageData.CanParse())
37  throw new InvalidOperationException();
38  }
39 
40  private void Dispose(bool disposing) {
41  parser = null;
42  languageData = null;
43  }
44 
45  public void Dispose() {
46  Dispose(true);
47  GC.SuppressFinalize(this);
48  }
49 
50  public string Dialect {
51  get { return ((SqlGrammarBase) languageData.Grammar).Dialect; }
52  }
53 
54  public SqlParseResult Parse(string input) {
55  var result = new SqlParseResult(Dialect);
56 
57  var timer = new Timer();
58 
59  try {
60  long time;
61  var node = ParseNode(input, result.Errors, out time);
62  result.RootNode = node;
63  } catch (Exception ex) {
64  // TODO: form a better exception
65  result.Errors.Add(new SqlParseError(ex.Message, 0, 0));
66  } finally {
67  timer.Dispose();
68  result.ParseTime = timer.Elapsed;
69  }
70 
71  return result;
72  }
73 
74  private ISqlNode ParseNode(string sqlSource, ICollection<SqlParseError> errors, out long parseTime) {
75  var tree = parser.Parse(sqlSource);
76  parseTime = tree.ParseTimeMilliseconds;
77 
78  if (tree.Status == ParseTreeStatus.Error) {
79  BuildErrors(tree, errors, tree.ParserMessages);
80  return null;
81  }
82 
83  var astContext = new AstContext(languageData) {
84  DefaultNodeType = typeof (SqlNode),
85  DefaultIdentifierNodeType = typeof (IdentifierNode)
86  };
87 
88  var astCompiler = new AstBuilder(astContext);
89  astCompiler.BuildAst(tree);
90 
91  if (tree.HasErrors())
92  BuildErrors(tree, errors, tree.ParserMessages);
93 
94  var node = (ISqlNode) tree.Root.AstNode;
95  if (node.NodeName == "root")
96  node = node.ChildNodes.FirstOrDefault();
97 
98  return node;
99  }
100 
101  private static void BuildErrors(ParseTree tree, ICollection<SqlParseError> errors, LogMessageList logMessages) {
102  foreach (var logMessage in logMessages) {
103  if (logMessage.Level == ErrorLevel.Error) {
104  var line = logMessage.Location.Line;
105  var column = logMessage.Location.Column;
106  var locationMessage = FormInfoMessage(tree, line, column);
107  var expected = logMessage.ParserState.ReportedExpectedSet.ToArray();
108  var infoMessage = String.Format("A parse error occurred near '{0}' in the source", locationMessage);
109  if (expected.Length > 0)
110  infoMessage = String.Format("{0}. Expected {1}", infoMessage, String.Join(", ", expected));
111 
112  errors.Add(new SqlParseError(infoMessage, line, column));
113  }
114  }
115  }
116 
117  private static string FormInfoMessage(ParseTree tree, int line, int column) {
118  const int tokensBeforeCount = 10;
119  const int tokensAfterCount = 10;
120 
121  var tokensBefore = FindTokensTo(tree, line, column).Reverse().ToList();
122  var tokensAfter = FindTokensFrom(tree, line, column);
123 
124  var countTokensBefore = System.Math.Min(tokensBefore.Count, tokensBeforeCount);
125  var countTokensAfter = System.Math.Min(tokensAfterCount, tokensAfter.Count);
126 
127  var takeTokensBefore = tokensBefore.Take(countTokensBefore).Reverse();
128  var takeTokensAfter = tokensAfter.Take(countTokensAfter);
129 
130  var sb = new StringBuilder();
131  foreach (var token in takeTokensBefore) {
132  sb.Append(token.Text);
133  sb.Append(" ");
134  }
135 
136  foreach (var token in takeTokensAfter) {
137  sb.Append(token.Text);
138  sb.Append(" ");
139  }
140 
141  return sb.ToString();
142  }
143 
144  private static IList<Irony.Parsing.Token> FindTokensFrom(ParseTree tree, int line, int column) {
145  var tokens = tree.Tokens;
146  bool startCollect = false;
147 
148  var result = new List<Irony.Parsing.Token>();
149  foreach (var token in tokens) {
150  if (token.Location.Line == line &&
151  token.Location.Column == column) {
152  startCollect = true;
153  }
154 
155  if (startCollect)
156  result.Add(token);
157  }
158 
159  return result.ToList();
160  }
161 
162  private static IList<Irony.Parsing.Token> FindTokensTo(ParseTree tree, int line, int column) {
163  var tokens = tree.Tokens;
164 
165  var result = new List<Irony.Parsing.Token>();
166  foreach (var token in tokens) {
167  if (token.Location.Line == line &&
168  token.Location.Column == column)
169  break;
170 
171  result.Add(token);
172  }
173 
174  return result.ToList();
175  }
176 
177 #region Timer
178 
179  class Timer : IDisposable {
180 #if PCL
181  private readonly DateTimeOffset startTime;
182 #else
183  private Stopwatch stopwatch;
184 #endif
185 
186  public Timer() {
187 #if PCL
188  startTime = DateTimeOffset.UtcNow;
189 #else
190  stopwatch = new Stopwatch();
191  stopwatch.Start();
192 #endif
193  }
194 
195  public TimeSpan Elapsed {
196  get {
197 #if PCL
198  return DateTimeOffset.UtcNow.Subtract(startTime);
199 #else
200  return stopwatch.Elapsed;
201 #endif
202  }
203  }
204 
205  public void Dispose() {
206 #if !PCL
207  if (stopwatch != null)
208  stopwatch.Stop();
209 #endif
210  }
211  }
212 
213 #endregion
214  }
215 }
The result of a parse of an SQL input
This is a simple identifier within a SQL grammar.
Defines the contract for nodes in an AST model for a SQL grammar analysis and parsing.
Definition: ISqlNode.cs:25
static IList< Irony.Parsing.Token > FindTokensFrom(ParseTree tree, int line, int column)
static void BuildErrors(ParseTree tree, ICollection< SqlParseError > errors, LogMessageList logMessages)
SqlDefaultParser(SqlGrammarBase grammar)
static string FormInfoMessage(ParseTree tree, int line, int column)
Implementations of this interface will parse input strings into ISqlNode that can be used to construc...
Definition: ISqlParser.cs:38
ISqlNode ParseNode(string sqlSource, ICollection< SqlParseError > errors, out long parseTime)
ErrorLevel
In case of error messages, this enumerates the level of severity of the error.
Definition: ErrorLevel.cs:24
SqlParseResult Parse(string input)
Analyzes and parses the input and results an object that describes the parsed nodes in a tree that ca...
static IList< Irony.Parsing.Token > FindTokensTo(ParseTree tree, int line, int column)
The default implementation of ISqlNode, that is a node in the text analysis parsing of SQL commands...
Definition: SqlNode.cs:32