Appearance
| 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 |