Skip to content
← Back

src/syntax/process/expand_namespaces.ghul

1
namespace Syntax.Process is
2
use System.Uri
3
use System.UriKind
4
5
use System.IO.Abstractions
6
use Source
7
use Trees
8
9
use Ghul.Pipes
10
11
class EXPAND_NAMESPACES: Visitor is
12
_file_system: IFileSystem
13
_depth: int
14
15
init(file_system: IFileSystem) is
16
super.init()
17
18
_file_system = file_system
19
si
20
21
apply(node: Node) is
22
assert isa Definitions.LIST(node) else "expected the root syntax tree node of file to be a definition list"
23
24
let list = cast Definitions.LIST(node)
25
26
let seen_any_namespaces mut = false
27
let seen_any_other_definitions mut = false
28
29
for definition in list do
30
if isa Definitions.NAMESPACE(definition) then
31
seen_any_namespaces = true
32
elif !isa Definitions.PRAGMA(definition) then
33
seen_any_other_definitions = true
34
fi
35
od
36
37
if seen_any_namespaces /\ seen_any_other_definitions then
38
let logger = IoC.CONTAINER.instance.logger
39
40
for definition in list do
41
if
42
!isa Definitions.NAMESPACE(definition) /\
43
!isa Definitions.PRAGMA(definition)
44
then
45
logger.error(definition.location, "cannot mix global definitions and namespaces in the same file")
46
fi
47
od
48
elif seen_any_other_definitions then
49
let root_namespace_name: string mut
50
51
if IoC.CONTAINER.instance.build_flags.want_global_namespace then
52
// An empty name aggregates every file's global
53
// definitions into the single unnamed root namespace,
54
// so they are mutually visible across the project.
55
root_namespace_name = ""
56
else
57
// The per-file name keeps each file's global
58
// definitions private to that file.
59
let first_node = list |> first()
60
let path = first_node!.location.file_name
61
62
root_namespace_name =
63
IoC.CONTAINER.instance.build_flags.submission_name ??
64
get_namespace_name(path)
65
fi
66
67
let new_definitions = Collections.LIST[Definitions.Definition](
68
list
69
)
70
71
if IoC.CONTAINER.instance.build_flags.want_implicit_default_use then
72
new_definitions.insert(_after_pragmas(new_definitions), _implicit_default_use(list.location))
73
fi
74
75
let root_namespace =
76
Definitions.NAMESPACE(
77
list.location,
78
Trees.Identifiers.Identifier(Source.LOCATION.internal, root_namespace_name),
79
Definitions.LIST(list.location, new_definitions),
80
true
81
)
82
83
list.clear_definitions()
84
85
list.add(root_namespace)
86
fi
87
88
node.walk(self)
89
si
90
91
// Where an implicit `use default` goes: after any pragmas, so a
92
// file-level pragma still comes first.
93
_after_pragmas(definitions: Collections.List[Definitions.Definition]) -> int is
94
let index mut = 0
95
96
while index < definitions.count /\ isa Definitions.PRAGMA(definitions[index]) do
97
index = index + 1
98
od
99
100
return index
101
si
102
103
_implicit_default_use(location: LOCATION) -> Definitions.USE is
104
let result = Definitions.USE(location, null, null)
105
106
result.is_default = true
107
108
return result
109
si
110
111
is_root_directory(info: IDirectoryInfo) -> bool is
112
let root_path = _file_system.path.get_path_root(info.full_name)
113
114
return root_path =~ info.full_name
115
si
116
117
convert_file_name_to_identifier(name: string mut) -> string is
118
if name.length == 0 then
119
return name
120
fi
121
122
name =
123
name
124
.replace('-', '_')
125
.replace('.', '_') |>
126
filter(c => char.is_letter_or_digit(c) \/ c == '_') |> join("")
127
128
// Filesystem root names ("/", "C:\\") and any other input
129
// whose characters are all dropped by the filter reduce to
130
// empty here. The post-parse pass walks up parent
131
// directories when no ghul.json / *.ghulproj marks the
132
// project root, so a file opened from a path with no
133
// upstream marker can deliver such a name. Earlier versions
134
// crashed on the index below, taking down the whole
135
// build pipeline (hover, completion, …) for the file.
136
if name.length == 0 then
137
return name
138
fi
139
140
if !char.is_letter(name[0]) then
141
name = "`{name}"
142
fi
143
144
return name
145
si
146
147
get_namespace_name(path: string) -> string is
148
let local_path: string mut
149
let uri: Uri? mut = null
150
151
if Uri.is_well_formed_uri_string(path, UriKind.ABSOLUTE) then
152
uri = Uri(path)
153
fi
154
155
if uri? /\ uri.is_file then
156
local_path = uri.local_path
157
else
158
local_path = path
159
fi
160
161
let result mut =
162
convert_file_name_to_identifier(
163
_file_system.path.get_file_name_without_extension(local_path) ?? ""
164
)
165
166
let folder = _file_system.path.get_directory_name(local_path) ?? ""
167
168
result = "{get_namespace_name_for_folder(folder)}{result}"
169
170
return result
171
si
172
173
get_namespace_name_for_folder(path: string? mut) -> string is
174
let original_path = path
175
176
if !path? \/ path =~ "" then
177
path = _file_system.directory.get_current_directory()
178
fi
179
180
let info = _file_system.directory_info.`new(path)
181
182
let result = get_namespace_name_for_folder(info)
183
184
return result
185
si
186
187
get_namespace_name_for_folder(info: IDirectoryInfo) -> string is
188
let result mut = "{convert_file_name_to_identifier(info.name)}__"
189
190
if
191
_file_system.file.exists(_file_system.path.combine(info.full_name, "ghul.json")) \/
192
_file_system.directory.get_files(info.full_name, "*.ghulproj").count > 0
193
then
194
return result
195
fi
196
197
if info.parent? /\ !is_root_directory(info) then
198
result = "{get_namespace_name_for_folder(info.parent!)}{result}"
199
fi
200
201
return result
202
si
203
204
pre(`namespace: Definitions.NAMESPACE) -> bool is
205
_depth = _depth + 1
206
return false
207
si
208
209
visit(`namespace: Definitions.NAMESPACE) is
210
let names = `namespace.name.qualifier_names
211
212
if _depth == 1 then
213
// `Ghul.Intrinsics` is the ambient set: the names the
214
// language lets every namespace block reference without
215
// a `use` - the magic types and the intrinsic operators.
216
// Anything else declared in `Ghul`, by the runtime or by
217
// any consumed assembly, is imported like any other
218
// namespace's symbols.
219
`namespace.body.push(
220
Definitions.USE(
221
Source.LOCATION.internal,
222
null,
223
Trees.Identifiers.QUALIFIED(
224
Source.LOCATION.internal,
225
Trees.Identifiers.Identifier(Source.LOCATION.internal, "Ghul"),
226
"Intrinsics",
227
Source.LOCATION.internal,
228
Source.LOCATION.internal
229
)
230
)
231
)
232
fi
233
234
if names.count > 0 then
235
`namespace.name = Identifiers.Identifier(`namespace.name.location, `namespace.name.name)
236
let body = `namespace.body
237
let current = `namespace
238
239
for n in names do
240
current.body =
241
Definitions.LIST(
242
current.location,
243
Collections.LIST[Definitions.Definition](
244
[Definitions.NAMESPACE(current.location, current.name, current.body, current.is_compiler_generated)]: Definitions.Definition
245
)
246
)
247
current.name = Identifiers.Identifier(`namespace.name.location, n)
248
od
249
fi
250
251
_depth = _depth - 1
252
si
253
si
254
si