aboutsummaryrefslogtreecommitdiffhomepage
path: root/lib/scite2co.lua
blob: b33220a44240c940e423a85e20fced89f43b7647 (plain)
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/lua

-- string property storage
-- this is what SciTE uses internally
local props = {}

-- Recursively expand property references like "$(property)"
-- This is done lazily as in SciTE, i.e. not
-- when loading property sets
function expand(str)
	return str and str:gsub("$(%b())", function(ref)
		-- strips braces and expands nested references
		return expand(props[expand(ref:sub(2, -2))]) or ""
	end)
end

-- SciTE property files use properties like
-- keywords.$(file.patterns.lua)
-- When looking for a list of keywords, it inefficiently
-- evaluates all properties beginning with "keywords.", expands
-- the suffix and globs the current file name against the pattern.
-- We have no choice than to emulate that behaviour.
-- However SciTECO does not rematch the current file name against
-- the pattern for every SCI_SETKEYWORDS, so we try to check if
-- the expanded suffix contains the file pattern itself
function get_property_by_pattern(prefix, pattern)
	for key, value in pairs(props) do
		if key:sub(1, #prefix) == prefix and
		   expand(key:sub(1+#prefix)):find(pattern, 1, true) then
			return value
		end
	end
	-- if property not found, return nil
end

function load_properties(filename)
	local file = io.open(filename, "r")
	local line = file:read()

	while line do
		-- strip leading white space
		line = line:gsub("^%s", "")

		-- strip comments
		line = line:gsub("^#.*", "")

		-- read continuations
		while line:sub(-1) == "\\" do
			line = line:sub(1, -2)..(file:read() or "")
		end

		if line:find("^if ") then
			-- ignore conditionals
			repeat
				line = file:read()
			until not line or line:sub(1, 1) ~= "\t"
		else
			-- process assignments
			-- property references are not yet expanded,
			-- since SciTE relies on lazy evaluation
			local key, value = line:match("^([^=]+)=(.*)")
			if key then props[key] = value end

			line = file:read()
		end
	end

	file:close()
end

-- redistribute (left-adjust) words (separated by spaces),
-- so that lines break around 80 characters
function reflow(str, indent)
	local col
	for word in str:gmatch("%g+") do
		if not col or col > 80 then
			io.write("\n", string.rep(" ", indent-1))
			col = indent-1
		end
		col = col + 1 + #word
		io.write(" ", word)
	end
end

if #arg ~= 2 then
	io.stderr:write("Usage: ./scite2co.lua <language> <property file>\n\n"..
	                "Auto-generates a SciTECO lexer configuration from a SciTE\n"..
	                "properties file and prints it to stdout.\n\n"..
	                "Example: ./scite2co.lua lua Embedded.properties\n")
	os.exit(1)
end

-- language to extract is first command line argument
local language = arg[1]
table.remove(arg, 1)

-- load all property files given on the command line in order
for _, filename in ipairs(arg) do
	load_properties(filename)
end

io.write("!* AUTO-GENERATED FROM SCITE PROPERTY SET *!\n\n")

-- print [lexer.test...] macro
local shbang = expand(props["shbang."..language])
local file_patterns = expand(props["file.patterns."..language])
io.write([=[
@[lexer.test.]=]..language:lower()..[=[]{
]=])
if shbang then io.write([=[  _#!M]=]..shbang..[=[M[lexer.checkheader]"S -1 '
]=]) end
local patterns = {}
for pattern in file_patterns:gmatch("[^;]+") do
	table.insert(patterns, pattern)
end
for i, pattern in ipairs(patterns) do
	io.write([=[  :EN]=]..pattern..[=[Q*]=])
	if i ~= #patterns then io.write([=["S -1 ']=]) end
	io.write("\n")
end
io.write([=[}

]=])

-- print [lexer.set...] macro
-- NOTE: The lexer encoded in the property file is not
-- a SCLEX_* name but rather the lexer's module name
-- as set by the LexerModule constructor.
-- Therefore we must emit SCI_SETILEXER calls here.
local lexer = expand(get_property_by_pattern("lexer.", file_patterns))
io.write([=[
@[lexer.set.]=]..language:lower()..[=[]{
  ESSETILEXER]=]..lexer..[=[
]=])

-- print keyword definitions with word wrapping
for i = 1, 9 do
	local keyword_prefix = "keywords"..(i == 1 and "" or i).."."
	local value = expand(get_property_by_pattern(keyword_prefix, file_patterns))

	if value and #value > 0 then
		io.write("  "..(i-1).."ESSETKEYWORDS")
		reflow(value, 4)
		io.write("\n")
	end
end

-- print styles
-- SciTE has a single list of styles per Scintilla lexer which
-- is used by multiple languages - we output these definitions
-- for every language.
-- The SciTE colour settings are mapped to SciTECO color profiles
-- by heuristically analyzing SciTE's style definitions.
local style_mapping = {
	["font.comment"]		= "color.comment",
	["colour.code.comment.box"]	= "color.comment",
	["colour.code.comment.line"]	= "color.comment",
	["colour.code.comment.doc"]	= "color.comment",
	["colour.number"]		= "color.number",
	["colour.keyword"]		= "color.keyword",
	["colour.string"]		= "color.string",
	["colour.char"]			= "color.string2",
	["colour.preproc"]		= "color.preproc",
	["colour.operator"]		= "color.operator",
	["colour.error"]		= "color.error"
}
function emit_style(prop, i)
	local value = props[prop]
	if not value then return end

	for p in value:gmatch("$(%b())") do
		local sciteco_color = style_mapping[p:sub(2, -2)]

		if sciteco_color then
			io.write("  :M["..sciteco_color.."],"..i.."M[color.set]\n")
			break
		end
	end
end

for i = 0, 255 do
	local substyles = props["substyles."..lexer.."."..i]
	if substyles then
		io.write("  "..substyles..","..i.."ESALLOCATESUBSTYLESU.s\n")
		for substyle = 1, substyles do
			local id_prefix = "substylewords."..i.."."..substyle.."."
			local value = expand(get_property_by_pattern(id_prefix, file_patterns))

			if value and #value > 0 then
				io.write("  Q.s+"..(substyle-1).."ESSETIDENTIFIERS")
				reflow(value, 4)
				io.write("\n")
			end

			emit_style("style."..lexer.."."..i.."."..substyle,
			           "Q.s+"..(substyle-1))
		end
	end

	emit_style("style."..lexer.."."..i, i)
end
io.write("}\n")