-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathUsedFile.zu
384 lines (336 loc) · 12.3 KB
/
UsedFile.zu
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
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
#
# The Zimbu compiler written in Zimbu
#
# UsedFile class: values for an imported or main file.
#
# Copyright 2009 Bram Moolenaar All Rights Reserved.
# Licensed under the Apache License, Version 2.0. See the LICENSE file or
# obtain a copy at: http://www.apache.org/licenses/LICENSE-2.0
#
IMPORT.PROTO "zui.proto"
IMPORT "Builtin.zu"
IMPORT "Config.zu"
IMPORT "Declaration.zu"
IMPORT "FileScope.zu"
IMPORT "Generate.zu"
IMPORT "Resolve.zu"
IMPORT "SContext.zu"
IMPORT "TopScope.zu"
IMPORT "Util.zu"
IMPORT "ZimbuFile.zu"
IMPORT "ZuiFile.zu"
IMPORT "ZuiDeclarationExt.zu"
IMPORT "ZuiImportExt.zu"
CLASS UsedFile @items=public # TODO: restrict visibility
bool $isTopFile # TRUE for main file and top ZWT file, code is to be
# generated for it
ZimbuFile $zimbuFile # Info about the imported file not related to where
# it's imported from.
string $asName # name for IMPORT "Foo.zu" AS OtherFoo
bool $didError # given error message for wrong $asName
BITS Flags
bool $isMainFile
bool $isTopFile
}
NEW(string fileName, Flags flags)
$zimbuFile = NEW(fileName, flags.isMainFile)
$isTopFile = flags.isTopFile
$zimbuFile.usedAsZimbu = TRUE
}
NEW(ZimbuFile zimbuFile, bool isTopFile)
$zimbuFile = zimbuFile
$isTopFile = isTopFile
}
FUNC $parse(TopScope topScope, string indent) status
status ret = $zimbuFile.parse(indent, THIS)
# Check for built-in modules that are used.
# Import nodes are prepended for these at the top file scope.
IF ret == OK
Builtin.checkBuiltin(THIS, topScope)
}
RETURN ret
}
PROC $parseImports(string indent, SContext ctx)
# Set the indent on the scope again, it changes when finding builtin
# modules and when skipping files.
$scope().importIndent = indent
# Go through the IMPORT statements of this scope.
ZuiFile zf = $zimbuFile.zuiFile
IF zf.contents.hasImport()
FOR import IN zf.contents.getImportList()
$parseImport(import, ctx)
}
}
}
PROC $parseImport(Zui.Import import, SContext ctx)
TopScope topScope = ctx.topScope
string pluginName = import.hasPlugin() ? import.getPlugin() : NIL
IF pluginName == "CHEADER"
# Handle IMPORT.CHEADER <somefile.h>
# TODO: check if file exists.
string key = import.getAngleQuotes()
? "<" .. import.getFileName() .. ">"
: "\"../" .. Generate.relativeName(
THIS, import.getFileName()) .. "\""
IF topScope.cheaders.find(key) < 0
topScope.cheaders.add(key)
}
RETURN
}
VAR importExt = ZuiImportExt.get(import)
IF importExt.usedFile == NIL
# Didn't handle this import before, create a UsedFile for it now.
# For IMPORT.ZWT it creates a new TopScope.
topScope = $handleImport(import, ctx)
IF importExt.usedFile == NIL
RETURN
}
IF pluginName == "ZWT"
# Remember the TopScope of the ZWT file tree.
importExt.topScope = topScope
}
}
ZimbuFile importZimbuFile = importExt.usedFile.zimbuFile
IF importZimbuFile == NIL
RETURN # error already reported
}
FileScope scope = ctx.scope
string name = importZimbuFile.filename
IF importZimbuFile.startedPass == -1
# Didn't parse the imported file yet, do it now.
# This will also check for using builtin modules.
importExt.usedFile.parse(topScope, scope.importIndent)
IF importZimbuFile.fileScope == NIL
ctx.error("Cannot open file for reading: " .. name, import.getPos())
ELSE
importZimbuFile.dirName = Util.dirName(name)
}
}
# Recursively handle IMPORT in the parsed file.
# We need to go into the imported file if we haven't done it yet for the
# current top file (Main() or .zwt file).
# Rationale: "foo.zu" may be included below the file containing Main() and
# also below a ZWT file. It must then be generated and included in both.
# This must be known at the toplevel, also for builtin modules.
IF topScope.isNewImport(importZimbuFile)
&& (LOG.errorCount == 0 || Generate.continueAfterError)
importExt.usedFile.parseImports(scope.importIndent,
NEW(ctx, importExt.usedFile.scope(), ctx.outs))
Zui.Statement stmt = importZimbuFile.fileScope.getFirstStatement()
IF stmt != NIL
IF !importZimbuFile.isProto
# Check that the toplevel item name appears in the file name,
# ignoring case.
IF stmt.getType() == Zui.StatementType.eCLASS_DECL
|| stmt.getType() == Zui.StatementType.eMODULE_DECL
|| stmt.getType() == Zui.StatementType.eENUM_DECL
|| stmt.getType() == Zui.StatementType.eBITS_DECL
string topName = stmt.getDeclaration().getName()
string root
int slash = name.findLast('/')
IF slash >= 0
root = name.slice(slash + 1) # use name after last /
ELSE
root = name
}
int dot = root.findLast('.')
IF dot > 0
root = root.slice(0, dot - 1) # remove .zu or .proto
}
int i = root.find(topName)
IF i < 0
ctx.error("Name must match the file name: " .. topName, stmt)
ELSEIF i > 0 && root[i - 1] != '_'
|| (i + topName.Size() < root.Size()
&& root[i + topName.Size()] != '_')
ctx.error("Name matches only part of the file name: "
.. topName, stmt)
}
}
}
}
}
}
# Handle an IMPORT for |import| and add the ZimbuFile to |import|.usedFile.
# This is only called once for every IMPORT statement that actually imports
# a .zu file, not for IMPORT.CHEADER.
FUNC $handleImport(Zui.Import import, SContext ctx) TopScope
FileScope scope = $scope()
string name = import.getFileName()
IF name.startsWith("$PLUGIN/")
name = Config.pluginPath .. name.slice(7)
ELSE
name = Generate.relativeName(THIS, name)
}
# TODO: more flexible way to handle plugins.
bool isProto
string pluginName
IF import.hasPlugin()
pluginName = import.getPlugin()
IF pluginName == "PROTO"
name = $plugin(import, name, protoPluginConfig, ctx)
IF name == NIL
RETURN ctx.topScope
}
ELSEIF pluginName == "ZUT"
name = $plugin(import, name, zutPluginConfig, ctx)
IF name == NIL
RETURN ctx.topScope
}
ELSEIF pluginName != "ZWT"
# IMPORT.ZWT is handled elsewhere
ctx.error("Unsupported import plugin: " .. pluginName, import.getPos())
RETURN ctx.topScope
}
isProto = TRUE
}
IF name.slice(-3) != ".zu"
ctx.error("Import name must end in '.zu': " .. name, import.getPos())
RETURN ctx.topScope
}
ZimbuFile zimbuFile = ZimbuFile.find(%importedFiles, name)
IF zimbuFile == NIL
# Didn't encounter this file before, create a new ZimbuFile.
zimbuFile = NEW(name, FALSE)
%importedFiles.add(zimbuFile)
}
zimbuFile.isProto = isProto
bool topFile
TopScope topScope = ctx.topScope
IF pluginName == "ZWT"
# This file needs to be produced as Javascript.
zimbuFile.usedAsZwt = TRUE
zimbuFile.topZwtFile = TRUE
# IMPORT.ZWT means generating a new JS file, thus topFile is TRUE.
topFile = TRUE
topScope = NEW()
# Use the same pass count as in the non-ZWT code, since .zu files are
# getting a pass only once, no matter though if the route through which
# they get imported is below IMPORT.ZWT or not.
topScope.pass = ctx.topScope.pass
}
# For IMPORT "Name.zu" @javascript
# add the file to the list of javascript files.
IF import.getJavascript()
zimbuFile.usedAsZwt = TRUE
topScope.addJavascript(zimbuFile)
}
VAR importExt = ZuiImportExt.get(import)
importExt.usedFile = NEW(zimbuFile, topFile)
importExt.usedFile.asName = import.hasAsName() ? import.getAsName() : NIL
RETURN topScope
}
# handle IMPORT.PROTO "file.proto", |name| is "file.proto".
# Return the name of the generated .zu file.
# Return NIL in case of an error
FUNC $plugin(Zui.Import import, string name, PluginConfig config,
SContext ctx) string
IF !name.endsWith("." .. config.extension)
ctx.error("Proto import name must end in '.\(config.extension)': \(name)",
import.getPos())
RETURN NIL
}
# "file.proto" to "ZUDIR/file.zu"
# "dir/file.proto" to "dir/ZUDIR/file.zu"
string outFileName = name.slice(0, -(config.extension.Size() + 2)) .. ".zu"
int slash = outFileName.findLast('/')
string outDirName
IF slash < 0
outDirName = Config.zudirName
ELSE
outDirName = outFileName.slice(0, slash) .. Config.zudirName
outFileName = outFileName.slice(slash + 1)
}
outFileName = outDirName .. "/" .. outFileName
IF !%protoDone.has(outFileName)
IF IO.fileInfo(outDirName).status == FAIL
IF IO.mkdir(outDirName) == FAIL
ctx.error("Cannot create directory " .. outDirName,
import.getPos())
}
}
# Find the plugin program in the same directory as zimbu itself.
string dir = ""
slash = ARG.exeName.findLast('/')
IF slash > 0
dir = ARG.exeName.slice(0, slash)
}
FileScope scope = $scope()
LOG.info("\(scope.importIndent) Converting \(name)...")
string options = import.hasOptions() ? import.getOptions() : ""
int rv = SYS.shell("\(dir)\(config.program) \(options) "
.. "\"\(name)\" \"\(outFileName)\"")
IF rv != 0 || IO.fileInfo(outFileName).size == 0
ctx.error("Conversion failed for " .. name, import.getPos())
RETURN NIL
}
LOG.info("\(scope.importIndent) Done.")
%protoDone.add(outFileName)
}
RETURN outFileName
}
# Return TRUE if there is a Main() method in this file.
FUNC $hasMain() bool
RETURN $findMainDecl() != NIL
}
FUNC $findMainDecl() Zui.Statement
IF $zimbuFile.fileScope == NIL
RETURN NIL
}
FOR stmt IN $zimbuFile.fileScope.statements ?: []
IF stmt.getType() == Zui.StatementType.eMETHOD_DECL
&& stmt.getDeclaration().getName() == "Main"
# Error for wrong type or arguments given elsewhere.
RETURN stmt
}
}
RETURN NIL
}
PROC $markMainUsed(Resolve gen, TopScope topScope)
# Mark used items. Start with the Main() method and descend recursively.
Zui.Statement mainStmt = $findMainDecl()
IF mainStmt == NIL
LOG.internal("Main() not found")
ELSE
Zui.Declaration zuiDecl = mainStmt.getDeclaration()
# "declExt.decl" was set in generateMain().
VAR declExt = ZuiDeclarationExt.get(zuiDecl)
IF declExt.decl == NIL
LOG.internal("Main() decl is NIL")
ELSE
# We do not want to write a declaration for abstract classes.
Declaration.abstractClass.clearUsed()
# We do not write an inherited method in the parent class.
Declaration.inheritMethod.clearUsed()
# Let the language add dependencies between symbols and mark items as
# used that are always used.
gen.addDependencies()
# Mark Main() as used, will recursively mark all contained items and
# their dependencies as used.
gen.setDeclUsed(declExt.decl)
}
}
}
FUNC $hasUsedItem(Resolve gen, string indent) bool
RETURN $zimbuFile.hasUsedItem(gen, indent)
}
FUNC $scope() FileScope
RETURN $zimbuFile.fileScope
}
SHARED
set<string> %protoDone = NEW() # Proto files already converted.
list<ZimbuFile> %importedFiles = NEW()
CLASS PluginConfig
string $extension
string $program
}
PluginConfig protoPluginConfig = {
extension: "proto",
program: "pluginproto",
}
PluginConfig zutPluginConfig = {
extension: "zut",
program: "pluginzut",
}
}
}