词法分析器生成器终于做好了,因此我又画了一个状态机然后生成了一个词法分析器,因此开始研究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(0, 1);
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 { get; set; }
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(0, 0);
23 private TextPosition grayEnd = new TextPosition(0, 0);
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(0, 0);
132 this.grayEnd = new TextPosition(0, 0);
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-14 08:23
陈梓瀚(vczh) 阅读(5837)
评论(6) 编辑 收藏 引用 所属分类:
开发自己的IDE