]> git.lizzy.rs Git - micro.git/blob - runtime/syntax/syntax_converter.go
Properly escape start and end regexes
[micro.git] / runtime / syntax / syntax_converter.go
1 package main
2
3 import (
4         "fmt"
5         "io/ioutil"
6         "os"
7         "regexp"
8         "strings"
9 )
10
11 type SingleRule struct {
12         color string
13         regex string
14 }
15
16 type MultiRule struct {
17         color string
18         start string
19         end   string
20 }
21
22 // JoinRule takes a syntax rule (which can be multiple regular expressions)
23 // and joins it into one regular expression by ORing everything together
24 func JoinRule(rule string) string {
25         split := strings.Split(rule, `" "`)
26         joined := strings.Join(split, "|")
27         joined = joined
28         return joined
29 }
30
31 func parseFile(text, filename string) (filetype, syntax, header string, rules []interface{}) {
32         lines := strings.Split(text, "\n")
33
34         // Regex for parsing syntax statements
35         syntaxParser := regexp.MustCompile(`syntax "(.*?)"\s+"(.*)"+`)
36         // Regex for parsing header statements
37         headerParser := regexp.MustCompile(`header "(.*)"`)
38
39         // Regex for parsing standard syntax rules
40         ruleParser := regexp.MustCompile(`color (.*?)\s+(?:\((.+?)?\)\s+)?"(.*)"`)
41         // Regex for parsing syntax rules with start="..." end="..."
42         ruleStartEndParser := regexp.MustCompile(`color (.*?)\s+(?:\((.+?)?\)\s+)?start="(.*)"\s+end="(.*)"`)
43
44         for lineNum, line := range lines {
45                 line = strings.TrimSpace(line)
46                 if line == "" {
47                         continue
48                 }
49                 if strings.HasPrefix(line, "#") {
50                         continue
51                 }
52                 if strings.HasPrefix(line, "syntax") {
53                         syntaxMatches := syntaxParser.FindSubmatch([]byte(line))
54                         if len(syntaxMatches) == 3 {
55                                 filetype = string(syntaxMatches[1])
56                                 syntax = JoinRule(string(syntaxMatches[2]))
57                         } else {
58                                 fmt.Println(filename, lineNum, "Syntax statement is not valid: "+line)
59                                 continue
60                         }
61                 }
62                 if strings.HasPrefix(line, "header") {
63                         // Header statement
64                         headerMatches := headerParser.FindSubmatch([]byte(line))
65                         if len(headerMatches) == 2 {
66                                 header = JoinRule(string(headerMatches[1]))
67                         } else {
68                                 fmt.Println(filename, lineNum, "Header statement is not valid: "+line)
69                                 continue
70                         }
71                 }
72
73                 // Syntax rule, but it could be standard or start-end
74                 if ruleParser.MatchString(line) {
75                         // Standard syntax rule
76                         // Parse the line
77                         submatch := ruleParser.FindSubmatch([]byte(line))
78                         var color string
79                         var regexStr string
80                         var flags string
81                         if len(submatch) == 4 {
82                                 // If len is 4 then the user specified some additional flags to use
83                                 color = string(submatch[1])
84                                 flags = string(submatch[2])
85                                 if flags != "" {
86                                         regexStr = "(?" + flags + ")" + JoinRule(string(submatch[3]))
87                                 } else {
88                                         regexStr = JoinRule(string(submatch[3]))
89                                 }
90                         } else if len(submatch) == 3 {
91                                 // If len is 3, no additional flags were given
92                                 color = string(submatch[1])
93                                 regexStr = JoinRule(string(submatch[2]))
94                         } else {
95                                 // If len is not 3 or 4 there is a problem
96                                 fmt.Println(filename, lineNum, "Invalid statement: "+line)
97                                 continue
98                         }
99
100                         rules = append(rules, SingleRule{color, regexStr})
101                 } else if ruleStartEndParser.MatchString(line) {
102                         // Start-end syntax rule
103                         submatch := ruleStartEndParser.FindSubmatch([]byte(line))
104                         var color string
105                         var start string
106                         var end string
107                         // Use m and s flags by default
108                         flags := "ms"
109                         if len(submatch) == 5 {
110                                 // If len is 5 the user provided some additional flags
111                                 color = string(submatch[1])
112                                 flags += string(submatch[2])
113                                 start = string(submatch[3])
114                                 end = string(submatch[4])
115                         } else if len(submatch) == 4 {
116                                 // If len is 4 the user did not provide additional flags
117                                 color = string(submatch[1])
118                                 start = string(submatch[2])
119                                 end = string(submatch[3])
120                         } else {
121                                 // If len is not 4 or 5 there is a problem
122                                 fmt.Println(filename, lineNum, "Invalid statement: "+line)
123                                 continue
124                         }
125
126                         // rules[color] = "(?" + flags + ")" + "(" + start + ").*?(" + end + ")"
127                         rules = append(rules, MultiRule{color, start, end})
128                 }
129         }
130
131         return
132 }
133
134 func generateFile(filetype, syntax, header string, rules []interface{}) string {
135         output := ""
136
137         output += fmt.Sprintf("filetype: %s\n\n", filetype)
138         output += fmt.Sprintf("detect: \n    filename: \"%s\"\n", strings.Replace(syntax, "\\", "\\\\", -1))
139
140         if header != "" {
141                 output += fmt.Sprintf("    header: \"%s\"\n", strings.Replace(header, "\\", "\\\\", -1))
142         }
143
144         output += "\nrules:\n"
145
146         for _, r := range rules {
147                 if rule, ok := r.(SingleRule); ok {
148                         output += fmt.Sprintf("    - %s: \"%s\"\n", rule.color, strings.Replace(strings.Replace(rule.regex, "\\", "\\\\", -1), "\"", "\\\"", -1))
149                 } else if rule, ok := r.(MultiRule); ok {
150                         output += fmt.Sprintf("    - %s:\n", rule.color)
151                         output += fmt.Sprintf("        start: \"%s\"\n", strings.Replace(strings.Replace(rule.start, "\\", "\\\\", -1), "\"", "\\\"", -1))
152                         output += fmt.Sprintf("        end: \"%s\"\n", strings.Replace(strings.Replace(rule.end, "\\", "\\\\", -1), "\"", "\\\"", -1))
153                         output += fmt.Sprintf("        rules: []\n\n")
154                 }
155         }
156
157         return output
158 }
159
160 func main() {
161         if len(os.Args) < 2 {
162                 fmt.Println("no args")
163                 return
164         }
165
166         data, _ := ioutil.ReadFile(os.Args[1])
167         fmt.Print(generateFile(parseFile(string(data), os.Args[1])))
168 }