1 #Region "Microsoft.VisualBasic::4680cb3e575391539fa5ed5c351c78c7, Microsoft.VisualBasic.Core\Scripting\TextGrepScriptEngine.vb"
2
3     ' Author:
4     
5     '       asuka (amethyst.asuka@gcmodeller.org)
6     '       xie (genetics@smrucc.org)
7     '       xieguigang (xie.guigang@live.com)
8     
9     ' Copyright (c) 2018 GPL3 Licensed
10     
11     
12     ' GNU GENERAL PUBLIC LICENSE (GPL3)
13     
14     
15     ' This program is free software: you can redistribute it and/or modify
16     ' it under the terms of the GNU General Public License as published by
17     ' the Free Software Foundation, either version 3 of the License, or
18     ' (at your option) any later version.
19     
20     ' This program is distributed in the hope that it will be useful,
21     ' but WITHOUT ANY WARRANTY; without even the implied warranty of
22     ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23     ' GNU General Public License for more details.
24     
25     ' You should have received a copy of the GNU General Public License
26     ' along with this program. If not, see <http://www.gnu.org/licenses/>.
27
28
29
30     ' /********************************************************************************/
31
32     ' Summaries:
33
34     '     Delegate Function
35     
36     
37     '     Delegate Function
38     
39     
40     '     Class TextGrepScriptEngine
41     
42     '         Properties: DoNothing, IsDoNothing, MethodPointers, PipelinePointer
43     
44     '         Constructor: (+1 OverloadsSub New
45     '         Function: Compile, CompileInternal, EnsureNotEmpty, Explains, Grep
46     '                   Match, MidString, NoOperation, Replace, Reverse
47     '                   Tokens, ToString
48     
49     
50     
51     
52     
53     ' /********************************************************************************/
54
55 #End Region
56
57 Imports System.Runtime.CompilerServices
58 Imports System.Text
59 Imports System.Text.RegularExpressions
60 Imports Microsoft.VisualBasic.CommandLine
61 Imports Microsoft.VisualBasic.CommandLine.Reflection
62 Imports Microsoft.VisualBasic.Language
63 Imports Microsoft.VisualBasic.Language.Default
64 Imports Token = System.Collections.Generic.KeyValuePair(Of String(), Microsoft.VisualBasic.Scripting.TextGrepMethodToken)
65 Imports r = System.Text.RegularExpressions.Regex
66 Imports System.ComponentModel
67
68 Namespace Scripting
69
70     ''' <summary>
71     ''' 
72     ''' </summary>
73     ''' <param name="source">文本源</param>
74     ''' <param name="args">脚本命令的参数</param>
75     ''' <returns></returns>
76     ''' <remarks></remarks>
77     Public Delegate Function TextGrepMethodToken(source$, args$()) As String
78     Public Delegate Function TextGrepMethod(source As StringAs String
79
80     ''' <summary>
81     ''' A script object for grep the gene id in the blast output query and subject title.
82     ''' (用于解析基因名称的脚本类,这个对象是在项目的初始阶段,为了方便命令行操作而设置的)
83     ''' </summary>
84     ''' <remarks></remarks>
85     Public NotInheritable Class TextGrepScriptEngine
86
87         Public Shared ReadOnly Property MethodPointers As New SortedDictionary(Of String, TextGrepMethodToken) From {
88  _
89             {"tokens"AddressOf TextGrepScriptEngine.Tokens},
90             {"match"AddressOf TextGrepScriptEngine.Match},
91             {"-"AddressOf TextGrepScriptEngine.NoOperation},
92             {"replace"AddressOf TextGrepScriptEngine.Replace},
93             {"mid"AddressOf TextGrepScriptEngine.MidString},
94             {"reverse"AddressOf TextGrepScriptEngine.Reverse}
95         }
96
97         Public Shared Function EnsureNotEmpty(ptr As TextGrepMethod) As TextGrepMethod
98             Return Function(str) As String
99                        Dim gs$ = ptr(str)
100
101                        If gs.StringEmpty Then
102                            Return str
103                        Else
104                            Return gs
105                        End If
106                    End Function
107         End Function
108
109         ''' <summary>
110         ''' Source,Script,ReturnValue
111         ''' </summary>
112         ''' <remarks></remarks>
113         Dim _operations As Token()
114         Dim _script$()
115
116         ''' <summary>
117         ''' 当前的这个脚本是否不进行任何操作
118         ''' </summary>
119         ''' <returns></returns>
120         Public ReadOnly Property IsDoNothing As Boolean
121             Get
122                 Dim emptyScript = _script.IsNullOrEmpty
123                 Dim emptyOperation = _script.Length = 1 AndAlso (_script.First = "-" OrElse _script.First.StringEmpty)
124
125                 Return emptyScript OrElse emptyOperation
126             End Get
127         End Property
128
129         ''' <summary>
130         ''' 对用户所输入的脚本进行编译,对于内部的空格,请使用单引号``'``进行分割
131         ''' </summary>
132         ''' <param name="scriptText">
133         ''' 如果这个参数传递的是一个空字符串,那么这个函数将会直接返回<see cref="DoNothing"/>脚本
134         ''' </param>
135         ''' <returns></returns>
136         ''' <remarks></remarks>
137         <ExportAPI("compile"Info:="", Usage:="script_tokens1;script_tokens2;....", Example:="")>
138         Public Shared Function Compile(scriptText As StringAs TextGrepScriptEngine
139             If scriptText.StringEmpty Then
140                 Return DoNothing
141             ElseIf scriptText = "-" Then
142                 Return DoNothing
143             Else
144                 Return CompileInternal(scriptText)
145             End If
146         End Function
147
148         Private Shared Function CompileInternal(scriptText As StringAs TextGrepScriptEngine
149             Dim script$() = TryParse(scriptText, TokenDelimited:=";"InnerDelimited:="'"c)
150             Dim builder = LinqAPI.Exec(Of Token) <=
151  _
152                 From sToken As String
153                 In script
154                 Let tokens As String() = TryParse(sToken, TokenDelimited:=" "InnerDelimited:="'"c)
155                 Let entryPoint As String = sToken.Split.First.ToLower
156                 Where MethodPointers.ContainsKey(entryPoint)
157                 Select New Token(tokens, _MethodPointers(entryPoint))
158
159             If script.Length > builder.Length Then
160                 ' 有非法的命令短语,则为了保护数据的一致性,这个
161                 ' 含有错误的语法的脚本是不能够用于操作的, 
162                 ' 则函数返回空指针
163                 Return Nothing
164             Else
165                 Return New TextGrepScriptEngine With {
166                     ._script = script,
167                     ._operations = builder
168                 }
169             End If
170         End Function
171
172         ''' <summary>
173         ''' Source in and then source out, no changes
174         ''' </summary>
175         ''' <returns></returns>
176         Public Shared ReadOnly Property DoNothing As DefaultValue(Of TextGrepScriptEngine)
177             Get
178                 Static opNothing As New TextGrepScriptEngine With {
179                     ._operations = {},
180                     ._script = {"-"}
181                 }
182                 Return opNothing
183             End Get
184         End Property
185
186         ''' <summary>
187         ''' <see cref="Grep"/>
188         ''' 字符串剪裁操作的函数指针
189         ''' </summary>
190         ''' <returns></returns>
191         Public ReadOnly Property PipelinePointer As TextGrepMethod
192             <MethodImpl(MethodImplOptions.AggressiveInlining)>
193             Get
194                 Return AddressOf Grep
195             End Get
196         End Property
197
198         Public Iterator Function Explains() As IEnumerable(Of String)
199             For Each op As Token In _operations
200                 Dim args$() = op.Key.Skip(1).Join("*".Replicate(5)).ToArray
201                 Dim description As DescriptionAttribute = op.Value _
202                     .Method _
203                     .GetCustomAttributes(GetType(DescriptionAttribute), True) _
204                     .First
205                 Dim explain$ = String.Format(description.Description, args)
206
207                 Yield explain
208             Next
209         End Function
210
211         ''' <summary>
212         ''' 修整目标字符串,按照脚本之中的方法取出所需要的字符串信息
213         ''' </summary>
214         ''' <param name="source"></param>
215         ''' <returns></returns>
216         ''' <remarks></remarks>
217         Public Function Grep(source As StringAs String
218             Dim doGrep As Func(Of StringToken, Integer) =
219                 Function(sourceText As String, method As Token) As Integer
220                     source = method.Value()(sourceText, method.Key) '迭代解析
221                     Return Len(source)
222                 End Function
223
224             ' 这里是迭代计算,所以请不要使用并行拓展
225             For Each operation As Token In _operations
226                 Call doGrep(source, operation)
227             Next
228
229             Return source
230         End Function
231
232         <MethodImpl(MethodImplOptions.AggressiveInlining)>
233         Public Overrides Function ToString() As String
234             Return _script.JoinBy(" -> ")
235         End Function
236
237         Protected Friend Sub New()
238         End Sub
239
240 #Region "API supports"
241
242         <MethodImpl(MethodImplOptions.AggressiveInlining)>
243         <ExportAPI("-"Info:="DO_NOTHING")>
244         <Description("Do nothing with the source input")>
245         Private Shared Function NoOperation(source As String, script As String()) As String
246             Return source
247         End Function
248
249         <MethodImpl(MethodImplOptions.AggressiveInlining)>
250         <ExportAPI("Reverse")>
251         <Description("Reverse the input source text")>
252         Private Shared Function Reverse(source As String, Script As String()) As String
253             Return source.Reverse.ToArray
254         End Function
255
256         ''' <summary>
257         ''' 
258         ''' </summary>
259         ''' <param name="Source"></param>
260         ''' <param name="Script"></param>
261         ''' <returns></returns>
262         ''' <remarks></remarks>
263         <ExportAPI("Tokens"Info:="", Usage:="tokens p_str pointer", Example:="")>
264         <Argument("pointer"False, Description:="pointer must be a zero base integer number which is smaller than 
265             the tokens array's length; pointer can also be assign of a specific string ""last"" to get the last 
266             element and ""first"" to get the first element in the tokens array.")>
267         <Description("Split source text with delimiter [{0}], and get the token at position [{1}]")>
268         Private Shared Function Tokens(source As String, script As String()) As String
269             Dim delimiter As String = script(1)
270             Dim Tstr As String() = Strings.Split(source, delimiter)
271
272             If String.Equals(script(2), "last"StringComparison.OrdinalIgnoreCase) Then
273                 Return If(Tstr.IsNullOrEmpty, "", Tstr.Last)
274             Else
275                 ' first指示符被计算为0,所以在这里不需要为first进行额外的处理
276                 Dim p As Integer = CInt(Val(script(2)))
277
278                 If Tstr.Length - 1 < p OrElse p < 0 Then
279                     Return ""
280                 Else
281                     Return If(Tstr.IsNullOrEmpty, "", Tstr(p))
282                 End If
283             End If
284         End Function
285
286         <ExportAPI("match"Info:="", Usage:="match pattern", Example:="")>
287         <Description("Get the text token which match pattern [{0}]")>
288         Private Shared Function Match(source As String, script As String()) As String
289             Dim pattern As String = script.Last
290             Return r.Match(source, pattern, RegexOptions.IgnoreCase).Value
291         End Function
292
293         ''' <summary>
294         ''' 
295         ''' </summary>
296         ''' <param name="source"></param>
297         ''' <param name="script">
298         ''' 向量之中的第一个元素为命令的名字,第二个元素为Mid函数的Start参数,第三个元素为Mid函数的Length参数,可以被忽略掉
299         ''' </param>
300         ''' <returns></returns>
301         ''' <remarks></remarks>
302         <ExportAPI("mid"Info:="Substring a token from the input text source.")>
303         <Description("Substring a region [{0}, {1}] from the input text source.")>
304         Private Shared Function MidString(source As String, script As String()) As String
305             Dim start As Integer = CInt(Val(script(1)))
306
307             If script.Length > 2 Then
308                 Dim length As Integer = CInt(Val(script(2)))
309                 Return Mid(source, start, length)
310             Else
311                 Return Mid(source, start)
312             End If
313         End Function
314
315         <ExportAPI("replace", Usage:="replace <regx_text> <replace_value>")>
316         <Description("replace source with [{1}] where match pattern [{0}]")>
317         Private Shared Function Replace(source As String, script As String()) As String
318             Dim regexp As New Regex(script(1))
319             Dim matchs = regexp.Matches(source)
320             Dim sBuilder As New StringBuilder(source)
321             Dim newValue = script(2)
322
323             For Each m As Match In matchs
324                 Call sBuilder.Replace(m.Value, newValue)
325             Next
326
327             Return sBuilder.ToString
328         End Function
329 #End Region
330     End Class
331 End Namespace