随笔-341  评论-2670  文章-0  trackbacks-0

    词法分析器生成器终于做好了,因此我又画了一个状态机然后生成了一个词法分析器,因此开始研究IDE的智能提示的技术了。智能提示的技术有几个要点,第一个是无论怎么慢都不能妨碍你打字,第二个是崩溃了也不能让IDE关掉,要重启分析器。因此我做了一个小实验。首先我将NativeX语言的着色器跟词法分析器都做好了,因此我要做的事情就是在你打字的时候,用另外一个线程进行词法分析,得到结果之后用一个框框来展示出光标所在的那个token:




    注意光标所在的位置是在方框的内部。为了实现这个过程,首先我们要一个安全的多线程机制。我们把词法分析作为一个请求来看,在打字的过程中我们就不断把请求发送到词法分析器头上,然后词法分析器分析完了就会发个消息给窗口然后传送结果。我们注意到如果打字太快导致来不及处理的话,可能会积累若干个请求,不过那些请求实际上只有最后一个需要被处理,之前的那些来不及处理的旧请求都会被忽略。反正我们的字已经打完了,词法分析器就不需要分析那些我们还没打完的字了。因此我们把它抽象一下:

    我们有一个请求的类,这个类需要的数据有输入类型、输出类型、计算方法以及传送方法。为什么要传送方法呢?因为计算是在另外一个线程完成的,因此在调用传送方法的时候也是在另外一个线程完成的,所以我们得提供一个函数来接受分析后的结果,然后用一种安全的方法传送到我们想要的地方去。因此我们就把这个过程分成了两个类,一个处理多线程的问题的抽象类,和一个用来提供计算方法和传送方法的子类:
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 
 7 namespace CodeBoxControl
 8 {
 9     public abstract class CalculationNotifier<I, O> : IDisposable
10     {
11         private Thread workingThread = null;
12         private I analyzingInput = default(I);
13         private bool analyzingInputAvailable = false;
14         private Semaphore codeSemaphore = null;
15         private object locker = null;
16 
17         public CalculationNotifier()
18         {
19             this.locker = new object();
20             this.codeSemaphore = new Semaphore(01);
21             this.workingThread = new Thread(Run);
22             this.workingThread.Start();
23         }
24 
25         public void Analyze(I input)
26         {
27             bool needRelease = false;
28             lock (this.locker)
29             {
30                 if (!this.analyzingInputAvailable)
31                 {
32                     needRelease = true;
33                 }
34                 this.analyzingInputAvailable = true;
35                 this.analyzingInput = input;
36             }
37             if (needRelease)
38             {
39                 this.codeSemaphore.Release(1);
40             }
41         }
42 
43         public void Dispose()
44         {
45             this.workingThread.Abort();
46             this.codeSemaphore.Dispose();
47         }
48 
49         protected abstract O Calculate(I input);
50         protected abstract void Receive(O output);
51 
52         private void Run()
53         {
54             while (true)
55             {
56                 this.codeSemaphore.WaitOne();
57                 I input = default(I);
58                 lock (this.locker)
59                 {
60                     input = this.analyzingInput;
61                     this.analyzingInputAvailable = false;
62                 }
63                 O output = Calculate(input);
64                 Receive(output);
65             }
66         }
67     }
68 }
69 

    怎么使用它呢?我们只需要不断地把数据发送给Analyze函数,那个函数就会自动替我们处理好跟同步相关的所有问题了,然后在另外一个线程调用分析函数和返回结果:
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using CodeBoxControl;
 6 using CodeBoxControl.CodeProvider;
 7 
 8 namespace CodeForm
 9 {
10     class NativeXAnalyzingResult
11     {
12         public List<CodeToken> Tokens { getset; }
13     }
14 
15     interface INativeXAnalyzingResultReceiver
16     {
17         void Receive(NativeXAnalyzingResult result);
18     }
19 
20     class NativeXCodeAnalyzer : CalculationNotifier<string, NativeXAnalyzingResult>
21     {
22         private NativeXTokenizer tokenizer = new NativeXTokenizer();
23         private INativeXAnalyzingResultReceiver receiver = null;
24 
25         public NativeXCodeAnalyzer(INativeXAnalyzingResultReceiver receiver)
26         {
27             this.receiver = receiver;
28         }
29 
30         protected override NativeXAnalyzingResult Calculate(string input)
31         {
32             NativeXAnalyzingResult result = new NativeXAnalyzingResult();
33             result.Tokens = this.tokenizer.Tokenize(input.ToCharArray());
34             return result;
35         }
36 
37         protected override void Receive(NativeXAnalyzingResult output)
38         {
39             this.receiver.Receive(output);
40         }
41     }
42 }
43 

    这个类就是用来负责做词法分析的了。当然我们注意到此时接受数据的过程还是被抽象掉了,因为这个过程实际上应该让插件去处理,因为插件才知道要怎么画框。目前的控件支持两个插件,一个是着色器,另一个是监视器。监视器负责监听字符串的改变和插手绘图的过程,我们可以看出这个插件是如何把词法分析结果和画框框联系在一起的:
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using CodeBoxControl;
  6 using System.Windows.Forms;
  7 using CodeBoxControl.Core;
  8 using CodeBoxControl.CodeProvider;
  9 using System.Drawing;
 10 
 11 namespace CodeForm
 12 {
 13     class NativeXControlPanel
 14         : ITextEditorControlPanel
 15         , INativeXAnalyzingResultReceiver
 16         , IDisposable
 17     {
 18         private ITextEditorControlPanelCallBack callback = null;
 19         private NativeXAnalyzingResult analyzingResult = null;
 20         private NativeXCodeAnalyzer analyzer = null;
 21 
 22         private TextPosition grayStart = new TextPosition(00);
 23         private TextPosition grayEnd = new TextPosition(00);
 24 
 25         public int Width
 26         {
 27             get
 28             {
 29                 return 0;
 30             }
 31         }
 32 
 33         public void InstallCallBack(ITextEditorControlPanelCallBack callback)
 34         {
 35             this.analyzer = new NativeXCodeAnalyzer(this);
 36             this.callback = callback;
 37             this.callback.TextEditorBox.SelectionChanged += new EventHandler(TextEditorBox_SelectionChanged);
 38         }
 39 
 40         public void OnEdit(CodeBoxControl.Core.TextPosition start, CodeBoxControl.Core.TextPosition oldEnd, CodeBoxControl.Core.TextPosition newEnd)
 41         {
 42             this.analyzer.Analyze(this.callback.TextEditorBox.Text);
 43         }
 44 
 45         public bool NeedColorLineForDisplay(int lineIndex)
 46         {
 47             return this.grayStart != this.grayEnd && this.grayStart.row <= lineIndex && lineIndex <= this.grayEnd.row;
 48         }
 49 
 50         public void ColorLineForDisplay(int lineIndex, int[] colors)
 51         {
 52             TextLine<TextEditorBox.LineInfo> line = this.callback.TextEditorBox.TextProvider[lineIndex];
 53             int length = line.CharCount;
 54 
 55             int start = grayStart.row == lineIndex ? Math.Min(grayStart.col, length) : 0;
 56             int end = grayEnd.row == lineIndex ? Math.Min(grayEnd.col, length) : line.CharCount;
 57             for (int i = start; i < end; i++)
 58             {
 59                 colors[i] = NativeXColorizer.BlockPointColorId;
 60             }
 61         }
 62 
 63         public void DrawLineBackground(Graphics g, int lineIndex, Rectangle backgroundArea)
 64         {
 65         }
 66 
 67         public void DrawLineForeground(Graphics g, int lineIndex, Rectangle backgroundArea)
 68         {
 69             if (NeedColorLineForDisplay(lineIndex))
 70             {
 71                 TextLine<TextEditorBox.LineInfo> line = this.callback.TextEditorBox.TextProvider[lineIndex];
 72                 int length = line.CharCount;
 73                 int start = grayStart.row == lineIndex ? Math.Min(grayStart.col, length) : 0;
 74                 int end = grayEnd.row == lineIndex ? Math.Min(grayEnd.col, length) : line.CharCount;
 75 
 76                 int x1 = this.callback.TextEditorBox.TextPositionToViewPoint(new TextPosition(lineIndex, start)).X;
 77                 int x2 = this.callback.TextEditorBox.TextPositionToViewPoint(new TextPosition(lineIndex, end)).X;
 78                 g.DrawRectangle(Pens.Gray, x1, backgroundArea.Top, x2 - x1, backgroundArea.Height);
 79             }
 80         }
 81 
 82         public void DrawControlPanel(Graphics g, int lineIndex, Rectangle controlPanelArea)
 83         {
 84         }
 85 
 86         public void DrawControlPanelBackground(Graphics g, Rectangle backgroundArea)
 87         {
 88         }
 89 
 90         public void OnMouseDown(int lineIndex, Rectangle controlPanelArea, Point relativePosition, System.Windows.Forms.MouseButtons buttons)
 91         {
 92         }
 93 
 94         public void OnMouseMove(int lineIndex, Rectangle controlPanelArea, Point relativePosition, System.Windows.Forms.MouseButtons buttons)
 95         {
 96         }
 97 
 98         public void OnMouseUp(int lineIndex, Rectangle controlPanelArea, Point relativePosition, System.Windows.Forms.MouseButtons buttons)
 99         {
100         }
101 
102         public void Receive(NativeXAnalyzingResult result)
103         {
104             this.callback.TextEditorBox.Invoke(new MethodInvoker(() =>
105             {
106                 this.analyzingResult = result;
107                 UpdateBlock();
108             }));
109         }
110 
111         public void Dispose()
112         {
113             this.analyzer.Dispose();
114         }
115 
116         private void UpdateBlock()
117         {
118             NativeXAnalyzingResult result = this.analyzingResult;
119             TextPosition pos = this.callback.TextEditorBox.SelectionCaret;
120             if (result != null)
121             {
122                 foreach (CodeToken token in result.Tokens)
123                 {
124                     if (token.Start < pos && pos < token.End)
125                     {
126                         this.grayStart = token.Start;
127                         this.grayEnd = token.End;
128                         return;
129                     }
130                 }
131                 this.grayStart = new TextPosition(00);
132                 this.grayEnd = new TextPosition(00);
133             }
134         }
135 
136         private void TextEditorBox_SelectionChanged(object sender, EventArgs e)
137         {
138             UpdateBlock();
139         }
140     }
141 }
142 


    这里我们借助了System.Windows.Forms.Control.Invoke来将一整个闭包压到了消息循环里面,然后消息循环来执行这个闭包,因此接收数据的过程就在UI线程上完成了。Invoke跟SendMessage其实是差不多的。我们还能看出这个插件的接口叫ControlPanel,所以是用来干预一切变化的。这里有Width属性但是设置成0了,如果不是0的话就可以模仿VisualStudio左边那个放断点的控制栏了,这个在之前已经演示过了。

    到这里如何在非UI线程处理代码的技术就介绍完了,现在让我们来看着色器和词法分析器的样子哈。首先上着色器的状态机:


    然后是词法分析器的状态机:


    至于他们生成的代码,可以去Vczh Library++ 3.0这里看哈。

 

posted on 2010-10-15 00:23 陈梓瀚(vczh) 阅读(4745) 评论(6)  编辑 收藏 引用 所属分类: 开发自己的IDE

评论:
# re: 开发自己的IDE(六)[未登录] 2010-10-15 00:33 | Lyt
看一次流一次口水…  回复  更多评论
  
# re: 开发自己的IDE(六) 2010-10-15 08:51 | Pear
MB真帅气。。MB真帅气。。真帅气。。  回复  更多评论
  
# re: 开发自己的IDE(六) 2010-10-16 23:57 | mm
好强大!!!  回复  更多评论
  
# re: 开发自己的IDE(六) 2010-10-18 00:45 | hhh2000
图有点小,有些符号看不清楚……

对着色器状态机的图有点疑问:1、如果CharIn是指单个字符的话,好像少了一个状态。2、StringIn指向自己的边是非 \ 和 " 号吗(图看不清)?这好像不大对吧。还

有单双引号一般只影响本行,加上一个行尾的状态约束一下,这样在引号不匹配的情况下看起来会好一些。
  回复  更多评论
  
# re: 开发自己的IDE(六) 2010-10-19 17:41 | 陈梓瀚(vczh)
@hhh2000
为了简单,char我还是允许他拥有多个字符了,反正对于一个字符的状态可以正确解析,而且这是用在IDE上面的,势必跟编译器会有一些小的区别

StringIn那个东西是\.哈,也就是任意字符的意思。  回复  更多评论
  
# re: 开发自己的IDE(六) 2010-10-19 17:42 | 陈梓瀚(vczh)
@hhh2000
到了行末尾其实就是一个\n字符,只要你在状态机上面把\n给排除了,自然就会只有一行了。  回复  更多评论
  

只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理