]> git.lizzy.rs Git - micro.git/blob - cmd/micro/highlight/parser.go
Don't skip included rules in end
[micro.git] / cmd / micro / highlight / parser.go
1 package highlight
2
3 import (
4         "fmt"
5         "regexp"
6
7         "gopkg.in/yaml.v2"
8 )
9
10 // A Group represents a syntax group
11 type Group uint8
12
13 // Groups contains all of the groups that are defined
14 // You can access them in the map via their string name
15 var Groups map[string]Group
16 var numGroups Group
17
18 // String returns the group name attached to the specific group
19 func (g Group) String() string {
20         for k, v := range Groups {
21                 if v == g {
22                         return k
23                 }
24         }
25         return ""
26 }
27
28 // A Def is a full syntax definition for a language
29 // It has a filetype, information about how to detect the filetype based
30 // on filename or header (the first line of the file)
31 // Then it has the rules which define how to highlight the file
32 type Def struct {
33         FileType string
34         ftdetect []*regexp.Regexp
35         rules    *rules
36 }
37
38 // A Pattern is one simple syntax rule
39 // It has a group that the rule belongs to, as well as
40 // the regular expression to match the pattern
41 type pattern struct {
42         group Group
43         regex *regexp.Regexp
44 }
45
46 // rules defines which patterns and regions can be used to highlight
47 // a filetype
48 type rules struct {
49         regions  []*region
50         patterns []*pattern
51         includes []string
52 }
53
54 // A region is a highlighted region (such as a multiline comment, or a string)
55 // It belongs to a group, and has start and end regular expressions
56 // A region also has rules of its own that only apply when matching inside the
57 // region and also rules from the above region do not match inside this region
58 // Note that a region may contain more regions
59 type region struct {
60         group  Group
61         parent *region
62         start  *regexp.Regexp
63         end    *regexp.Regexp
64         skip   *regexp.Regexp
65         rules  *rules
66 }
67
68 func init() {
69         Groups = make(map[string]Group)
70 }
71
72 // ParseDef parses an input syntax file into a highlight Def
73 func ParseDef(input []byte) (s *Def, err error) {
74         // This is just so if we have an error, we can exit cleanly and return the parse error to the user
75         defer func() {
76                 if e := recover(); e != nil {
77                         err = e.(error)
78                 }
79         }()
80
81         var rules map[interface{}]interface{}
82         if err = yaml.Unmarshal(input, &rules); err != nil {
83                 return nil, err
84         }
85
86         s = new(Def)
87
88         for k, v := range rules {
89                 if k == "filetype" {
90                         filetype := v.(string)
91
92                         s.FileType = filetype
93                 } else if k == "detect" {
94                         ftdetect := v.(map[interface{}]interface{})
95                         if len(ftdetect) >= 1 {
96                                 syntax, err := regexp.Compile(ftdetect["filename"].(string))
97                                 if err != nil {
98                                         return nil, err
99                                 }
100
101                                 s.ftdetect = append(s.ftdetect, syntax)
102                         }
103                         if len(ftdetect) >= 2 {
104                                 header, err := regexp.Compile(ftdetect["header"].(string))
105                                 if err != nil {
106                                         return nil, err
107                                 }
108
109                                 s.ftdetect = append(s.ftdetect, header)
110                         }
111                 } else if k == "rules" {
112                         inputRules := v.([]interface{})
113
114                         rules, err := parseRules(inputRules, nil)
115                         if err != nil {
116                                 return nil, err
117                         }
118
119                         s.rules = rules
120                 }
121         }
122
123         return s, err
124 }
125
126 // ResolveIncludes will sort out the rules for including other filetypes
127 // You should call this after parsing all the Defs
128 func ResolveIncludes(defs []*Def) {
129         for _, d := range defs {
130                 resolveIncludesInDef(defs, d)
131         }
132 }
133
134 func resolveIncludesInDef(defs []*Def, d *Def) {
135         for _, lang := range d.rules.includes {
136                 for _, searchDef := range defs {
137                         if lang == searchDef.FileType {
138                                 d.rules.patterns = append(d.rules.patterns, searchDef.rules.patterns...)
139                                 d.rules.regions = append(d.rules.regions, searchDef.rules.regions...)
140                         }
141                 }
142         }
143         for _, r := range d.rules.regions {
144                 resolveIncludesInRegion(defs, r)
145                 r.parent = nil
146         }
147 }
148
149 func resolveIncludesInRegion(defs []*Def, region *region) {
150         for _, lang := range region.rules.includes {
151                 for _, searchDef := range defs {
152                         if lang == searchDef.FileType {
153                                 region.rules.patterns = append(region.rules.patterns, searchDef.rules.patterns...)
154                                 region.rules.regions = append(region.rules.regions, searchDef.rules.regions...)
155                         }
156                 }
157         }
158         for _, r := range region.rules.regions {
159                 resolveIncludesInRegion(defs, r)
160                 r.parent = region
161         }
162 }
163
164 func parseRules(input []interface{}, curRegion *region) (*rules, error) {
165         rules := new(rules)
166
167         for _, v := range input {
168                 rule := v.(map[interface{}]interface{})
169                 for k, val := range rule {
170                         group := k
171
172                         switch object := val.(type) {
173                         case string:
174                                 if k == "include" {
175                                         rules.includes = append(rules.includes, object)
176                                 } else {
177                                         // Pattern
178                                         r, err := regexp.Compile(object)
179                                         if err != nil {
180                                                 return nil, err
181                                         }
182
183                                         groupStr := group.(string)
184                                         if _, ok := Groups[groupStr]; !ok {
185                                                 numGroups++
186                                                 Groups[groupStr] = numGroups
187                                         }
188                                         groupNum := Groups[groupStr]
189                                         rules.patterns = append(rules.patterns, &pattern{groupNum, r})
190                                 }
191                         case map[interface{}]interface{}:
192                                 // region
193                                 region, err := parseRegion(group.(string), object, curRegion)
194                                 if err != nil {
195                                         return nil, err
196                                 }
197                                 rules.regions = append(rules.regions, region)
198                         default:
199                                 return nil, fmt.Errorf("Bad type %T", object)
200                         }
201                 }
202         }
203
204         return rules, nil
205 }
206
207 func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegion *region) (*region, error) {
208         var err error
209
210         region := new(region)
211         if _, ok := Groups[group]; !ok {
212                 numGroups++
213                 Groups[group] = numGroups
214         }
215         groupNum := Groups[group]
216         region.group = groupNum
217         region.parent = prevRegion
218
219         region.start, err = regexp.Compile(regionInfo["start"].(string))
220
221         if err != nil {
222                 return nil, err
223         }
224
225         region.end, err = regexp.Compile(regionInfo["end"].(string))
226
227         if err != nil {
228                 return nil, err
229         }
230
231         // skip is optional
232         if _, ok := regionInfo["skip"]; ok {
233                 region.skip, err = regexp.Compile(regionInfo["skip"].(string))
234
235                 if err != nil {
236                         return nil, err
237                 }
238         }
239
240         region.rules, err = parseRules(regionInfo["rules"].([]interface{}), region)
241
242         if err != nil {
243                 return nil, err
244         }
245
246         return region, nil
247 }