-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathformateum.ex
More file actions
361 lines (312 loc) · 12.7 KB
/
formateum.ex
File metadata and controls
361 lines (312 loc) · 12.7 KB
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
-- This program modifies your source code. It will make sure that each line of source is correctly
-- indented. Correctly means, that inside a type,function,procedure,for,loop,while,if,else,elsif,
-- case,or switch statement, other statements are four spaces more to the right.
--
-- Usage is:
--
-- eui formateum.ex [--follow|-f] source_file1 source_file2 ....
--
-- -f or --follow is optional.
-- source_file1 source_file2 ... should be at least one source file.
--
-- If you use the --follow option, the program will wait for changes and reproess the file after each
-- change to the file's timestamp.
--
include euphoria/tokenize.e
include std/io.e
include std/filesys.e
include std/types.e
include std/math.e
include std/search.e
include std/os.e
constant cl = command_line()
boolean follow_flag = 0
constant args = cl[3..$]
keep_whitespace(1)
keep_comments(1)
string_strip_quotes(0)
string_numbers(1)
keep_keywords(1)
type token_sequence(object x)
if atom(x) then
return 0
end if
for i = 1 to length(x) do
if length(x[i]) != 5 then
return 0
end if
end for
return 1
end type
constant block_starters = { "if", "loop", "while", "for", "switch", "function", "procedure", "type", "ifdef" }
constant left_shifter = { "else", "elsif", "entry", "until" }
enum ERR_CANNOT_WRITE_TEMPORARY_FILE = ERR_HEX_STRING+1, ERR_TOO_FAR_LEFT, ERR_END_WITH_NO_BLOCK, ERR_WRONG_END, ERR_INTERNAL_ERROR
-- Takes Euphoria code from in_filename and writes the formatted Euphoria to out_filename
-- A pair is returned. The first member of the pair is a set of errors. When no errors occur this is an empty sequence.
-- The second member is the tokens that come out.
export function format_tokens(sequence tokens)
sequence errors = {}
sequence block_stack = {}
sequence out_tokens = {}
boolean new_line_starts = 1
integer start_column = 0
integer this_line_shift = 0
integer token_start = 1
token_sequence current_line = {}
integer switch_column = 0
boolean last_keyword_case = 0
boolean last_keyword_end = 0
sequence last_keyword = ""
for tokeni = 1 to length(tokens) do
sequence t = tokens[tokeni]
sequence v = t[TDATA]
switch t[TTYPE] do
case T_NEWLINE then
case T_WHITE then
if find('\n', v) then
new_line_starts = 1
t[TDATA] = "\n"
current_line = current_line & t
if this_line_shift < 0 then
errors = append(errors, sprintf("Error processing at %d:%d\n", t[TLNUM..TLPOS]))
this_line_shift = 0
end if
current_line = {{T_WHITE, repeat(' ', this_line_shift * 4), t[TLNUM], 0, 0}} & current_line
out_tokens = out_tokens & current_line
--show_tokens(outfn, tokens[token_start..tokeni])
token_start = tokeni + 1
current_line = ""
this_line_shift = start_column
elsif new_line_starts then
-- nothing
else
current_line = current_line & v
end if
case T_KEYWORD then
new_line_starts = 0
if equal(last_keyword,"end") then
if length(block_stack) = 0 then
errors = append(errors, sprintf("Unbalanced keyword %s: Block stack empty\n", {v}) )
elsif compare(v, block_stack[$][1]) then
errors = append(errors, sprintf("End statement should be for %s but it is for %s", {block_stack[$][TDATA], v}))
else
start_column = block_stack[$][2]
this_line_shift = block_stack[$][2]
block_stack = block_stack[1..$-1]
end if
elsif equal(v, "case") then
this_line_shift = switch_column + 1
start_column = switch_column + 2
elsif find(v, block_starters) then
block_stack = append(block_stack, {v, start_column})
if equal(v,"switch") then
switch_column = start_column
start_column += 1
end if
start_column += 1
elsif find(v, left_shifter) then
if compare(v,"else") or compare(last_keyword,"case") then
this_line_shift = this_line_shift-1
if this_line_shift < 0 then
errors = append(errors, sprintf("Cannot place %s", {v}))
end if
end if
end if
last_keyword = v
fallthru
case else
new_line_starts = 0
current_line = current_line & v
end switch
-- show_tokens(outfn, {t})
--printf(outfn, "%d %d %d\n", {new_line_starts, start_column, this_line_shift})
end for
return errors
end function
-- Takes Euphoria code from in_filename and writes the formatted Euphoria to out_filename
-- A set of errors is always returned. When no errors occur this is an empty sequence.
export function format_file(sequence in_filename, sequence out_filename)
sequence errors = {}
sequence block_stack = {}
sequence out = tokenize_file(in_filename,, io:TEXT_MODE)
sequence tokens
object error_code , error_line, error_column
{tokens, error_code, error_line, error_column} = out
if compare({error_code,error_line,error_column},{0,0,0}) then
errors = append(errors, sprintf("Error processing %s: %s Line %d, Column %d \n", {in_filename, error_string(error_code), error_line, error_column}))
return errors
end if
integer outfn = open(out_filename, "w")
if outfn = -1 then
errors = append(errors, sprintf("Cannot open %s for writing", {out_filename}))
return errors
end if
boolean new_line_starts = 1
integer start_column = 0
integer this_line_shift = 0
integer token_start = 1
sequence current_line = ""
integer switch_column = 0
boolean last_keyword_case = 0
boolean last_keyword_end = 0
sequence last_keyword = ""
for tokeni = 1 to length(tokens) do
sequence t = tokens[tokeni]
sequence v = t[TDATA]
switch t[1] do
case T_NEWLINE then
case T_WHITE then
if find('\n', v) then
new_line_starts = 1
if this_line_shift < 0 then
errors = append(errors, sprintf("Error processing at %d:%d\n", {in_filename} & t[TLNUM..TLPOS]))
this_line_shift = 0
end if
current_line = repeat(' ', this_line_shift * 4) & current_line
-- remove trailing white
while length(current_line) > 0 and find(current_line[$], " \r\t") do
current_line = remove(current_line, length(current_line))
end while
current_line = append(current_line, '\n')
puts(outfn, current_line)
--show_tokens(outfn, tokens[token_start..tokeni])
token_start = tokeni + 1
current_line = ""
this_line_shift = start_column
elsif new_line_starts then
-- nothing
else
current_line = current_line & v
end if
case T_KEYWORD then
new_line_starts = 0
if equal(last_keyword,"end") then
if length(block_stack) = 0 then
errors = append(errors, sprintf("Unbalanced keyword %s: Block stack empty\n", {v}) )
elsif compare(v, block_stack[$][1]) then
errors = append(errors, sprintf("End statement should be for %s but it is for %s", {block_stack[$][TDATA], v}))
else
start_column = block_stack[$][2]
this_line_shift = block_stack[$][2]
block_stack = block_stack[1..$-1]
end if
elsif equal(v, "case") then
this_line_shift = switch_column + 1
start_column = switch_column + 2
elsif find(v, block_starters) then
block_stack = append(block_stack, {v, start_column})
if equal(v,"switch") then
switch_column = start_column
start_column += 1
end if
start_column += 1
elsif find(v, left_shifter) then
if compare(v,"else") or compare(last_keyword,"case") then
this_line_shift = this_line_shift-1
if this_line_shift < 0 then
errors = append(errors, sprintf("Cannot place %s", {v}))
end if
end if
end if
last_keyword = v
fallthru
case else
new_line_starts = 0
current_line = current_line & v
end switch
-- show_tokens(outfn, {t})
--printf(outfn, "%d %d %d\n", {new_line_starts, start_column, this_line_shift})
end for
close(outfn)
return errors
end function
if length(args) = 0 then
printf(io:STDERR, "usage:\n"&
"\t\teui formateum.ex [--follow] filename1 filename2 ...\n")
end if
sequence dents = {}
for argi = 1 to length(args) do
sequence arg = args[argi]
if equal(arg,"-") then
printf(io:STDERR, "Invalid option -\n", {})
elsif begins("-", arg) then
if length(arg) > 2 and begins(arg, "--follow") then
follow_flag = 1
continue
elsif begins("--", arg ) then
printf(io:STDERR, "Invalid argument %s\n", {arg})
abort(1)
end if
for argij = 2 to length(arg) do
atom option = arg[argij]
switch option do
case 'f' then
follow_flag = 1
exit
case else
printf(io:STDERR, "Invalid option '%s'\n", {option})
abort(1)
end switch
end for
continue
end if
sequence new_filename = dirname(arg) & SLASH & filebase(arg) & "-new." & fileext(arg)
if equal(dirname(arg),"") then
new_filename = filebase(arg) & "-new." & fileext(arg)
end if
sequence errors = format_file(arg, new_filename)
for ei = 1 to length(errors) do
puts(io:STDERR, errors[ei])
end for
if length(errors) = 0 then
move_file(new_filename, arg, 1)
end if
object dstat = dir(arg)
if sequence(dstat) and length(dstat) = 1 and not find('d', dstat[1][D_ATTRIBUTES]) then
dstat[1][D_NAME] = arg
dents = append(dents, dstat[1])
end if
end for
while follow_flag do
for ti = 1 to length(dents) do
sequence ts = dents[ti]
object dstat = dir(ts[D_NAME])
if atom(dstat) then
printf(io:STDERR, "Unable to get directory information for '%s'\n", {ts[D_NAME]})
continue
end if
dstat = dstat[1]
if compare(ts[D_YEAR..D_SECOND],dstat[D_YEAR..D_SECOND]) < 0 then
-- timestamp has changed
sequence path = dirname(ts[D_NAME])
sequence new_filename
if equal(path,"") then
new_filename = filebase(ts[D_NAME]) & "-new." & fileext(ts[D_NAME])
else
new_filename = path & SLASH & filebase(ts[D_NAME]) & "-new." & fileext(ts[D_NAME])
end if
sequence errors = format_file(ts[D_NAME], new_filename)
for ei = 1 to length(errors) do
puts(io:STDERR, errors[ei])
end for
-- give the OS time to update the records
sleep(5)
dstat = dir(new_filename)
if atom(dstat) then
-- do again
continue
end if
if length(dstat) != 1 or find('d', dstat[1][D_ATTRIBUTES]) then
printf(io:STDERR, "Unusual file exception: %s\n", {new_filename})
end if
if length(errors) = 0 then
move_file(new_filename, ts[D_NAME], 1)
end if
dstat = dstat[1]
-- needs the old filename and the whole path.
dstat[D_NAME] = ts[D_NAME]
dents[ti] = dstat
end if
end for
sleep(5)
end while