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.Classy(definition) /\
44
!isa Definitions.PRAGMA(definition)
45
then
46
logger.error(definition.location, "cannot mix global definitions and namespaces in the same file");
47
fi
48
od
49
elif seen_any_other_definitions then
50
let root_namespace_name: string mut;
51
52
if IoC.CONTAINER.instance.build_flags.want_global_namespace then
53
// An empty name aggregates every file's global
54
// definitions into the single unnamed root namespace,
55
// so they are mutually visible across the project.
56
root_namespace_name = "";
57
else
58
// The per-file name keeps each file's global
59
// definitions private to that file.
60
let first_node = list |> first();
61
let path = first_node!.location.file_name;
62
63
root_namespace_name = get_namespace_name(path);
64
fi
65
66
let new_definitions = Collections.LIST[Definitions.Definition](
67
list
68
);
69
70
let root_namespace =
71
Definitions.NAMESPACE(
72
list.location,
73
Trees.Identifiers.Identifier(Source.LOCATION.internal, root_namespace_name),
74
Definitions.LIST(list.location, new_definitions),
75
true
76
);
77
78
list.clear_definitions();
79
80
list.add(root_namespace);
81
fi
82
83
node.walk(self);
84
si
85
86
is_root_directory(info: IDirectoryInfo) -> bool is
87
let root_path = _file_system.path.get_path_root(info.full_name);
88
89
return root_path =~ info.full_name;
90
si
91
92
convert_file_name_to_identifier(name: string mut) -> string is
93
if name.length == 0 then
94
return name;
95
fi
96
97
name =
98
name
99
.replace('-', '_')
100
.replace('.', '_') |>
101
filter(c => char.is_letter_or_digit(c) \/ c == '_') |> join("");
102
103
// Filesystem root names ("/", "C:\\") and any other input
104
// whose characters are all dropped by the filter reduce to
105
// empty here. The post-parse pass walks up parent
106
// directories when no ghul.json / *.ghulproj marks the
107
// project root, so a file opened from a path with no
108
// upstream marker can deliver such a name. Earlier versions
109
// crashed on the get_chars(0) below, taking down the whole
110
// build pipeline (hover, completion, …) for the file.
111
if name.length == 0 then
112
return name;
113
fi
114
115
if !char.is_letter(name.get_chars(0)) then
116
name = "`{name}";
117
fi
118
119
return name;
120
si
121
122
get_namespace_name(path: string) -> string is
123
let local_path: string mut;
124
let uri: Uri? mut = null;
125
126
if Uri.is_well_formed_uri_string(path, UriKind.ABSOLUTE) then
127
uri = Uri(path);
128
fi
129
130
if uri? /\ uri.is_file then
131
local_path = uri.local_path;
132
else
133
local_path = path;
134
fi
135
136
let result mut =
137
convert_file_name_to_identifier(
138
_file_system.path.get_file_name_without_extension(local_path) ?? ""
139
);
140
141
let folder = _file_system.path.get_directory_name(local_path) ?? "";
142
143
result = "{get_namespace_name_for_folder(folder)}{result}";
144
145
return result;
146
si
147
148
get_namespace_name_for_folder(path: string? mut) -> string is
149
let original_path = path;
150
151
if !path? \/ path =~ "" then
152
path = _file_system.directory.get_current_directory();
153
fi
154
155
let info = _file_system.directory_info.from_directory_name(path);
156
157
let result = get_namespace_name_for_folder(info);
158
159
return result;
160
si
161
162
get_namespace_name_for_folder(info: IDirectoryInfo) -> string is
163
let result mut = "{convert_file_name_to_identifier(info.name)}__";
164
165
if
166
_file_system.file.exists(_file_system.path.combine(info.full_name, "ghul.json")) \/
167
_file_system.directory.get_files(info.full_name, "*.ghulproj").count > 0
168
then
169
return result;
170
fi
171
172
if info.parent? /\ !is_root_directory(info) then
173
result = "{get_namespace_name_for_folder(info.parent!)}{result}";
174
fi
175
176
return result;
177
si
178
179
pre(`namespace: Definitions.NAMESPACE) -> bool is
180
_depth = _depth + 1;
181
return false;
182
si
183
184
visit(`namespace: Definitions.NAMESPACE) is
185
let names = `namespace.name.qualifier_names;
186
187
if _depth == 1 then
188
`namespace.body.push(
189
Definitions.USE(
190
Source.LOCATION.internal,
191
null,
192
Identifiers.Identifier(
193
LOCATION.internal,
194
"Ghul"
195
)
196
)
197
);
198
fi
199
200
if names.count > 0 then
201
`namespace.name = Identifiers.Identifier(`namespace.name.location, `namespace.name.name);
202
let body = `namespace.body;
203
let current = `namespace;
204
205
for n in names do
206
current.body =
207
Definitions.LIST(
208
current.location,
209
Collections.LIST[Definitions.Definition](
210
[Definitions.NAMESPACE(current.location, current.name, current.body, current.is_compiler_generated)]: Definitions.Definition
211
)
212
);
213
current.name = Identifiers.Identifier(`namespace.name.location, n);
214
od
215
fi
216
217
_depth = _depth - 1;
218
si
219
si
220
si