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