File: syntax_converter.go

package info (click to toggle)
micro 2.0.15-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,128 kB
  • sloc: sh: 265; makefile: 77; xml: 53
file content (165 lines) | stat: -rw-r--r-- 4,932 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//go:build ignore

package main

import (
	"fmt"
	"os"
	"regexp"
	"strings"
)

type SingleRule struct {
	color string
	regex string
}

type MultiRule struct {
	color string
	start string
	end   string
}

// JoinRule takes a syntax rule (which can be multiple regular expressions)
// and joins it into one regular expression by ORing everything together
func JoinRule(rule string) string {
	split := strings.Split(rule, `" "`)
	joined := strings.Join(split, "|")
	return joined
}

func parseFile(text, filename string) (filetype, syntax, header string, rules []any) {
	lines := strings.Split(text, "\n")

	// Regex for parsing syntax statements
	syntaxParser := regexp.MustCompile(`syntax "(.*?)"\s+"(.*)"+`)
	// Regex for parsing header statements
	headerParser := regexp.MustCompile(`header "(.*)"`)

	// Regex for parsing standard syntax rules
	ruleParser := regexp.MustCompile(`color (.*?)\s+(?:\((.+?)?\)\s+)?"(.*)"`)
	// Regex for parsing syntax rules with start="..." end="..."
	ruleStartEndParser := regexp.MustCompile(`color (.*?)\s+(?:\((.+?)?\)\s+)?start="(.*)"\s+end="(.*)"`)

	for lineNum, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}
		if strings.HasPrefix(line, "#") {
			continue
		}
		if strings.HasPrefix(line, "syntax") {
			syntaxMatches := syntaxParser.FindSubmatch([]byte(line))
			if len(syntaxMatches) == 3 {
				filetype = string(syntaxMatches[1])
				syntax = JoinRule(string(syntaxMatches[2]))
			} else {
				fmt.Println(filename, lineNum, "Syntax statement is not valid: "+line)
				continue
			}
		}
		if strings.HasPrefix(line, "header") {
			// Header statement
			headerMatches := headerParser.FindSubmatch([]byte(line))
			if len(headerMatches) == 2 {
				header = JoinRule(string(headerMatches[1]))
			} else {
				fmt.Println(filename, lineNum, "Header statement is not valid: "+line)
				continue
			}
		}

		// Syntax rule, but it could be standard or start-end
		if ruleParser.MatchString(line) {
			// Standard syntax rule
			// Parse the line
			submatch := ruleParser.FindSubmatch([]byte(line))
			var color string
			var regexStr string
			var flags string
			if len(submatch) == 4 {
				// If len is 4 then the user specified some additional flags to use
				color = string(submatch[1])
				flags = string(submatch[2])
				if flags != "" {
					regexStr = "(?" + flags + ")" + JoinRule(string(submatch[3]))
				} else {
					regexStr = JoinRule(string(submatch[3]))
				}
			} else if len(submatch) == 3 {
				// If len is 3, no additional flags were given
				color = string(submatch[1])
				regexStr = JoinRule(string(submatch[2]))
			} else {
				// If len is not 3 or 4 there is a problem
				fmt.Println(filename, lineNum, "Invalid statement: "+line)
				continue
			}

			rules = append(rules, SingleRule{color, regexStr})
		} else if ruleStartEndParser.MatchString(line) {
			// Start-end syntax rule
			submatch := ruleStartEndParser.FindSubmatch([]byte(line))
			var color string
			var start string
			var end string
			// Use m and s flags by default
			if len(submatch) == 5 {
				// If len is 5 the user provided some additional flags
				color = string(submatch[1])
				start = string(submatch[3])
				end = string(submatch[4])
			} else if len(submatch) == 4 {
				// If len is 4 the user did not provide additional flags
				color = string(submatch[1])
				start = string(submatch[2])
				end = string(submatch[3])
			} else {
				// If len is not 4 or 5 there is a problem
				fmt.Println(filename, lineNum, "Invalid statement: "+line)
				continue
			}

			// rules[color] = "(?" + flags + ")" + "(" + start + ").*?(" + end + ")"
			rules = append(rules, MultiRule{color, start, end})
		}
	}

	return
}

func generateFile(filetype, syntax, header string, rules []any) string {
	output := ""

	output += fmt.Sprintf("filetype: %s\n\n", filetype)
	output += fmt.Sprintf("detect: \n    filename: \"%s\"\n", strings.Replace(strings.Replace(syntax, "\\", "\\\\", -1), "\"", "\\\"", -1))

	if header != "" {
		output += fmt.Sprintf("    signature: \"%s\"\n", strings.Replace(strings.Replace(header, "\\", "\\\\", -1), "\"", "\\\"", -1))
	}

	output += "\nrules:\n"

	for _, r := range rules {
		if rule, ok := r.(SingleRule); ok {
			output += fmt.Sprintf("    - %s: \"%s\"\n", rule.color, strings.Replace(strings.Replace(rule.regex, "\\", "\\\\", -1), "\"", "\\\"", -1))
		} else if rule, ok := r.(MultiRule); ok {
			output += fmt.Sprintf("    - %s:\n", rule.color)
			output += fmt.Sprintf("        start: \"%s\"\n", strings.Replace(strings.Replace(rule.start, "\\", "\\\\", -1), "\"", "\\\"", -1))
			output += fmt.Sprintf("        end: \"%s\"\n", strings.Replace(strings.Replace(rule.end, "\\", "\\\\", -1), "\"", "\\\"", -1))
		}
	}

	return output
}

func main() {
	if len(os.Args) < 2 {
		fmt.Println("no args")
		return
	}

	data, _ := os.ReadFile(os.Args[1])
	fmt.Print(generateFile(parseFile(string(data), os.Args[1])))
}