12 // A Group represents a syntax group
15 // Groups contains all of the groups that are defined
16 // You can access them in the map via their string name
17 var Groups map[string]Group
20 // String returns the group name attached to the specific group
21 func (g Group) String() string {
22 for k, v := range Groups {
30 // A Def is a full syntax definition for a language
31 // It has a filetype, information about how to detect the filetype based
32 // on filename or header (the first line of the file)
33 // Then it has the rules which define how to highlight the file
42 FtDetect [2]*regexp.Regexp
45 type HeaderYaml struct {
46 FileType string `yaml:"filetype"`
48 FNameRgx string `yaml:"filename"`
49 HeaderRgx string `yaml:"header"`
56 yamlSrc map[interface{}]interface{}
59 // A Pattern is one simple syntax rule
60 // It has a group that the rule belongs to, as well as
61 // the regular expression to match the pattern
67 // rules defines which patterns and regions can be used to highlight
75 // A region is a highlighted region (such as a multiline comment, or a string)
76 // It belongs to a group, and has start and end regular expressions
77 // A region also has rules of its own that only apply when matching inside the
78 // region and also rules from the above region do not match inside this region
79 // Note that a region may contain more regions
91 Groups = make(map[string]Group)
94 // MakeHeader takes a header (.hdr file) file and parses the header
95 // Header files make parsing more efficient when you only want to compute
96 // on the headers of syntax files
97 // A yaml file might take ~400us to parse while a header file only takes ~20us
98 func MakeHeader(data []byte) (*Header, error) {
99 lines := bytes.Split(data, []byte{'\n'})
101 return nil, errors.New("Header file has incorrect format")
103 header := new(Header)
105 header.FileType = string(lines[0])
106 fnameRgx := string(lines[1])
107 headerRgx := string(lines[2])
110 header.FtDetect[0], err = regexp.Compile(fnameRgx)
113 header.FtDetect[1], err = regexp.Compile(headerRgx)
123 // MakeHeaderYaml takes a yaml spec for a syntax file and parses the
125 func MakeHeaderYaml(data []byte) (*Header, error) {
126 var hdrYaml HeaderYaml
127 err := yaml.Unmarshal(data, &hdrYaml)
132 header := new(Header)
133 header.FileType = hdrYaml.FileType
135 if hdrYaml.Detect.FNameRgx != "" {
136 header.FtDetect[0], err = regexp.Compile(hdrYaml.Detect.FNameRgx)
138 if hdrYaml.Detect.HeaderRgx != "" {
139 header.FtDetect[1], err = regexp.Compile(hdrYaml.Detect.HeaderRgx)
149 func ParseFile(input []byte) (f *File, err error) {
150 // This is just so if we have an error, we can exit cleanly and return the parse error to the user
152 if r := recover(); r != nil {
156 err = fmt.Errorf("pkg: %v", r)
161 var rules map[interface{}]interface{}
162 if err = yaml.Unmarshal(input, &rules); err != nil {
169 for k, v := range rules {
171 filetype := v.(string)
173 f.FileType = filetype
181 // ParseDef parses an input syntax file into a highlight Def
182 func ParseDef(f *File, header *Header) (s *Def, err error) {
183 // This is just so if we have an error, we can exit cleanly and return the parse error to the user
185 if r := recover(); r != nil {
189 err = fmt.Errorf("pkg: %v", r)
199 for k, v := range rules {
201 inputRules := v.([]interface{})
203 rules, err := parseRules(inputRules, nil)
215 // HasIncludes returns whether this syntax def has any include statements
216 func HasIncludes(d *Def) bool {
217 hasIncludes := len(d.rules.includes) > 0
218 for _, r := range d.rules.regions {
219 hasIncludes = hasIncludes || hasIncludesInRegion(r)
224 func hasIncludesInRegion(region *region) bool {
225 hasIncludes := len(region.rules.includes) > 0
226 for _, r := range region.rules.regions {
227 hasIncludes = hasIncludes || hasIncludesInRegion(r)
232 // GetIncludes returns a list of filetypes that are included by this syntax def
233 func GetIncludes(d *Def) []string {
234 includes := d.rules.includes
235 for _, r := range d.rules.regions {
236 includes = append(includes, getIncludesInRegion(r)...)
241 func getIncludesInRegion(region *region) []string {
242 includes := region.rules.includes
243 for _, r := range region.rules.regions {
244 includes = append(includes, getIncludesInRegion(r)...)
249 // ResolveIncludes will sort out the rules for including other filetypes
250 // You should call this after parsing all the Defs
251 func ResolveIncludes(def *Def, files []*File) {
252 resolveIncludesInDef(files, def)
255 func resolveIncludesInDef(files []*File, d *Def) {
256 for _, lang := range d.rules.includes {
257 for _, searchFile := range files {
258 if lang == searchFile.FileType {
259 searchDef, _ := ParseDef(searchFile, nil)
260 d.rules.patterns = append(d.rules.patterns, searchDef.rules.patterns...)
261 d.rules.regions = append(d.rules.regions, searchDef.rules.regions...)
265 for _, r := range d.rules.regions {
266 resolveIncludesInRegion(files, r)
271 func resolveIncludesInRegion(files []*File, region *region) {
272 for _, lang := range region.rules.includes {
273 for _, searchFile := range files {
274 if lang == searchFile.FileType {
275 searchDef, _ := ParseDef(searchFile, nil)
276 region.rules.patterns = append(region.rules.patterns, searchDef.rules.patterns...)
277 region.rules.regions = append(region.rules.regions, searchDef.rules.regions...)
281 for _, r := range region.rules.regions {
282 resolveIncludesInRegion(files, r)
287 func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
289 if r := recover(); r != nil {
293 err = fmt.Errorf("pkg: %v", r)
299 for _, v := range input {
300 rule := v.(map[interface{}]interface{})
301 for k, val := range rule {
304 switch object := val.(type) {
307 ru.includes = append(ru.includes, object)
310 r, err := regexp.Compile(object)
315 groupStr := group.(string)
316 if _, ok := Groups[groupStr]; !ok {
318 Groups[groupStr] = numGroups
320 groupNum := Groups[groupStr]
321 ru.patterns = append(ru.patterns, &pattern{groupNum, r})
323 case map[interface{}]interface{}:
325 region, err := parseRegion(group.(string), object, curRegion)
329 ru.regions = append(ru.regions, region)
331 return nil, fmt.Errorf("Bad type %T", object)
339 func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegion *region) (r *region, err error) {
341 if r := recover(); r != nil {
345 err = fmt.Errorf("pkg: %v", r)
351 if _, ok := Groups[group]; !ok {
353 Groups[group] = numGroups
355 groupNum := Groups[group]
357 r.parent = prevRegion
359 r.start, err = regexp.Compile(regionInfo["start"].(string))
365 r.end, err = regexp.Compile(regionInfo["end"].(string))
372 if _, ok := regionInfo["skip"]; ok {
373 r.skip, err = regexp.Compile(regionInfo["skip"].(string))
380 // limit-color is optional
381 if _, ok := regionInfo["limit-group"]; ok {
382 groupStr := regionInfo["limit-group"].(string)
383 if _, ok := Groups[groupStr]; !ok {
385 Groups[groupStr] = numGroups
387 groupNum := Groups[groupStr]
388 r.limitGroup = groupNum
394 r.limitGroup = r.group
397 r.rules, err = parseRules(regionInfo["rules"].([]interface{}), r)