1
+ require 'set'
2
+
3
+ module CodeRay
4
+ module Scanners
5
+ module RougeScannerDSL
6
+ NoStatesError = Class . new StandardError
7
+
8
+ State = Struct . new :name , :rules do
9
+ def initialize ( name , &block )
10
+ super name , [ ]
11
+
12
+ instance_eval ( &block )
13
+ end
14
+
15
+ def code scanner
16
+ <<-RUBY
17
+ when #{ name . inspect }
18
+ #{ rules_code ( scanner ) . chomp . gsub ( /^/ , ' ' ) }
19
+ else
20
+ encoder.text_token getch, :error
21
+ end
22
+ RUBY
23
+ end
24
+
25
+ def rules_code scanner , first : true
26
+ raise 'no rules defined for %p' % [ self ] if rules . empty?
27
+
28
+ [
29
+ rules . first . code ( scanner , first : first ) ,
30
+ *rules . drop ( 1 ) . map { |rule | rule . code ( scanner ) }
31
+ ] . join
32
+ end
33
+
34
+ protected
35
+
36
+ # DSL
37
+
38
+ def rule pattern , token = nil , next_state = nil , &block
39
+ unless token || block
40
+ raise 'please pass `rule` a token to yield or a callback'
41
+ end
42
+
43
+ case token
44
+ when Class
45
+ unless token < Rouge ::Token
46
+ raise "invalid token: #{ token . inspect } "
47
+ end
48
+
49
+ case next_state
50
+ when Symbol
51
+ rules << Rule . new ( pattern , token , next_state )
52
+ when nil
53
+ rules << Rule . new ( pattern , token )
54
+ else
55
+ raise "invalid next state: #{ next_state . inspect } "
56
+ end
57
+ when nil
58
+ rules << CallbackRule . new ( pattern , block )
59
+ else
60
+ raise "invalid token: #{ token . inspect } "
61
+ end
62
+ end
63
+
64
+ def mixin state_name
65
+ rules << Mixin . new ( state_name )
66
+ end
67
+ end
68
+
69
+ Rule = Struct . new :pattern , :token , :action do
70
+ def initialize ( pattern , token , action = nil )
71
+ super
72
+ end
73
+
74
+ def code scanner , first : false
75
+ <<-RUBY + action_code . to_s
76
+ #{ 'els' unless first } if match = scan(#{ pattern . inspect } )
77
+ encoder.text_token match, #{ token . token_chain . map ( &:name ) . join ( '::' ) }
78
+ RUBY
79
+ end
80
+
81
+ def action_code
82
+ case action
83
+ when :pop!
84
+ <<-RUBY
85
+ states.pop
86
+ state = states.last
87
+ RUBY
88
+ when Symbol
89
+ <<-RUBY
90
+ state = #{ action . inspect }
91
+ states << state
92
+ RUBY
93
+ end
94
+ end
95
+ end
96
+
97
+ CallbackRule = Struct . new :pattern , :callback do
98
+ def code scanner , first : false
99
+ <<-RUBY
100
+ #{ 'els' unless first } if match = scan(#{ pattern . inspect } )
101
+ @match = match
102
+ #{ scanner . add_callback ( callback ) }
103
+ RUBY
104
+ end
105
+ end
106
+
107
+ Mixin = Struct . new ( :state_name ) do
108
+ def code scanner , first : false
109
+ scanner . states [ state_name ] . rules_code ( scanner , first : first )
110
+ end
111
+ end
112
+
113
+ attr_accessor :states
114
+
115
+ def state name , &block
116
+ @states ||= { }
117
+ @states [ name ] = State . new ( name , &block )
118
+ end
119
+
120
+ def add_callback block
121
+ base_name = "__callback_line_#{ block . source_location . last } "
122
+ callback_name = base_name
123
+ counter = 'a'
124
+ while callbacks . key? ( callback_name )
125
+ callback_name = "#{ base_name } _#{ counter } "
126
+ counter = counter . succ
127
+ end
128
+
129
+ callbacks [ callback_name ] = define_method ( callback_name , &block )
130
+
131
+ parameters = block . parameters
132
+
133
+ if parameters . empty?
134
+ callback_name
135
+ else
136
+ parameter_names = parameters . map do |type , name |
137
+ raise "callbacks don't allow rest parameters: %p" % [ parameters ] unless type == :req || type == :opt
138
+ name = :match if name == :m
139
+ name
140
+ end
141
+
142
+ parameter_names . each { |name | variables << name }
143
+ "#{ callback_name } (#{ parameter_names . join ( ', ' ) } )"
144
+ end
145
+ end
146
+
147
+ def add_variable name
148
+ variables << name
149
+ end
150
+
151
+ protected
152
+
153
+ def callbacks
154
+ @callbacks ||= { }
155
+ end
156
+
157
+ def variables
158
+ @variables ||= Set . new
159
+ end
160
+
161
+ def additional_variables
162
+ variables - %i( encoder options state states match kind )
163
+ end
164
+
165
+ def scan_tokens_code
166
+ <<-"RUBY"
167
+ state = options[:state] || @state
168
+ states = [state]
169
+ #{ restore_local_variables_code }
170
+ until eos?
171
+ case state
172
+ #{ states_code . chomp . gsub ( /^/ , ' ' ) }
173
+ else
174
+ raise_inspect 'Unknown state: %p' % [state], encoder
175
+ end
176
+ end
177
+
178
+ @state = state if options[:keep_state]
179
+
180
+ close_groups(encoder, states)
181
+
182
+ encoder
183
+ RUBY
184
+ end
185
+
186
+ def restore_local_variables_code
187
+ additional_variables . sort . map { |name | "#{ name } = @#{ name } " } . join ( "\n " )
188
+ end
189
+
190
+ def states_code
191
+ unless defined? ( @states ) && !@states . empty?
192
+ raise NoStatesError , 'no states defined for %p' % [ self . class ]
193
+ end
194
+
195
+ @states . values . map { |state | state . code ( self ) } . join
196
+ end
197
+ end
198
+ end
199
+ end
0 commit comments