├── .gitattributes ├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.zig ├── src ├── StoreToScip.zig ├── analysis │ ├── Analyzer.zig │ ├── DocumentStore.zig │ ├── offsets.zig │ └── utils.zig ├── main.zig ├── protobruh.zig └── scip.zig └── test ├── king.zig ├── loris.zig └── luuk.zig /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.zig text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | paths: 6 | - "**.zig" 7 | pull_request: 8 | paths: 9 | - "**.zig" 10 | schedule: 11 | - cron: "0 0 * * *" 12 | workflow_dispatch: 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, macos-latest, windows-latest] 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v3 22 | with: 23 | fetch-depth: 0 24 | submodules: true 25 | - uses: goto-bus-stop/setup-zig@v2 26 | with: 27 | version: master 28 | 29 | - run: zig version 30 | - run: zig env 31 | 32 | - name: Build 33 | run: zig build 34 | 35 | - name: Run Tests 36 | run: zig build test 37 | 38 | - name: Build artifacts 39 | if: ${{ matrix.os == 'ubuntu-latest' }} 40 | run: | 41 | declare -a targets=("x86_64-windows" "x86_64-linux" "x86_64-macos" "x86-windows" "x86-linux" "aarch64-linux" "aarch64-macos") 42 | mkdir -p "artifacts/" 43 | 44 | for target in "${targets[@]}"; do 45 | mkdir -p artifacts/$target 46 | echo "Building target ${target}..." 47 | if [ "${GITHUB_REF##*/}" == "master" ]; then 48 | echo "Building safe" 49 | zig build -Dtarget=${target} -Drelease-safe --prefix artifacts/${target}/ 50 | else 51 | echo "Building debug as action is not running on master" 52 | zig build -Dtarget=${target} --prefix artifacts/${target}/ 53 | fi 54 | sed -e '1,5d' < README.md > artifacts/${target}/README.md 55 | cp LICENSE artifacts/${target}/ 56 | done 57 | 58 | - name: Upload x86_64-windows artifact 59 | if: ${{ matrix.os == 'ubuntu-latest' }} 60 | uses: actions/upload-artifact@v3 61 | with: 62 | name: scip-zig-x86_64-windows 63 | path: artifacts/x86_64-windows/ 64 | 65 | - name: Upload x86_64-linux artifact 66 | if: ${{ matrix.os == 'ubuntu-latest' }} 67 | uses: actions/upload-artifact@v3 68 | with: 69 | name: scip-zig-x86_64-linux 70 | path: artifacts/x86_64-linux/ 71 | 72 | - name: Upload x86_64-macos artifact 73 | if: ${{ matrix.os == 'ubuntu-latest' }} 74 | uses: actions/upload-artifact@v3 75 | with: 76 | name: scip-zig-x86_64-macos 77 | path: artifacts/x86_64-macos/ 78 | 79 | - name: Upload x86-windows artifact 80 | if: ${{ matrix.os == 'ubuntu-latest' }} 81 | uses: actions/upload-artifact@v3 82 | with: 83 | name: scip-zig-x86-windows 84 | path: artifacts/x86-windows/ 85 | 86 | - name: Upload x86-linux artifact 87 | if: ${{ matrix.os == 'ubuntu-latest' }} 88 | uses: actions/upload-artifact@v3 89 | with: 90 | name: scip-zig-x86-linux 91 | path: artifacts/x86-linux/ 92 | 93 | - name: Upload aarch64-linux artifact 94 | if: ${{ matrix.os == 'ubuntu-latest' }} 95 | uses: actions/upload-artifact@v3 96 | with: 97 | name: scip-zig-aarch64-linux 98 | path: artifacts/aarch64-linux/ 99 | 100 | - name: Upload aarch64-macos artifact 101 | if: ${{ matrix.os == 'ubuntu-latest' }} 102 | uses: actions/upload-artifact@v3 103 | with: 104 | name: scip-zig-aarch64-macos 105 | path: artifacts/aarch64-macos/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /zig-cache 2 | /zig-out 3 | /scip-snapshot 4 | *.scip 5 | *.lsif 6 | /tok -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Auguste Rame 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scip-zig 2 | 3 | [SCIP](https://github.com/sourcegraph/scip) indexer for [Zig](https://ziglang.org). Experimental. 4 | 5 | ## Supported features 6 | 7 | Our benchmark for `scip-zig` is its ability to index `std`; unfortunately importing external packages via `addPackage` isn't supported *yet*. 8 | 9 | - [x] Globals 10 | - [ ] Properly creating multiple indices for different packages 11 | - [x] Imports 12 | - [x] Namespaced declarations (structs, enums, unions) 13 | - [x] Functions 14 | - [ ] Arguments 15 | - [x] Return values 16 | - [x] Bodies 17 | - [x] Locals 18 | - [x] With explicit typing 19 | - [ ] With inferred typing 20 | - [ ] Easy integration into `build.zig` / CI 21 | - [ ] `usingnamespace` 22 | - [ ] `comptime` 23 | 24 | ## Installing 25 | 26 | To install `scip-zig`, simply `git clone` this repository and run `zig build`; you'll find the indexer in `zig-out/bin`! 27 | 28 | ## Usage 29 | 30 | ```bash 31 | # To index std; currently uses about a 400mbs of memory 32 | # Windows: should finish in under 10 seconds in release-fast, under a minute in debug 33 | # WSL: should finish in under 3 seconds in release-fast 34 | scip-zig --root-path /path/to/zig --pkg std /path/to/zig/lib/std/std.zig --root-pkg std 35 | src code-intel upload -github-token=$(cat tok) -file=index.scip 36 | ``` 37 | 38 | For example, let's index this very repo: 39 | 40 | ```bash 41 | zig-out/bin/scip-zig --root-path $(pwd) --pkg scip-zig $(pwd)/src/main.zig --root-pkg scip-zig 42 | scip convert --from index.scip 43 | src code-intel upload -github-token=$(cat tok) -file=index.scip 44 | ``` 45 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.build.Builder) void { 4 | // Standard target options allows the person running `zig build` to choose 5 | // what target to build for. Here we do not override the defaults, which 6 | // means any target is allowed, and the default is native. Other options 7 | // for restricting supported target set are available. 8 | const target = b.standardTargetOptions(.{}); 9 | 10 | // Standard release options allow the person running `zig build` to select 11 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. 12 | const mode = b.standardReleaseOptions(); 13 | 14 | const exe = b.addExecutable("scip-zig", "src/main.zig"); 15 | exe.setTarget(target); 16 | exe.setBuildMode(mode); 17 | exe.install(); 18 | 19 | const run_cmd = exe.run(); 20 | run_cmd.step.dependOn(b.getInstallStep()); 21 | if (b.args) |args| { 22 | run_cmd.addArgs(args); 23 | } 24 | 25 | const run_step = b.step("run", "Run the app"); 26 | run_step.dependOn(&run_cmd.step); 27 | 28 | const exe_tests = b.addTest("src/main.zig"); 29 | exe_tests.setTarget(target); 30 | exe_tests.setBuildMode(mode); 31 | 32 | const test_step = b.step("test", "Run unit tests"); 33 | test_step.dependOn(&exe_tests.step); 34 | } 35 | -------------------------------------------------------------------------------- /src/StoreToScip.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const scip = @import("scip.zig"); 3 | const builtin = @import("builtin"); 4 | const DocumentStore = @import("analysis/DocumentStore.zig"); 5 | 6 | const analysis_utils = @import("analysis/utils.zig"); 7 | 8 | // TODO: Support external type info 9 | pub fn storeToScip(allocator: std.mem.Allocator, store: *DocumentStore, pkg: []const u8) !std.ArrayListUnmanaged(scip.Document) { 10 | var documents = std.ArrayListUnmanaged(scip.Document){}; 11 | 12 | var docit = store.packages.get(pkg).?.handles.iterator(); 13 | while (docit.next()) |entry| { 14 | var handle = entry.value_ptr.*; 15 | var document = try documents.addOne(allocator); 16 | 17 | document.* = .{ 18 | .language = "zig", 19 | // TODO: Investigate a good solution 20 | .relative_path = try std.mem.replaceOwned(u8, allocator, entry.key_ptr.*, "\\", "/"), 21 | .occurrences = handle.analyzer.occurrences, 22 | .symbols = handle.analyzer.symbols, 23 | }; 24 | } 25 | 26 | return documents; 27 | } 28 | -------------------------------------------------------------------------------- /src/analysis/Analyzer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const zig = std.zig; 3 | const Ast = zig.Ast; 4 | const scip = @import("../scip.zig"); 5 | const utils = @import("utils.zig"); 6 | const offsets = @import("offsets.zig"); 7 | const DocumentStore = @import("DocumentStore.zig"); 8 | 9 | const logger = std.log.scoped(.analyzer); 10 | 11 | const Analyzer = @This(); 12 | 13 | allocator: std.mem.Allocator, 14 | handle: *DocumentStore.Handle, 15 | scopes: std.ArrayListUnmanaged(Scope) = .{}, 16 | 17 | /// Occurrences recorded at occurrence site 18 | recorded_occurrences: std.AutoHashMapUnmanaged(Ast.TokenIndex, void) = .{}, 19 | 20 | symbols: std.ArrayListUnmanaged(scip.SymbolInformation) = .{}, 21 | occurrences: std.ArrayListUnmanaged(scip.Occurrence) = .{}, 22 | 23 | local_counter: usize = 0, 24 | 25 | post_resolves: std.ArrayListUnmanaged(PostResolve) = .{}, 26 | const PostResolve = struct { scope_idx: usize, node_idx: Ast.Node.Index }; 27 | 28 | // TODO: Make scope map to avoid scope duplication, decrease lookup times 29 | 30 | pub fn init(analyzer: *Analyzer) !void { 31 | logger.info("Initializing file {s}", .{analyzer.handle.path}); 32 | try analyzer.newContainerScope(null, 0, "root"); 33 | } 34 | 35 | pub const SourceRange = std.zig.Token.Loc; 36 | 37 | pub const Scope = struct { 38 | pub const Data = union(enum) { 39 | container: struct { 40 | descriptor: []const u8, 41 | node_idx: Ast.Node.Index, 42 | fields: std.StringHashMapUnmanaged(Field) = .{}, 43 | }, // .tag is ContainerDecl or Root or ErrorSetDecl 44 | function: struct { 45 | descriptor: []const u8, 46 | node_idx: Ast.Node.Index, 47 | }, // .tag is FnProto 48 | block: Ast.Node.Index, // .tag is Block 49 | // TODO: Is this really the most efficient way? 50 | import: []const u8, 51 | other, 52 | }; 53 | 54 | node_idx: zig.Ast.Node.Index, 55 | parent_scope_idx: ?usize, 56 | range: SourceRange, 57 | decls: std.StringHashMapUnmanaged(Declaration) = .{}, 58 | // tests: std.ArrayListUnmanaged(Ast.Node.Index) = .{}, 59 | children: std.ArrayListUnmanaged(Ast.Node.Index) = .{}, 60 | data: Data, 61 | 62 | pub fn deinit(self: *Scope, allocator: std.mem.Allocator) void { 63 | self.decls.deinit(allocator); 64 | // self.tests.deinit(allocator); 65 | self.children.deinit(allocator); 66 | } 67 | 68 | pub fn toNodeIndex(self: Scope) ?Ast.Node.Index { 69 | return switch (self.data) { 70 | .container, .function, .block => |idx| idx, 71 | else => null, 72 | }; 73 | } 74 | }; 75 | 76 | // pub const ResolveAndMarkResult = struct { 77 | // analyzer: *Analyzer, 78 | // scope_idx: usize, 79 | // declaration: ?Declaration = null, 80 | // }; 81 | 82 | pub const TrueScopeIndexResult = struct { 83 | /// Analyzer where scope is located 84 | analyzer: *Analyzer, 85 | /// Scope 86 | scope_idx: usize, 87 | }; 88 | 89 | pub fn resolveTrueScopeIndex( 90 | analyzer: *Analyzer, 91 | scope_idx: usize, 92 | ) anyerror!?TrueScopeIndexResult { 93 | const scope = analyzer.scopes.items[scope_idx]; 94 | return switch (scope.data) { 95 | .import => |i| TrueScopeIndexResult{ 96 | // NOTE: This really seems dangerous... but it works so like can't complain (yet) 97 | .analyzer = &((try analyzer.handle.document_store.resolveImportHandle(analyzer.handle, i)) orelse return null).analyzer, 98 | .scope_idx = 0, 99 | }, 100 | else => TrueScopeIndexResult{ .analyzer = analyzer, .scope_idx = scope_idx }, 101 | }; 102 | } 103 | 104 | pub const DeclarationWithAnalyzer = struct { 105 | analyzer: *Analyzer, 106 | declaration: ?Declaration = null, 107 | scope_idx: usize, 108 | }; 109 | 110 | pub fn getDeclFromScopeByName( 111 | analyzer: *Analyzer, 112 | scope_idx: usize, 113 | name: []const u8, 114 | ) anyerror!DeclarationWithAnalyzer { 115 | var ts = (try analyzer.resolveTrueScopeIndex(scope_idx)) orelse return DeclarationWithAnalyzer{ .analyzer = analyzer, .scope_idx = scope_idx }; 116 | if (ts.analyzer.scopes.items.len == 0) return DeclarationWithAnalyzer{ .analyzer = ts.analyzer, .scope_idx = ts.scope_idx }; 117 | return DeclarationWithAnalyzer{ 118 | .analyzer = ts.analyzer, 119 | .declaration = ts.analyzer.scopes.items[ts.scope_idx].decls.get(name), 120 | .scope_idx = ts.scope_idx, 121 | }; 122 | } 123 | 124 | pub fn formatSubSymbol(analyzer: *Analyzer, symbol: []const u8) []const u8 { 125 | _ = analyzer; 126 | return if (std.mem.startsWith(u8, symbol, "@\"")) symbol[2 .. symbol.len - 1] else symbol; 127 | } 128 | 129 | pub fn resolveAndMarkDeclarationIdentifier( 130 | analyzer: *Analyzer, 131 | foreign_analyzer: *Analyzer, 132 | scope_idx: usize, 133 | token_idx: Ast.TokenIndex, 134 | ) anyerror!DeclarationWithAnalyzer { 135 | const tree = analyzer.handle.tree; 136 | // const scope = analyzer.scopes.items[scope_idx]; 137 | 138 | var dwa = try foreign_analyzer.getDeclFromScopeByName(scope_idx, tree.tokenSlice(token_idx)); 139 | if (dwa.declaration == null) 140 | dwa = 141 | if (dwa.scope_idx > foreign_analyzer.scopes.items.len) 142 | return DeclarationWithAnalyzer{ .analyzer = foreign_analyzer, .scope_idx = scope_idx } 143 | else r: { 144 | const maybe_rtsi = try foreign_analyzer.resolveTrueScopeIndex(scope_idx); 145 | if (maybe_rtsi) |rtsi| { 146 | if (rtsi.analyzer.scopes.items[rtsi.scope_idx].parent_scope_idx) |psi| { 147 | break :r try analyzer.resolveAndMarkDeclarationIdentifier(rtsi.analyzer, psi, token_idx); 148 | } 149 | } 150 | 151 | return DeclarationWithAnalyzer{ .analyzer = foreign_analyzer, .scope_idx = if (maybe_rtsi) |m| m.scope_idx else 0 }; 152 | }; 153 | 154 | if (dwa.declaration) |decl| { 155 | if ((try analyzer.recorded_occurrences.fetchPut(analyzer.allocator, token_idx, {})) == null) { 156 | try analyzer.occurrences.append(analyzer.allocator, .{ 157 | .range = analyzer.rangeArray(token_idx), 158 | .symbol = decl.symbol, 159 | .symbol_roles = 0, 160 | .override_documentation = .{}, 161 | .syntax_kind = .identifier, 162 | .diagnostics = .{}, 163 | }); 164 | } 165 | } 166 | 167 | return dwa; 168 | } 169 | 170 | pub fn resolveAndMarkDeclarationComplex( 171 | analyzer: *Analyzer, 172 | foreign_analyzer: *Analyzer, 173 | scope_idx: usize, 174 | node_idx: Ast.Node.Index, 175 | ) anyerror!DeclarationWithAnalyzer { 176 | const tree = analyzer.handle.tree; 177 | const tags = tree.nodes.items(.tag); 178 | const data = tree.nodes.items(.data); 179 | 180 | if (node_idx >= tags.len) std.log.err("BRUH {d}", .{node_idx}); 181 | return switch (tags[node_idx]) { 182 | .identifier => analyzer.resolveAndMarkDeclarationIdentifier(foreign_analyzer, scope_idx, tree.nodes.items(.main_token)[node_idx]), 183 | .field_access => { 184 | const curr_name_idx = data[node_idx].rhs; 185 | const prev_node_idx = data[node_idx].lhs; 186 | 187 | // const scope_decl = () orelse return .{ .analyzer = analyzer }; 188 | var result = try analyzer.resolveAndMarkDeclarationComplex(foreign_analyzer, scope_idx, prev_node_idx); 189 | if (result.declaration) |decl| 190 | switch (result.analyzer.handle.tree.nodes.items(.tag)[decl.node_idx]) { 191 | .global_var_decl, 192 | .local_var_decl, 193 | .aligned_var_decl, 194 | .simple_var_decl, 195 | => { 196 | const var_decl = utils.varDecl(result.analyzer.handle.tree, decl.node_idx).?; 197 | 198 | switch (result.analyzer.handle.tree.nodes.items(.tag)[var_decl.ast.init_node]) { 199 | .identifier, .field_access => { 200 | result = try result.analyzer.resolveAndMarkDeclarationComplex(result.analyzer, result.scope_idx, var_decl.ast.init_node); 201 | }, 202 | else => {}, 203 | } 204 | }, 205 | else => {}, 206 | }; 207 | if (result.declaration) |scope_decl| { 208 | for (result.analyzer.scopes.items) |scope, idx| { 209 | if (scope.node_idx == scope_decl.data.variable.ast.init_node) { 210 | const maybe_decl = try analyzer.resolveAndMarkDeclarationIdentifier(result.analyzer, idx, curr_name_idx); 211 | if (maybe_decl.declaration == null) logger.warn("Lookup failure while searching for {s} from {s}", .{ tree.getNodeSource(node_idx), analyzer.handle.path }); 212 | return maybe_decl; 213 | } 214 | } 215 | } 216 | 217 | return DeclarationWithAnalyzer{ .analyzer = analyzer, .scope_idx = scope_idx }; 218 | }, 219 | else => { 220 | // logger.info("HUH! {any}", .{tags[node_idx]}); 221 | return DeclarationWithAnalyzer{ .analyzer = analyzer, .scope_idx = scope_idx }; 222 | }, 223 | }; 224 | } 225 | 226 | pub const Declaration = struct { 227 | node_idx: Ast.Node.Index, 228 | symbol: []const u8 = "", 229 | data: union(enum) { 230 | none, 231 | function: Ast.full.FnProto, 232 | variable: Ast.full.VarDecl, 233 | param: Ast.full.FnProto.Param, 234 | }, 235 | }; 236 | 237 | pub const Field = struct { 238 | node_idx: Ast.Node.Index, 239 | data: Ast.full.ContainerField, 240 | }; 241 | 242 | pub fn getDescriptor(analyzer: *Analyzer, maybe_scope_idx: ?usize) ?[]const u8 { 243 | if (maybe_scope_idx) |scope_idx| { 244 | var scope = analyzer.scopes.items[scope_idx]; 245 | return switch (scope.data) { 246 | .container => |container| container.descriptor, 247 | .function => |function| function.descriptor, 248 | else => null, 249 | }; 250 | } else return null; 251 | } 252 | 253 | pub fn rangeArray(analyzer: *Analyzer, token: zig.Ast.TokenIndex) [4]i32 { 254 | var range_orig = offsets.tokenToRange(analyzer.handle.tree, token); 255 | return [4]i32{ 256 | @intCast(i32, range_orig.start.line), 257 | @intCast(i32, range_orig.start.character), 258 | @intCast(i32, range_orig.end.line), 259 | @intCast(i32, range_orig.end.character), 260 | }; 261 | } 262 | 263 | pub fn addSymbol( 264 | analyzer: *Analyzer, 265 | node_idx: zig.Ast.Node.Index, 266 | symbol_name: []const u8, 267 | ) !void { 268 | const tree = analyzer.handle.tree; 269 | const name_token = utils.getDeclNameToken(tree, node_idx) orelse @panic("Cannot find decl name token"); 270 | 271 | if (try analyzer.recorded_occurrences.fetchPut(analyzer.allocator, name_token, {})) |_| { 272 | logger.err("Encountered reoccuring entry symbol {s} @ token {d}", .{ symbol_name, name_token }); 273 | // @panic("Reoccuring entry!"); 274 | return error.Reocc; 275 | // return; 276 | } 277 | 278 | var comments = try utils.getDocComments(analyzer.allocator, tree, node_idx); 279 | // logger.info("{s}", .{symbol_name}); 280 | try analyzer.symbols.append(analyzer.allocator, .{ 281 | .symbol = symbol_name, 282 | .documentation = comments orelse .{}, 283 | .relationships = .{}, 284 | }); 285 | 286 | try analyzer.occurrences.append(analyzer.allocator, .{ 287 | .range = analyzer.rangeArray(name_token), 288 | .symbol = symbol_name, 289 | .symbol_roles = 0x1, 290 | .override_documentation = .{}, 291 | .syntax_kind = .identifier, 292 | .diagnostics = .{}, 293 | }); 294 | } 295 | 296 | pub fn generateSymbol( 297 | analyzer: *Analyzer, 298 | scope_idx: usize, 299 | declaration: Declaration, 300 | name: []const u8, 301 | ) ![]const u8 { 302 | const scope = &analyzer.scopes.items[scope_idx]; 303 | 304 | return switch (scope.data) { 305 | .block => switch (declaration.data) { 306 | .variable => v: { 307 | analyzer.local_counter += 1; 308 | break :v try std.fmt.allocPrint(analyzer.allocator, "local {d}", .{analyzer.local_counter}); 309 | }, 310 | else => @panic("never gonna let you down"), 311 | }, 312 | .container => switch (declaration.data) { 313 | .function => try std.mem.concat(analyzer.allocator, u8, &.{ scope.data.container.descriptor, "`", analyzer.formatSubSymbol(name), "`", "()." }), 314 | else => try std.mem.concat(analyzer.allocator, u8, &.{ scope.data.container.descriptor, "`", analyzer.formatSubSymbol(name), "`", "#" }), 315 | }, 316 | else => @panic("never gonna give you up."), 317 | }; 318 | } 319 | 320 | pub fn addDeclaration( 321 | analyzer: *Analyzer, 322 | scope_idx: usize, 323 | declaration: Declaration, 324 | /// If name is not specified, default will method will be used 325 | provided_name: ?[]const u8, 326 | ) !void { 327 | const name = provided_name orelse utils.getDeclName(analyzer.handle.tree, declaration.node_idx) orelse @panic("Cannot find decl name for declaration"); 328 | const scope = &analyzer.scopes.items[scope_idx]; 329 | 330 | if (try scope.decls.fetchPut( 331 | analyzer.allocator, 332 | name, 333 | declaration, 334 | )) |curr| { 335 | _ = curr; 336 | @panic("This shouldn't happen!"); 337 | } else { 338 | try analyzer.addSymbol(declaration.node_idx, declaration.symbol); 339 | } 340 | } 341 | 342 | pub fn newContainerScope( 343 | analyzer: *Analyzer, 344 | maybe_parent_scope_idx: ?usize, 345 | node_idx: Ast.Node.Index, 346 | scope_name: ?[]const u8, 347 | ) !void { 348 | const tree = analyzer.handle.tree; 349 | 350 | for (tree.nodes.items(.tag)) |tag, i| { 351 | switch (tag) { 352 | .builtin_call, 353 | .builtin_call_comma, 354 | .builtin_call_two, 355 | .builtin_call_two_comma, 356 | => { 357 | var buffer: [2]Ast.Node.Index = undefined; 358 | const params = utils.builtinCallParams(tree, @intCast(Ast.Node.Index, i), &buffer).?; 359 | 360 | if (std.mem.eql(u8, tree.tokenSlice(tree.nodes.items(.main_token)[i]), "@import")) { 361 | if (params.len == 0) continue; 362 | const import_param = params[0]; 363 | if (tree.nodes.items(.tag)[import_param] != .string_literal) continue; 364 | 365 | const import_str = tree.tokenSlice(tree.nodes.items(.main_token)[import_param]); 366 | _ = try analyzer.handle.document_store.resolveImportHandle(analyzer.handle, import_str[1 .. import_str.len - 1]); 367 | } 368 | }, 369 | else => {}, 370 | } 371 | } 372 | 373 | var scope = try analyzer.scopes.addOne(analyzer.allocator); 374 | scope.* = .{ 375 | .node_idx = node_idx, 376 | .parent_scope_idx = maybe_parent_scope_idx, 377 | .range = nodeSourceRange(tree, node_idx), 378 | .data = .{ 379 | .container = .{ 380 | .descriptor = if (node_idx == 0) 381 | try std.fmt.allocPrint(analyzer.allocator, "file . {s} unversioned {s}", .{ analyzer.handle.package, analyzer.handle.formatter() }) 382 | else 383 | (if (analyzer.getDescriptor(maybe_parent_scope_idx)) |desc| 384 | try std.mem.concat(analyzer.allocator, u8, &.{ desc, "`", analyzer.formatSubSymbol(scope_name orelse @panic("amogus")), "`", "#" }) 385 | else 386 | try std.fmt.allocPrint(analyzer.allocator, "file . {s} unversioned ", .{analyzer.handle.package})), 387 | .node_idx = node_idx, 388 | }, 389 | }, 390 | }; 391 | 392 | const scope_idx = analyzer.scopes.items.len - 1; 393 | 394 | var buffer: [2]Ast.Node.Index = undefined; 395 | const members = utils.declMembers(tree, node_idx, &buffer); 396 | 397 | const tags = tree.nodes.items(.tag); 398 | 399 | for (members) |member| { 400 | const name = utils.getDeclName(tree, member) orelse continue; 401 | 402 | const maybe_container_field: ?zig.Ast.full.ContainerField = switch (tags[member]) { 403 | .container_field => tree.containerField(member), 404 | .container_field_align => tree.containerFieldAlign(member), 405 | .container_field_init => tree.containerFieldInit(member), 406 | else => null, 407 | }; 408 | 409 | if (maybe_container_field) |container_field| { 410 | const field = Field{ 411 | .node_idx = member, 412 | .data = container_field, 413 | }; 414 | 415 | if (try analyzer.scopes.items[scope_idx].data.container.fields.fetchPut( 416 | analyzer.allocator, 417 | name, 418 | field, 419 | )) |curr| { 420 | std.log.info("Duplicate field, handling regardless: {any}", .{curr}); 421 | } else { 422 | try analyzer.addSymbol(member, try std.mem.concat(analyzer.allocator, u8, &.{ analyzer.scopes.items[scope_idx].data.container.descriptor, analyzer.formatSubSymbol(utils.getDeclName(tree, member) orelse @panic("Cannot create declaration name")), "." })); 423 | } 424 | } else { 425 | try analyzer.scopeIntermediate(scope_idx, member, name); 426 | } 427 | } 428 | } 429 | 430 | pub fn postResolves(analyzer: *Analyzer) !void { 431 | for (analyzer.post_resolves.items) |pr| { 432 | _ = try analyzer.resolveAndMarkDeclarationComplex(analyzer, pr.scope_idx, pr.node_idx); 433 | } 434 | } 435 | 436 | pub fn scopeIntermediate( 437 | analyzer: *Analyzer, 438 | scope_idx: usize, 439 | node_idx: Ast.Node.Index, 440 | scope_name: ?[]const u8, 441 | ) anyerror!void { 442 | const tree = analyzer.handle.tree; 443 | const tags = tree.nodes.items(.tag); 444 | const data = tree.nodes.items(.data); 445 | const main_tokens = tree.nodes.items(.main_token); 446 | // const token_tags = tree.tokens.items(.tag); 447 | 448 | // logger.info("{any}", .{tags[node_idx]}); 449 | 450 | // std.log.info("BBBBBBBBBBBBB {d}, {d}", .{ analyzer.scopes.items.len, analyzer.scopes.items[analyzer.scopes.items.len - 1].node_idx }); 451 | 452 | if (analyzer.scopes.items.len != 1 and analyzer.scopes.items[analyzer.scopes.items.len - 1].node_idx == 0) return error.abc; 453 | 454 | switch (tags[node_idx]) { 455 | // .identifier => { 456 | // _ = try analyzer.resolveAndMarkDeclarationIdentifier(analyzer, scope_idx, main_tokens[node_idx]); 457 | // }, 458 | .identifier, .field_access => { 459 | try analyzer.post_resolves.append(analyzer.allocator, .{ .scope_idx = scope_idx, .node_idx = node_idx }); 460 | // _ = try analyzer.resolveAndMarkDeclarationComplex(analyzer, scope_idx, node_idx); 461 | }, 462 | .container_decl, 463 | .container_decl_trailing, 464 | .container_decl_arg, 465 | .container_decl_arg_trailing, 466 | .container_decl_two, 467 | .container_decl_two_trailing, 468 | .tagged_union, 469 | .tagged_union_trailing, 470 | .tagged_union_two, 471 | .tagged_union_two_trailing, 472 | .tagged_union_enum_tag, 473 | .tagged_union_enum_tag_trailing, 474 | .root, 475 | .error_set_decl, 476 | => { 477 | try analyzer.newContainerScope(scope_idx, node_idx, scope_name); 478 | }, 479 | .global_var_decl, 480 | .local_var_decl, 481 | .aligned_var_decl, 482 | .simple_var_decl, 483 | => { 484 | const var_decl = utils.varDecl(tree, node_idx).?; 485 | 486 | var decl = Declaration{ 487 | .node_idx = node_idx, 488 | .data = .{ .variable = var_decl }, 489 | }; 490 | 491 | decl.symbol = try analyzer.generateSymbol(scope_idx, decl, utils.getDeclName(tree, node_idx).?); 492 | 493 | try analyzer.addDeclaration(scope_idx, decl, null); 494 | 495 | if (var_decl.ast.type_node != 0) { 496 | try analyzer.scopeIntermediate(scope_idx, var_decl.ast.type_node, scope_name); 497 | } 498 | 499 | if (var_decl.ast.init_node != 0) { 500 | try analyzer.scopeIntermediate(scope_idx, var_decl.ast.init_node, scope_name); 501 | } 502 | }, 503 | .fn_proto, 504 | .fn_proto_one, 505 | .fn_proto_simple, 506 | .fn_proto_multi, 507 | .fn_decl, 508 | => |fn_tag| { 509 | var buf: [1]Ast.Node.Index = undefined; 510 | const func = utils.fnProto(tree, node_idx, &buf) orelse @panic("Cannot create fnProto"); 511 | 512 | var decl = Declaration{ 513 | .node_idx = node_idx, 514 | .data = .{ .function = func }, 515 | }; 516 | decl.symbol = try analyzer.generateSymbol(scope_idx, decl, utils.getDeclName(analyzer.handle.tree, node_idx) orelse return); 517 | 518 | try analyzer.addDeclaration(scope_idx, decl, null); 519 | 520 | const func_scope_name = if (node_idx == 0) 521 | try std.fmt.allocPrint(analyzer.allocator, "file . {s} unversioned {s}", .{ analyzer.handle.package, analyzer.handle.formatter() }) 522 | else 523 | (if (analyzer.getDescriptor(scope_idx)) |desc| 524 | try std.mem.concat(analyzer.allocator, u8, &.{ desc, analyzer.formatSubSymbol(scope_name orelse @panic("amogus")), "()." }) 525 | else 526 | try std.fmt.allocPrint(analyzer.allocator, "file . {s} unversioned {d} ", .{ analyzer.handle.package, 69 })); 527 | 528 | var scope = try analyzer.scopes.addOne(analyzer.allocator); 529 | scope.* = .{ 530 | .node_idx = node_idx, 531 | .parent_scope_idx = scope_idx, 532 | .range = nodeSourceRange(tree, node_idx), 533 | .data = .{ 534 | .function = .{ 535 | .descriptor = func_scope_name, 536 | .node_idx = node_idx, 537 | }, 538 | }, 539 | }; 540 | 541 | const func_scope_idx = analyzer.scopes.items.len - 1; 542 | 543 | var it = func.iterate(&tree); 544 | while (it.next()) |param| { 545 | // Add parameter decls 546 | if (param.name_token) |name_token| { 547 | _ = name_token; 548 | // try analyzer.addDeclaration(scope_idx, .{ .node_idx = node_idx, .data = .{ .param = param } }, tree.tokenSlice(name_token)); 549 | } 550 | // Visit parameter types to pick up any error sets and enum 551 | // completions 552 | if (param.type_expr != 0) 553 | try analyzer.scopeIntermediate(scope_idx, param.type_expr, scope_name); 554 | } 555 | 556 | // TODO: Fix scoping issue here 557 | // _ = fn_tag; 558 | if (fn_tag == .fn_decl) blk: { 559 | if (data[node_idx].lhs == 0) break :blk; 560 | const return_type_node = data[data[node_idx].lhs].rhs; 561 | 562 | // Visit the return type 563 | if (return_type_node != 0) 564 | try analyzer.scopeIntermediate(func_scope_idx, return_type_node, scope_name); 565 | } 566 | 567 | // Visit the function body 568 | if (data[node_idx].rhs != 0) 569 | try analyzer.scopeIntermediate(func_scope_idx, data[node_idx].rhs, scope_name); 570 | }, 571 | .block, 572 | .block_semicolon, 573 | .block_two, 574 | .block_two_semicolon, 575 | => { 576 | const first_token = tree.firstToken(node_idx); 577 | const last_token = utils.lastToken(tree, node_idx); 578 | 579 | _ = first_token; 580 | _ = last_token; 581 | 582 | // if labeled block 583 | // if (token_tags[first_token] == .identifier) { 584 | // const scope = try scopes.addOne(allocator); 585 | // scope.* = .{ 586 | // .loc = .{ 587 | // .start = offsets.tokenToIndex(tree, main_tokens[node_idx]), 588 | // .end = offsets.tokenToLoc(tree, last_token).start, 589 | // }, 590 | // .data = .other, 591 | // }; 592 | // // TODO: try scope.decls.putNoClobber(allocator, tree.tokenSlice(first_token), .{ .label_decl = first_token }); 593 | // } 594 | 595 | var scope = try analyzer.scopes.addOne(analyzer.allocator); 596 | scope.* = .{ 597 | .node_idx = node_idx, 598 | .parent_scope_idx = scope_idx, 599 | .range = nodeSourceRange(tree, node_idx), 600 | .data = .{ 601 | .block = node_idx, 602 | }, 603 | }; 604 | 605 | const block_scope_idx = analyzer.scopes.items.len - 1; 606 | 607 | var buffer: [2]Ast.Node.Index = undefined; 608 | const statements = utils.blockStatements(tree, node_idx, &buffer) orelse return; 609 | 610 | for (statements) |idx| { 611 | // TODO: 612 | // if (tags[idx] == .@"usingnamespace") { 613 | // try scopes.items[scope_idx].uses.append(allocator, idx); 614 | // continue; 615 | // } 616 | 617 | try analyzer.scopeIntermediate(block_scope_idx, idx, null); 618 | } 619 | 620 | return; 621 | }, 622 | .call, 623 | .call_comma, 624 | .call_one, 625 | .call_one_comma, 626 | .async_call, 627 | .async_call_comma, 628 | .async_call_one, 629 | .async_call_one_comma, 630 | => { 631 | var buf: [1]Ast.Node.Index = undefined; 632 | const call = utils.callFull(tree, node_idx, &buf) orelse return; 633 | 634 | if (call.ast.fn_expr != 0) 635 | try analyzer.scopeIntermediate(scope_idx, call.ast.fn_expr, scope_name); 636 | for (call.ast.params) |param| 637 | if (param != 0) 638 | try analyzer.scopeIntermediate(scope_idx, param, scope_name); 639 | }, 640 | .equal_equal, 641 | .bang_equal, 642 | .less_than, 643 | .greater_than, 644 | .less_or_equal, 645 | .greater_or_equal, 646 | .assign_mul, 647 | .assign_div, 648 | .assign_mod, 649 | .assign_add, 650 | .assign_sub, 651 | .assign_shl, 652 | .assign_shr, 653 | .assign_bit_and, 654 | .assign_bit_xor, 655 | .assign_bit_or, 656 | .assign_mul_wrap, 657 | .assign_add_wrap, 658 | .assign_sub_wrap, 659 | .assign_mul_sat, 660 | .assign_add_sat, 661 | .assign_sub_sat, 662 | .assign_shl_sat, 663 | .assign, 664 | .merge_error_sets, 665 | .mul, 666 | .div, 667 | .mod, 668 | .array_mult, 669 | .mul_wrap, 670 | .mul_sat, 671 | .add, 672 | .sub, 673 | .array_cat, 674 | .add_wrap, 675 | .sub_wrap, 676 | .add_sat, 677 | .sub_sat, 678 | .shl, 679 | .shl_sat, 680 | .shr, 681 | .bit_and, 682 | .bit_xor, 683 | .bit_or, 684 | .@"orelse", 685 | .bool_and, 686 | .bool_or, 687 | .array_type, 688 | .array_access, 689 | .error_union, 690 | => { 691 | if (data[node_idx].lhs != 0) 692 | try analyzer.scopeIntermediate(scope_idx, data[node_idx].lhs, scope_name); 693 | if (data[node_idx].rhs != 0) 694 | try analyzer.scopeIntermediate(scope_idx, data[node_idx].rhs, scope_name); 695 | }, 696 | .@"return", 697 | .@"resume", 698 | .@"suspend", 699 | .deref, 700 | .@"try", 701 | .@"await", 702 | .optional_type, 703 | .@"comptime", 704 | .@"nosuspend", 705 | .bool_not, 706 | .negation, 707 | .bit_not, 708 | .negation_wrap, 709 | .address_of, 710 | .grouped_expression, 711 | .unwrap_optional, 712 | .@"usingnamespace", 713 | => { 714 | if (data[node_idx].lhs != 0) 715 | try analyzer.scopeIntermediate(scope_idx, data[node_idx].lhs, scope_name); 716 | }, 717 | .@"if", 718 | .if_simple, 719 | => { 720 | // TODO: Handle payload, create scopes 721 | const if_node = utils.ifFull(tree, node_idx); 722 | 723 | if (if_node.ast.cond_expr != 0) 724 | try analyzer.scopeIntermediate(scope_idx, if_node.ast.cond_expr, scope_name); 725 | 726 | try analyzer.scopeIntermediate(scope_idx, if_node.ast.then_expr, scope_name); 727 | if (if_node.ast.else_expr != 0) 728 | try analyzer.scopeIntermediate(scope_idx, if_node.ast.else_expr, scope_name); 729 | }, 730 | .@"while", 731 | .while_simple, 732 | .while_cont, 733 | .@"for", 734 | .for_simple, 735 | => { 736 | // TODO: Handle payload, create scopes 737 | 738 | const while_node = utils.whileAst(tree, node_idx).?; 739 | const is_for = tags[node_idx] == .@"for" or tags[node_idx] == .for_simple; 740 | _ = is_for; 741 | 742 | if (while_node.ast.cond_expr != 0) 743 | try analyzer.scopeIntermediate(scope_idx, while_node.ast.cond_expr, scope_name); 744 | try analyzer.scopeIntermediate(scope_idx, while_node.ast.then_expr, scope_name); 745 | if (while_node.ast.else_expr != 0) 746 | try analyzer.scopeIntermediate(scope_idx, while_node.ast.else_expr, scope_name); 747 | }, 748 | .builtin_call, 749 | .builtin_call_comma, 750 | .builtin_call_two, 751 | .builtin_call_two_comma, 752 | => { 753 | var buffer: [2]Ast.Node.Index = undefined; 754 | const params = utils.builtinCallParams(tree, node_idx, &buffer).?; 755 | const call_name = tree.tokenSlice(main_tokens[node_idx]); 756 | 757 | for (params) |p| 758 | try analyzer.scopeIntermediate(scope_idx, p, scope_name); 759 | 760 | if (std.mem.eql(u8, call_name, "@import")) { 761 | const import_param = params[0]; 762 | const import_str = tree.tokenSlice(main_tokens[import_param]); 763 | 764 | try analyzer.scopes.append(analyzer.allocator, .{ 765 | .node_idx = node_idx, 766 | .parent_scope_idx = scope_idx, 767 | .range = nodeSourceRange(tree, node_idx), 768 | .data = .{ 769 | .import = import_str[1 .. import_str.len - 1], 770 | }, 771 | }); 772 | 773 | // _ = analyzer.resolveAndMarkDeclarationComplex(analyzer, scope_idx, node_idx); 774 | // _ = try analyzer.handle.document_store.resolveImportHandle(analyzer.handle, import_str[1 .. import_str.len - 1]); 775 | } 776 | }, 777 | else => {}, 778 | } 779 | } 780 | 781 | fn nodeSourceRange(tree: Ast, node: Ast.Node.Index) SourceRange { 782 | const loc_start = utils.tokenLocation(tree, tree.firstToken(node)); 783 | const loc_end = utils.tokenLocation(tree, tree.lastToken(node)); 784 | 785 | return SourceRange{ 786 | .start = loc_start.start, 787 | .end = loc_end.end, 788 | }; 789 | } 790 | -------------------------------------------------------------------------------- /src/analysis/DocumentStore.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Analyzer = @import("Analyzer.zig"); 3 | 4 | const logger = std.log.scoped(.store); 5 | 6 | const DocumentStore = @This(); 7 | 8 | allocator: std.mem.Allocator, 9 | root_path: []const u8, 10 | /// Root -> Package 11 | packages: std.StringHashMapUnmanaged(Package) = .{}, 12 | 13 | pub const Package = struct { 14 | root: []const u8, 15 | /// Relative path -> Handle 16 | handles: std.StringHashMapUnmanaged(*Handle) = .{}, 17 | }; 18 | 19 | pub const Handle = struct { 20 | document_store: *DocumentStore, 21 | package: []const u8, 22 | /// Relative to package root 23 | path: []const u8, 24 | text: [:0]const u8, 25 | tree: std.zig.Ast, 26 | analyzer: Analyzer, 27 | 28 | pub const PathFormatter = struct { 29 | handle: Handle, 30 | 31 | pub fn format( 32 | self: PathFormatter, 33 | comptime fmt: []const u8, 34 | options: std.fmt.FormatOptions, 35 | writer: anytype, 36 | ) !void { 37 | _ = fmt; 38 | _ = options; 39 | 40 | var splitit = std.mem.split(u8, self.handle.path, &.{std.fs.path.sep}); 41 | while (splitit.next()) |segment| { 42 | if (std.mem.indexOfAny(u8, segment, ".") != null) 43 | try writer.print("`{s}`", .{segment}) 44 | else 45 | try writer.writeAll(segment); 46 | 47 | try writer.writeByte('/'); 48 | } 49 | } 50 | }; 51 | 52 | pub fn formatter(handle: Handle) PathFormatter { 53 | return .{ .handle = handle }; 54 | } 55 | }; 56 | 57 | pub fn createPackage(store: *DocumentStore, package: []const u8, root: []const u8) !void { 58 | if (store.packages.contains(package)) return; 59 | 60 | try store.packages.put(store.allocator, try store.allocator.dupe(u8, package), .{ 61 | .root = try store.allocator.dupe(u8, root), 62 | }); 63 | 64 | _ = try store.loadFile(package, try std.fs.path.relative(store.allocator, store.root_path, root)); 65 | } 66 | 67 | pub fn loadFile(store: *DocumentStore, package: []const u8, path: []const u8) !*Handle { 68 | std.log.info("Loading {s}", .{path}); 69 | std.debug.assert(!std.fs.path.isAbsolute(path)); // use relative path 70 | 71 | const package_entry = store.packages.getEntry(package).?; 72 | const path_duped = try store.allocator.dupe(u8, path); 73 | 74 | var concat_path = try std.fs.path.join(store.allocator, &.{ store.root_path, path }); 75 | defer store.allocator.free(concat_path); 76 | 77 | var file = try std.fs.openFileAbsolute(concat_path, .{}); 78 | defer file.close(); 79 | 80 | const text = try file.readToEndAllocOptions( 81 | store.allocator, 82 | std.math.maxInt(usize), 83 | null, 84 | @alignOf(u8), 85 | 0, 86 | ); 87 | errdefer store.allocator.free(text); 88 | 89 | var tree = try std.zig.parse(store.allocator, text); 90 | errdefer tree.deinit(store.allocator); 91 | 92 | var handle = try store.allocator.create(Handle); 93 | errdefer store.allocator.destroy(handle); 94 | 95 | handle.* = .{ 96 | .document_store = store, 97 | .package = package_entry.key_ptr.*, 98 | .path = path_duped, 99 | .text = text, 100 | .tree = tree, 101 | .analyzer = .{ .allocator = store.allocator, .handle = handle }, 102 | }; 103 | 104 | try store.packages.getEntry(package).?.value_ptr.handles.put(store.allocator, path_duped, handle); 105 | 106 | try handle.analyzer.init(); 107 | 108 | return handle; 109 | } 110 | 111 | pub fn getOrLoadFile(store: *DocumentStore, package: []const u8, path: []const u8) anyerror!*Handle { 112 | return store.packages.get(package).?.handles.get(path) orelse store.loadFile(package, path); 113 | } 114 | 115 | pub fn resolveImportHandle(store: *DocumentStore, handle: *Handle, import: []const u8) anyerror!?*Handle { 116 | if (std.mem.endsWith(u8, import, ".zig")) { 117 | var rel = try std.fs.path.resolve(store.allocator, &[_][]const u8{ std.fs.path.dirname(store.packages.get(handle.package).?.root).?, handle.path, "..", import }); 118 | defer store.allocator.free(rel); 119 | 120 | // logger.info("Importing {s} @ {s} (basename {?s}, rel {s}) within package {s}", .{ import, handle.path, std.fs.path.dirname(handle.path), rel, handle.package }); 121 | 122 | return try store.getOrLoadFile(handle.package, rel[std.fs.path.dirname(store.packages.get(handle.package).?.root).?.len + 1 ..]); 123 | } else { 124 | const pkg = store.packages.get(import) orelse return null; 125 | return pkg.handles.get(pkg.root); 126 | } 127 | 128 | return null; 129 | } 130 | 131 | // pub fn resolveImportPath(store: *DocumentStore, handle: *Handle, import: []const u8) ![]const u8 { 132 | // _ = store; 133 | // _ = handle; 134 | 135 | // // TODO: Stop doing this hack :( 136 | // if (std.mem.eql(u8, import, "std")) return "C:\\Programming\\Zig\\zig-windows-install\\lib\\std\\std.zig"; 137 | // @panic("Import for non-std not implemented"); 138 | // } 139 | 140 | // pub fn getOrCreateHandle(store: *DocumentStore, path: []const u8) !*Handle { 141 | // var gop = try store.handles.getOrPut(store.allocator, try store.allocator.dupe(u8, path)); 142 | // return if (gop.found_existing) 143 | // gop.value_ptr.* 144 | // else 145 | // try store.load(path); 146 | // } 147 | -------------------------------------------------------------------------------- /src/analysis/offsets.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const utils = @import("utils.zig"); 3 | const Ast = std.zig.Ast; 4 | 5 | pub const Position = struct { 6 | line: u32, 7 | character: u32, 8 | }; 9 | 10 | pub const Range = struct { 11 | start: Position, 12 | end: Position, 13 | }; 14 | 15 | pub const Loc = std.zig.Token.Loc; 16 | 17 | pub fn indexToPosition(text: []const u8, index: usize) Position { 18 | const last_line_start = if (std.mem.lastIndexOf(u8, text[0..index], "\n")) |line| line + 1 else 0; 19 | const line_count = std.mem.count(u8, text[0..last_line_start], "\n"); 20 | 21 | return .{ 22 | .line = @intCast(u32, line_count), 23 | .character = @intCast(u32, countCodeUnits(text[last_line_start..index])), 24 | }; 25 | } 26 | 27 | pub fn positionToIndex(text: []const u8, position: Position) usize { 28 | var line: u32 = 0; 29 | var line_start_index: usize = 0; 30 | for (text) |c, i| { 31 | if (line == position.line) break; 32 | if (c == '\n') { 33 | line += 1; 34 | line_start_index = i; 35 | } 36 | } 37 | std.debug.assert(line == position.line); 38 | 39 | const line_text = std.mem.sliceTo(text[line_start_index..], '\n'); 40 | const line_byte_length = getNCodeUnitByteCount(line_text, position.character); 41 | 42 | return line_start_index + line_byte_length; 43 | } 44 | 45 | pub fn tokenToIndex(tree: Ast, token_index: Ast.TokenIndex) usize { 46 | return tree.tokens.items(.start)[token_index]; 47 | } 48 | 49 | pub fn tokenToLoc(tree: Ast, token_index: Ast.TokenIndex) Loc { 50 | const start = tree.tokens.items(.start)[token_index]; 51 | const tag = tree.tokens.items(.tag)[token_index]; 52 | 53 | // Many tokens can be determined entirely by their tag. 54 | if (tag.lexeme()) |lexeme| { 55 | return .{ 56 | .start = start, 57 | .end = start + lexeme.len, 58 | }; 59 | } 60 | 61 | // For some tokens, re-tokenization is needed to find the end. 62 | var tokenizer: std.zig.Tokenizer = .{ 63 | .buffer = tree.source, 64 | .index = start, 65 | .pending_invalid_token = null, 66 | }; 67 | 68 | // Maybe combine multi-line tokens? 69 | const token = tokenizer.next(); 70 | // A failure would indicate a corrupted tree.source 71 | std.debug.assert(token.tag == tag); 72 | return token.loc; 73 | } 74 | 75 | pub fn tokenToSlice(tree: Ast, token_index: Ast.TokenIndex) []const u8 { 76 | return locToSlice(tree.source, tokenToLoc(tree, token_index)); 77 | } 78 | 79 | pub fn tokenToPosition(tree: Ast, token_index: Ast.TokenIndex) Position { 80 | const start = tokenToIndex(tree, token_index); 81 | return indexToPosition(tree.source, start); 82 | } 83 | 84 | pub fn tokenToRange(tree: Ast, token_index: Ast.TokenIndex) Range { 85 | const start = tokenToPosition(tree, token_index); 86 | const loc = tokenToLoc(tree, token_index); 87 | 88 | return .{ 89 | .start = start, 90 | .end = advancePosition(tree.source, start, loc.start, loc.end), 91 | }; 92 | } 93 | 94 | pub fn locLength(text: []const u8, loc: Loc) usize { 95 | return countCodeUnits(text[loc.start..loc.end]); 96 | } 97 | 98 | pub fn tokenLength(tree: Ast, token_index: Ast.TokenIndex) usize { 99 | const loc = tokenToLoc(tree, token_index); 100 | return locLength(tree.source, loc); 101 | } 102 | 103 | pub fn rangeLength(text: []const u8, range: Range) usize { 104 | const loc: Loc = .{ 105 | .start = positionToIndex(text, range.start), 106 | .end = positionToIndex(text, range.end), 107 | }; 108 | return locLength(text, loc); 109 | } 110 | 111 | pub fn tokenIndexLength(text: [:0]const u8, index: usize) usize { 112 | const loc = tokenIndexToLoc(text, index); 113 | return locLength(text, loc); 114 | } 115 | 116 | pub fn tokenIndexToLoc(text: [:0]const u8, index: usize) Loc { 117 | var tokenizer: std.zig.Tokenizer = .{ 118 | .buffer = text, 119 | .index = index, 120 | .pending_invalid_token = null, 121 | }; 122 | 123 | const token = tokenizer.next(); 124 | return .{ .start = token.loc.start, .end = token.loc.end }; 125 | } 126 | 127 | pub fn tokenPositionToLoc(text: [:0]const u8, position: Position) Loc { 128 | const index = positionToIndex(text, position); 129 | return tokenIndexToLoc(text, index); 130 | } 131 | 132 | pub fn tokenIndexToSlice(text: [:0]const u8, index: usize) []const u8 { 133 | return locToSlice(text, tokenIndexToLoc(text, index)); 134 | } 135 | 136 | pub fn tokenPositionToSlice(text: [:0]const u8, position: Position) []const u8 { 137 | return locToSlice(text, tokenPositionToLoc(text, position)); 138 | } 139 | 140 | pub fn tokenIndexToRange(text: [:0]const u8, index: usize) Range { 141 | const start = indexToPosition(text, index); 142 | const loc = tokenIndexToLoc(text, index); 143 | 144 | return .{ 145 | .start = start, 146 | .end = advancePosition(text, start, loc.start, loc.end), 147 | }; 148 | } 149 | 150 | pub fn tokenPositionToRange(text: [:0]const u8, position: Position) Range { 151 | const index = positionToIndex(text, position); 152 | const loc = tokenIndexToLoc(text, index); 153 | 154 | return .{ 155 | .start = position, 156 | .end = advancePosition(text, position, loc.start, loc.end), 157 | }; 158 | } 159 | 160 | pub fn locToSlice(text: []const u8, loc: Loc) []const u8 { 161 | return text[loc.start..loc.end]; 162 | } 163 | 164 | pub fn locToRange(text: []const u8, loc: Loc) Range { 165 | std.debug.assert(loc.start <= loc.end and loc.end <= text.len); 166 | const start = indexToPosition(text, loc.start); 167 | return .{ 168 | .start = start, 169 | .end = advancePosition(text, start, loc.start, loc.end), 170 | }; 171 | } 172 | 173 | pub fn nodeToLoc(tree: Ast, node: Ast.Node.Index) Loc { 174 | return .{ .start = tokenToIndex(tree, tree.firstToken(node)), .end = tokenToLoc(tree, utils.lastToken(tree, node)).end }; 175 | } 176 | 177 | pub fn nodeToSlice(tree: Ast, node: Ast.Node.Index) []const u8 { 178 | return locToSlice(tree.source, nodeToLoc(tree, node)); 179 | } 180 | 181 | pub fn nodeToRange(tree: Ast, node: Ast.Node.Index) Range { 182 | return locToRange(tree.source, nodeToLoc(tree, node)); 183 | } 184 | 185 | pub fn lineLocAtIndex(text: []const u8, index: usize) Loc { 186 | return .{ 187 | .start = if (std.mem.lastIndexOfScalar(u8, text[0..index], '\n')) |idx| idx else 0, 188 | .end = std.mem.indexOfScalarPos(u8, text, index, '\n') orelse text.len, 189 | }; 190 | } 191 | 192 | pub fn lineSliceAtIndex(text: []const u8, index: usize) []const u8 { 193 | return locToSlice(text, lineLocAtIndex(text, index)); 194 | } 195 | 196 | pub fn lineLocAtPosition(text: []const u8, position: Position) Loc { 197 | return lineLocAtIndex(text, positionToIndex(text, position)); 198 | } 199 | 200 | pub fn lineSliceAtPosition(text: []const u8, position: Position) []const u8 { 201 | return locToSlice(text, lineLocAtPosition(text, position)); 202 | } 203 | 204 | pub fn lineLocUntilIndex(text: []const u8, index: usize) Loc { 205 | return .{ 206 | .start = if (std.mem.lastIndexOfScalar(u8, text[0..index], '\n')) |idx| idx + 1 else 0, 207 | .end = index, 208 | }; 209 | } 210 | 211 | pub fn lineSliceUntilIndex(text: []const u8, index: usize) []const u8 { 212 | return locToSlice(text, lineLocUntilIndex(text, index)); 213 | } 214 | 215 | pub fn lineLocUntilPosition(text: []const u8, position: Position) Loc { 216 | return lineLocUntilIndex(text, positionToIndex(text, position)); 217 | } 218 | 219 | pub fn lineSliceUntilPosition(text: []const u8, position: Position) []const u8 { 220 | return locToSlice(text, lineLocUntilPosition(text, position)); 221 | } 222 | 223 | // Helper functions 224 | 225 | /// advance `position` which starts at `from_index` to `to_index` accounting for line breaks 226 | pub fn advancePosition(text: []const u8, position: Position, from_index: usize, to_index: usize) Position { 227 | var line = position.line; 228 | 229 | for (text[from_index..to_index]) |c| { 230 | if (c == '\n') { 231 | line += 1; 232 | } 233 | } 234 | 235 | const line_loc = lineLocUntilIndex(text, to_index); 236 | 237 | return .{ 238 | .line = line, 239 | .character = @intCast(u32, locLength(text, line_loc)), 240 | }; 241 | } 242 | 243 | /// returns the number of code units in `text` 244 | pub fn countCodeUnits(text: []const u8) usize { 245 | return text.len; 246 | } 247 | 248 | /// returns the number of (utf-8 code units / bytes) that represent `n` code units in `text` 249 | pub fn getNCodeUnitByteCount(text: []const u8, n: usize) usize { 250 | _ = text; 251 | return n; 252 | } 253 | -------------------------------------------------------------------------------- /src/analysis/utils.zig: -------------------------------------------------------------------------------- 1 | //! Collection of functions from std.zig.ast that we need 2 | //! and may hit undefined in the standard library implementation 3 | //! when there are parser errors. 4 | 5 | const std = @import("std"); 6 | const Ast = std.zig.Ast; 7 | const Node = Ast.Node; 8 | const full = Ast.full; 9 | const builtin = @import("builtin"); 10 | 11 | fn fullPtrType(tree: Ast, info: full.PtrType.Components) full.PtrType { 12 | const token_tags = tree.tokens.items(.tag); 13 | // TODO: looks like stage1 isn't quite smart enough to handle enum 14 | // literals in some places here 15 | const Size = std.builtin.TypeInfo.Pointer.Size; 16 | const size: Size = switch (token_tags[info.main_token]) { 17 | .asterisk, 18 | .asterisk_asterisk, 19 | => switch (token_tags[info.main_token + 1]) { 20 | .r_bracket, .colon => .Many, 21 | .identifier => if (token_tags[info.main_token - 1] == .l_bracket) Size.C else .One, 22 | else => .One, 23 | }, 24 | .l_bracket => Size.Slice, 25 | else => unreachable, 26 | }; 27 | var result: full.PtrType = .{ 28 | .size = size, 29 | .allowzero_token = null, 30 | .const_token = null, 31 | .volatile_token = null, 32 | .ast = info, 33 | }; 34 | // We need to be careful that we don't iterate over any sub-expressions 35 | // here while looking for modifiers as that could result in false 36 | // positives. Therefore, start after a sentinel if there is one and 37 | // skip over any align node and bit range nodes. 38 | var i = if (info.sentinel != 0) lastToken(tree, info.sentinel) + 1 else info.main_token; 39 | const end = tree.firstToken(info.child_type); 40 | while (i < end) : (i += 1) { 41 | switch (token_tags[i]) { 42 | .keyword_allowzero => result.allowzero_token = i, 43 | .keyword_const => result.const_token = i, 44 | .keyword_volatile => result.volatile_token = i, 45 | .keyword_align => { 46 | std.debug.assert(info.align_node != 0); 47 | if (info.bit_range_end != 0) { 48 | std.debug.assert(info.bit_range_start != 0); 49 | i = lastToken(tree, info.bit_range_end) + 1; 50 | } else { 51 | i = lastToken(tree, info.align_node) + 1; 52 | } 53 | }, 54 | else => {}, 55 | } 56 | } 57 | return result; 58 | } 59 | 60 | pub fn ptrTypeSimple(tree: Ast, node: Node.Index) full.PtrType { 61 | std.debug.assert(tree.nodes.items(.tag)[node] == .ptr_type); 62 | const data = tree.nodes.items(.data)[node]; 63 | const extra = tree.extraData(data.lhs, Node.PtrType); 64 | return fullPtrType(tree, .{ 65 | .main_token = tree.nodes.items(.main_token)[node], 66 | .align_node = extra.align_node, 67 | .addrspace_node = extra.addrspace_node, 68 | .sentinel = extra.sentinel, 69 | .bit_range_start = 0, 70 | .bit_range_end = 0, 71 | .child_type = data.rhs, 72 | }); 73 | } 74 | 75 | pub fn ptrTypeSentinel(tree: Ast, node: Node.Index) full.PtrType { 76 | std.debug.assert(tree.nodes.items(.tag)[node] == .ptr_type_sentinel); 77 | const data = tree.nodes.items(.data)[node]; 78 | return fullPtrType(tree, .{ 79 | .main_token = tree.nodes.items(.main_token)[node], 80 | .align_node = 0, 81 | .addrspace_node = 0, 82 | .sentinel = data.lhs, 83 | .bit_range_start = 0, 84 | .bit_range_end = 0, 85 | .child_type = data.rhs, 86 | }); 87 | } 88 | 89 | pub fn ptrTypeAligned(tree: Ast, node: Node.Index) full.PtrType { 90 | std.debug.assert(tree.nodes.items(.tag)[node] == .ptr_type_aligned); 91 | const data = tree.nodes.items(.data)[node]; 92 | return fullPtrType(tree, .{ 93 | .main_token = tree.nodes.items(.main_token)[node], 94 | .align_node = data.lhs, 95 | .addrspace_node = 0, 96 | .sentinel = 0, 97 | .bit_range_start = 0, 98 | .bit_range_end = 0, 99 | .child_type = data.rhs, 100 | }); 101 | } 102 | 103 | pub fn ptrTypeBitRange(tree: Ast, node: Node.Index) full.PtrType { 104 | std.debug.assert(tree.nodes.items(.tag)[node] == .ptr_type_bit_range); 105 | const data = tree.nodes.items(.data)[node]; 106 | const extra = tree.extraData(data.lhs, Node.PtrTypeBitRange); 107 | return fullPtrType(tree, .{ 108 | .main_token = tree.nodes.items(.main_token)[node], 109 | .align_node = extra.align_node, 110 | .addrspace_node = extra.addrspace_node, 111 | .sentinel = extra.sentinel, 112 | .bit_range_start = extra.bit_range_start, 113 | .bit_range_end = extra.bit_range_end, 114 | .child_type = data.rhs, 115 | }); 116 | } 117 | 118 | fn fullIf(tree: Ast, info: full.If.Components) full.If { 119 | const token_tags = tree.tokens.items(.tag); 120 | var result: full.If = .{ 121 | .ast = info, 122 | .payload_token = null, 123 | .error_token = null, 124 | .else_token = undefined, 125 | }; 126 | // if (cond_expr) |x| 127 | // ^ ^ 128 | const payload_pipe = lastToken(tree, info.cond_expr) + 2; 129 | if (token_tags[payload_pipe] == .pipe) { 130 | result.payload_token = payload_pipe + 1; 131 | } 132 | if (info.else_expr != 0) { 133 | // then_expr else |x| 134 | // ^ ^ 135 | result.else_token = lastToken(tree, info.then_expr) + 1; 136 | if (token_tags[result.else_token + 1] == .pipe) { 137 | result.error_token = result.else_token + 2; 138 | } 139 | } 140 | return result; 141 | } 142 | 143 | pub fn ifFull(tree: Ast, node: Node.Index) full.If { 144 | const data = tree.nodes.items(.data)[node]; 145 | if (tree.nodes.items(.tag)[node] == .@"if") { 146 | const extra = tree.extraData(data.rhs, Node.If); 147 | return fullIf(tree, .{ 148 | .cond_expr = data.lhs, 149 | .then_expr = extra.then_expr, 150 | .else_expr = extra.else_expr, 151 | .if_token = tree.nodes.items(.main_token)[node], 152 | }); 153 | } else { 154 | std.debug.assert(tree.nodes.items(.tag)[node] == .if_simple); 155 | return fullIf(tree, .{ 156 | .cond_expr = data.lhs, 157 | .then_expr = data.rhs, 158 | .else_expr = 0, 159 | .if_token = tree.nodes.items(.main_token)[node], 160 | }); 161 | } 162 | } 163 | 164 | fn fullWhile(tree: Ast, info: full.While.Components) full.While { 165 | const token_tags = tree.tokens.items(.tag); 166 | var result: full.While = .{ 167 | .ast = info, 168 | .inline_token = null, 169 | .label_token = null, 170 | .payload_token = null, 171 | .else_token = undefined, 172 | .error_token = null, 173 | }; 174 | var tok_i = info.while_token - 1; 175 | if (token_tags[tok_i] == .keyword_inline) { 176 | result.inline_token = tok_i; 177 | tok_i -= 1; 178 | } 179 | if (token_tags[tok_i] == .colon and 180 | token_tags[tok_i - 1] == .identifier) 181 | { 182 | result.label_token = tok_i - 1; 183 | } 184 | const last_cond_token = lastToken(tree, info.cond_expr); 185 | if (token_tags[last_cond_token + 2] == .pipe) { 186 | result.payload_token = last_cond_token + 3; 187 | } 188 | if (info.else_expr != 0) { 189 | // then_expr else |x| 190 | // ^ ^ 191 | result.else_token = lastToken(tree, info.then_expr) + 1; 192 | if (token_tags[result.else_token + 1] == .pipe) { 193 | result.error_token = result.else_token + 2; 194 | } 195 | } 196 | return result; 197 | } 198 | 199 | pub fn whileSimple(tree: Ast, node: Node.Index) full.While { 200 | const data = tree.nodes.items(.data)[node]; 201 | return fullWhile(tree, .{ 202 | .while_token = tree.nodes.items(.main_token)[node], 203 | .cond_expr = data.lhs, 204 | .cont_expr = 0, 205 | .then_expr = data.rhs, 206 | .else_expr = 0, 207 | }); 208 | } 209 | 210 | pub fn whileCont(tree: Ast, node: Node.Index) full.While { 211 | const data = tree.nodes.items(.data)[node]; 212 | const extra = tree.extraData(data.rhs, Node.WhileCont); 213 | return fullWhile(tree, .{ 214 | .while_token = tree.nodes.items(.main_token)[node], 215 | .cond_expr = data.lhs, 216 | .cont_expr = extra.cont_expr, 217 | .then_expr = extra.then_expr, 218 | .else_expr = 0, 219 | }); 220 | } 221 | 222 | pub fn whileFull(tree: Ast, node: Node.Index) full.While { 223 | const data = tree.nodes.items(.data)[node]; 224 | const extra = tree.extraData(data.rhs, Node.While); 225 | return fullWhile(tree, .{ 226 | .while_token = tree.nodes.items(.main_token)[node], 227 | .cond_expr = data.lhs, 228 | .cont_expr = extra.cont_expr, 229 | .then_expr = extra.then_expr, 230 | .else_expr = extra.else_expr, 231 | }); 232 | } 233 | 234 | pub fn forSimple(tree: Ast, node: Node.Index) full.While { 235 | const data = tree.nodes.items(.data)[node]; 236 | return fullWhile(tree, .{ 237 | .while_token = tree.nodes.items(.main_token)[node], 238 | .cond_expr = data.lhs, 239 | .cont_expr = 0, 240 | .then_expr = data.rhs, 241 | .else_expr = 0, 242 | }); 243 | } 244 | 245 | pub fn forFull(tree: Ast, node: Node.Index) full.While { 246 | const data = tree.nodes.items(.data)[node]; 247 | const extra = tree.extraData(data.rhs, Node.If); 248 | return fullWhile(tree, .{ 249 | .while_token = tree.nodes.items(.main_token)[node], 250 | .cond_expr = data.lhs, 251 | .cont_expr = 0, 252 | .then_expr = extra.then_expr, 253 | .else_expr = extra.else_expr, 254 | }); 255 | } 256 | 257 | pub fn lastToken(tree: Ast, node: Ast.Node.Index) Ast.TokenIndex { 258 | const TokenIndex = Ast.TokenIndex; 259 | const tags = tree.nodes.items(.tag); 260 | const datas = tree.nodes.items(.data); 261 | const main_tokens = tree.nodes.items(.main_token); 262 | const token_starts = tree.tokens.items(.start); 263 | const token_tags = tree.tokens.items(.tag); 264 | var n = node; 265 | var end_offset: TokenIndex = 0; 266 | while (true) switch (tags[n]) { 267 | .root => return @intCast(TokenIndex, tree.tokens.len - 1), 268 | .@"usingnamespace" => { 269 | // lhs is the expression 270 | if (datas[n].lhs == 0) { 271 | return main_tokens[n] + end_offset; 272 | } else { 273 | n = datas[n].lhs; 274 | } 275 | }, 276 | .test_decl => { 277 | // rhs is the block 278 | // lhs is the name 279 | if (datas[n].rhs != 0) { 280 | n = datas[n].rhs; 281 | } else if (datas[n].lhs != 0) { 282 | n = datas[n].lhs; 283 | } else { 284 | return main_tokens[n] + end_offset; 285 | } 286 | }, 287 | .global_var_decl => { 288 | // rhs is init node 289 | if (datas[n].rhs != 0) { 290 | n = datas[n].rhs; 291 | } else { 292 | const extra = tree.extraData(datas[n].lhs, Node.GlobalVarDecl); 293 | if (extra.section_node != 0) { 294 | end_offset += 1; // for the rparen 295 | n = extra.section_node; 296 | } else if (extra.align_node != 0) { 297 | end_offset += 1; // for the rparen 298 | n = extra.align_node; 299 | } else if (extra.type_node != 0) { 300 | n = extra.type_node; 301 | } else { 302 | end_offset += 1; // from mut token to name 303 | return main_tokens[n] + end_offset; 304 | } 305 | } 306 | }, 307 | .local_var_decl => { 308 | // rhs is init node 309 | if (datas[n].rhs != 0) { 310 | n = datas[n].rhs; 311 | } else { 312 | const extra = tree.extraData(datas[n].lhs, Node.LocalVarDecl); 313 | if (extra.align_node != 0) { 314 | end_offset += 1; // for the rparen 315 | n = extra.align_node; 316 | } else if (extra.type_node != 0) { 317 | n = extra.type_node; 318 | } else { 319 | end_offset += 1; // from mut token to name 320 | return main_tokens[n] + end_offset; 321 | } 322 | } 323 | }, 324 | .simple_var_decl => { 325 | // rhs is init node 326 | if (datas[n].rhs != 0) { 327 | n = datas[n].rhs; 328 | } else if (datas[n].lhs != 0) { 329 | n = datas[n].lhs; 330 | } else { 331 | end_offset += 1; // from mut token to name 332 | return main_tokens[n] + end_offset; 333 | } 334 | }, 335 | .aligned_var_decl => { 336 | // rhs is init node, lhs is align node 337 | if (datas[n].rhs != 0) { 338 | n = datas[n].rhs; 339 | } else if (datas[n].lhs != 0) { 340 | end_offset += 1; // for the rparen 341 | n = datas[n].lhs; 342 | } else { 343 | end_offset += 1; // from mut token to name 344 | return main_tokens[n] + end_offset; 345 | } 346 | }, 347 | .@"errdefer" => { 348 | // lhs is the token payload, rhs is the expression 349 | if (datas[n].rhs != 0) { 350 | n = datas[n].rhs; 351 | } else if (datas[n].lhs != 0) { 352 | // right pipe 353 | end_offset += 1; 354 | n = datas[n].lhs; 355 | } else { 356 | return main_tokens[n] + end_offset; 357 | } 358 | }, 359 | .@"defer" => { 360 | // rhs is the defered expr 361 | if (datas[n].rhs != 0) { 362 | n = datas[n].rhs; 363 | } else { 364 | return main_tokens[n] + end_offset; 365 | } 366 | }, 367 | 368 | .bool_not, 369 | .negation, 370 | .bit_not, 371 | .negation_wrap, 372 | .address_of, 373 | .@"try", 374 | .@"await", 375 | .optional_type, 376 | .@"resume", 377 | .@"nosuspend", 378 | .@"comptime", 379 | => n = datas[n].lhs, 380 | 381 | .@"catch", 382 | .equal_equal, 383 | .bang_equal, 384 | .less_than, 385 | .greater_than, 386 | .less_or_equal, 387 | .greater_or_equal, 388 | .assign_mul, 389 | .assign_div, 390 | .assign_mod, 391 | .assign_add, 392 | .assign_sub, 393 | .assign_shl, 394 | .assign_shr, 395 | .assign_bit_and, 396 | .assign_bit_xor, 397 | .assign_bit_or, 398 | .assign_mul_wrap, 399 | .assign_add_wrap, 400 | .assign_sub_wrap, 401 | .assign_mul_sat, 402 | .assign_add_sat, 403 | .assign_sub_sat, 404 | .assign_shl_sat, 405 | .assign, 406 | .merge_error_sets, 407 | .mul, 408 | .div, 409 | .mod, 410 | .array_mult, 411 | .mul_wrap, 412 | .mul_sat, 413 | .add, 414 | .sub, 415 | .array_cat, 416 | .add_wrap, 417 | .sub_wrap, 418 | .add_sat, 419 | .sub_sat, 420 | .shl, 421 | .shl_sat, 422 | .shr, 423 | .bit_and, 424 | .bit_xor, 425 | .bit_or, 426 | .@"orelse", 427 | .bool_and, 428 | .bool_or, 429 | .anyframe_type, 430 | .error_union, 431 | .if_simple, 432 | .while_simple, 433 | .for_simple, 434 | .ptr_type_aligned, 435 | .ptr_type_sentinel, 436 | .ptr_type, 437 | .ptr_type_bit_range, 438 | .array_type, 439 | .switch_case_one, 440 | .switch_case, 441 | .switch_case_inline_one, 442 | .switch_case_inline, 443 | .switch_range, 444 | => n = datas[n].rhs, 445 | 446 | .field_access, 447 | .unwrap_optional, 448 | .grouped_expression, 449 | .multiline_string_literal, 450 | .error_set_decl, 451 | .asm_simple, 452 | .asm_output, 453 | .asm_input, 454 | => return datas[n].rhs + end_offset, 455 | 456 | .error_value => { 457 | if (datas[n].rhs != 0) { 458 | return datas[n].rhs + end_offset; 459 | } else if (datas[n].lhs != 0) { 460 | return datas[n].lhs + end_offset; 461 | } else { 462 | return main_tokens[n] + end_offset; 463 | } 464 | }, 465 | 466 | .anyframe_literal, 467 | .char_literal, 468 | .number_literal, 469 | // .float_literal, 470 | .unreachable_literal, 471 | .identifier, 472 | .deref, 473 | .enum_literal, 474 | .string_literal, 475 | => return main_tokens[n] + end_offset, 476 | 477 | .@"return" => if (datas[n].lhs != 0) { 478 | n = datas[n].lhs; 479 | } else { 480 | return main_tokens[n] + end_offset; 481 | }, 482 | 483 | .call, .async_call => { 484 | end_offset += 1; // for the rparen 485 | const params = tree.extraData(datas[n].rhs, Node.SubRange); 486 | if (params.end - params.start == 0) { 487 | return main_tokens[n] + end_offset; 488 | } 489 | n = tree.extra_data[params.end - 1]; // last parameter 490 | }, 491 | .tagged_union_enum_tag => { 492 | const members = tree.extraData(datas[n].rhs, Node.SubRange); 493 | if (members.end - members.start == 0) { 494 | end_offset += 4; // for the rparen + rparen + lbrace + rbrace 495 | n = datas[n].lhs; 496 | } else { 497 | end_offset += 1; // for the rbrace 498 | n = tree.extra_data[members.end - 1]; // last parameter 499 | } 500 | }, 501 | .call_comma, 502 | .async_call_comma, 503 | .tagged_union_enum_tag_trailing, 504 | => { 505 | end_offset += 2; // for the comma/semicolon + rparen/rbrace 506 | const params = tree.extraData(datas[n].rhs, Node.SubRange); 507 | std.debug.assert(params.end > params.start); 508 | n = tree.extra_data[params.end - 1]; // last parameter 509 | }, 510 | .@"switch" => { 511 | const cases = tree.extraData(datas[n].rhs, Node.SubRange); 512 | if (cases.end - cases.start == 0) { 513 | end_offset += 3; // rparen, lbrace, rbrace 514 | n = datas[n].lhs; // condition expression 515 | } else { 516 | end_offset += 1; // for the rbrace 517 | n = tree.extra_data[cases.end - 1]; // last case 518 | } 519 | }, 520 | .container_decl_arg => { 521 | const members = tree.extraData(datas[n].rhs, Node.SubRange); 522 | if (members.end - members.start == 0) { 523 | end_offset += 3; // for the rparen + lbrace + rbrace 524 | n = datas[n].lhs; 525 | } else { 526 | end_offset += 1; // for the rbrace 527 | n = tree.extra_data[members.end - 1]; // last parameter 528 | } 529 | }, 530 | .@"asm" => { 531 | const extra = tree.extraData(datas[n].rhs, Node.Asm); 532 | return extra.rparen + end_offset; 533 | }, 534 | .array_init, 535 | .struct_init, 536 | => { 537 | const elements = tree.extraData(datas[n].rhs, Node.SubRange); 538 | std.debug.assert(elements.end - elements.start > 0); 539 | end_offset += 1; // for the rbrace 540 | n = tree.extra_data[elements.end - 1]; // last element 541 | }, 542 | .array_init_comma, 543 | .struct_init_comma, 544 | .container_decl_arg_trailing, 545 | .switch_comma, 546 | => { 547 | const members = tree.extraData(datas[n].rhs, Node.SubRange); 548 | std.debug.assert(members.end - members.start > 0); 549 | end_offset += 2; // for the comma + rbrace 550 | n = tree.extra_data[members.end - 1]; // last parameter 551 | }, 552 | .array_init_dot, 553 | .struct_init_dot, 554 | .block, 555 | .container_decl, 556 | .tagged_union, 557 | .builtin_call, 558 | => { 559 | std.debug.assert(datas[n].rhs - datas[n].lhs > 0); 560 | end_offset += 1; // for the rbrace 561 | n = tree.extra_data[datas[n].rhs - 1]; // last statement 562 | }, 563 | .array_init_dot_comma, 564 | .struct_init_dot_comma, 565 | .block_semicolon, 566 | .container_decl_trailing, 567 | .tagged_union_trailing, 568 | .builtin_call_comma, 569 | => { 570 | std.debug.assert(datas[n].rhs - datas[n].lhs > 0); 571 | end_offset += 2; // for the comma/semicolon + rbrace/rparen 572 | n = tree.extra_data[datas[n].rhs - 1]; // last member 573 | }, 574 | .call_one, 575 | .async_call_one, 576 | .array_access, 577 | => { 578 | end_offset += 1; // for the rparen/rbracket 579 | if (datas[n].rhs == 0) { 580 | return main_tokens[n] + end_offset; 581 | } 582 | n = datas[n].rhs; 583 | }, 584 | .array_init_dot_two, 585 | .block_two, 586 | .builtin_call_two, 587 | .struct_init_dot_two, 588 | .container_decl_two, 589 | .tagged_union_two, 590 | => { 591 | if (datas[n].rhs != 0) { 592 | end_offset += 1; // for the rparen/rbrace 593 | n = datas[n].rhs; 594 | } else if (datas[n].lhs != 0) { 595 | end_offset += 1; // for the rparen/rbrace 596 | n = datas[n].lhs; 597 | } else { 598 | switch (tags[n]) { 599 | .array_init_dot_two, 600 | .block_two, 601 | .struct_init_dot_two, 602 | => end_offset += 1, // rbrace 603 | .builtin_call_two => end_offset += 2, // lparen/lbrace + rparen/rbrace 604 | .container_decl_two => { 605 | var i: u32 = 2; // lbrace + rbrace 606 | while (token_tags[main_tokens[n] + i] == .container_doc_comment) i += 1; 607 | end_offset += i; 608 | }, 609 | .tagged_union_two => { 610 | var i: u32 = 5; // (enum) {} 611 | while (token_tags[main_tokens[n] + i] == .container_doc_comment) i += 1; 612 | end_offset += i; 613 | }, 614 | else => unreachable, 615 | } 616 | return main_tokens[n] + end_offset; 617 | } 618 | }, 619 | .array_init_dot_two_comma, 620 | .builtin_call_two_comma, 621 | .block_two_semicolon, 622 | .struct_init_dot_two_comma, 623 | .container_decl_two_trailing, 624 | .tagged_union_two_trailing, 625 | => { 626 | end_offset += 2; // for the comma/semicolon + rbrace/rparen 627 | if (datas[n].rhs != 0) { 628 | n = datas[n].rhs; 629 | } else if (datas[n].lhs != 0) { 630 | n = datas[n].lhs; 631 | } else { 632 | return main_tokens[n] + end_offset; // returns { } 633 | } 634 | }, 635 | .container_field_init => { 636 | if (datas[n].rhs != 0) { 637 | n = datas[n].rhs; 638 | } else if (datas[n].lhs != 0) { 639 | n = datas[n].lhs; 640 | } else { 641 | return main_tokens[n] + end_offset; 642 | } 643 | }, 644 | .container_field_align => { 645 | if (datas[n].rhs != 0) { 646 | end_offset += 1; // for the rparen 647 | n = datas[n].rhs; 648 | } else if (datas[n].lhs != 0) { 649 | n = datas[n].lhs; 650 | } else { 651 | return main_tokens[n] + end_offset; 652 | } 653 | }, 654 | .container_field => { 655 | const extra = tree.extraData(datas[n].rhs, Node.ContainerField); 656 | if (extra.value_expr != 0) { 657 | n = extra.value_expr; 658 | } else if (extra.align_expr != 0) { 659 | end_offset += 1; // for the rparen 660 | n = extra.align_expr; 661 | } else if (datas[n].lhs != 0) { 662 | n = datas[n].lhs; 663 | } else { 664 | return main_tokens[n] + end_offset; 665 | } 666 | }, 667 | 668 | .array_init_one, 669 | .struct_init_one, 670 | => { 671 | end_offset += 1; // rbrace 672 | if (datas[n].rhs == 0) { 673 | return main_tokens[n] + end_offset; 674 | } else { 675 | n = datas[n].rhs; 676 | } 677 | }, 678 | .slice_open, 679 | .call_one_comma, 680 | .async_call_one_comma, 681 | .array_init_one_comma, 682 | .struct_init_one_comma, 683 | => { 684 | end_offset += 2; // ellipsis2 + rbracket, or comma + rparen 685 | n = datas[n].rhs; 686 | std.debug.assert(n != 0); 687 | }, 688 | .slice => { 689 | const extra = tree.extraData(datas[n].rhs, Node.Slice); 690 | std.debug.assert(extra.end != 0); // should have used slice_open 691 | end_offset += 1; // rbracket 692 | n = extra.end; 693 | }, 694 | .slice_sentinel => { 695 | const extra = tree.extraData(datas[n].rhs, Node.SliceSentinel); 696 | if (extra.sentinel != 0) { 697 | end_offset += 1; // right bracket 698 | n = extra.sentinel; 699 | } else if (extra.end != 0) { 700 | end_offset += 2; // colon, right bracket 701 | n = extra.end; 702 | } else { 703 | // Assume both sentinel and end are completely devoid of tokens 704 | end_offset += 3; // ellipsis, colon, right bracket 705 | n = extra.start; 706 | } 707 | }, 708 | 709 | .@"continue" => { 710 | if (datas[n].lhs != 0) { 711 | return datas[n].lhs + end_offset; 712 | } else { 713 | return main_tokens[n] + end_offset; 714 | } 715 | }, 716 | .@"break" => { 717 | if (datas[n].rhs != 0) { 718 | n = datas[n].rhs; 719 | } else if (datas[n].lhs != 0) { 720 | return datas[n].lhs + end_offset; 721 | } else { 722 | return main_tokens[n] + end_offset; 723 | } 724 | }, 725 | .fn_decl => { 726 | if (datas[n].rhs != 0) { 727 | n = datas[n].rhs; 728 | } else { 729 | n = datas[n].lhs; 730 | } 731 | }, 732 | .fn_proto_multi => { 733 | const extra = tree.extraData(datas[n].lhs, Node.SubRange); 734 | // rhs can be 0 when no return type is provided 735 | if (datas[n].rhs != 0) { 736 | n = datas[n].rhs; 737 | } else { 738 | // Use the last argument and skip right paren 739 | n = tree.extra_data[extra.end - 1]; 740 | end_offset += 1; 741 | } 742 | }, 743 | .fn_proto_simple => { 744 | // rhs can be 0 when no return type is provided 745 | // lhs can be 0 when no parameter is provided 746 | if (datas[n].rhs != 0) { 747 | n = datas[n].rhs; 748 | } else if (datas[n].lhs != 0) { 749 | n = datas[n].lhs; 750 | // Skip right paren 751 | end_offset += 1; 752 | } else { 753 | // Skip left and right paren 754 | return main_tokens[n] + end_offset + 2; 755 | } 756 | }, 757 | .fn_proto_one => { 758 | const extra = tree.extraData(datas[n].lhs, Node.FnProtoOne); 759 | // addrspace, linksection, callconv, align can appear in any order, so we 760 | // find the last one here. 761 | // rhs can be zero if no return type is provided 762 | var max_node: Node.Index = 0; 763 | var max_start: u32 = 0; 764 | if (datas[n].rhs != 0) { 765 | max_node = datas[n].rhs; 766 | max_start = token_starts[main_tokens[max_node]]; 767 | } 768 | 769 | var max_offset: TokenIndex = 0; 770 | if (extra.align_expr != 0) { 771 | const start = token_starts[main_tokens[extra.align_expr]]; 772 | if (start > max_start) { 773 | max_node = extra.align_expr; 774 | max_start = start; 775 | max_offset = 1; // for the rparen 776 | } 777 | } 778 | if (extra.addrspace_expr != 0) { 779 | const start = token_starts[main_tokens[extra.addrspace_expr]]; 780 | if (start > max_start) { 781 | max_node = extra.addrspace_expr; 782 | max_start = start; 783 | max_offset = 1; // for the rparen 784 | } 785 | } 786 | if (extra.section_expr != 0) { 787 | const start = token_starts[main_tokens[extra.section_expr]]; 788 | if (start > max_start) { 789 | max_node = extra.section_expr; 790 | max_start = start; 791 | max_offset = 1; // for the rparen 792 | } 793 | } 794 | if (extra.callconv_expr != 0) { 795 | const start = token_starts[main_tokens[extra.callconv_expr]]; 796 | if (start > max_start) { 797 | max_node = extra.callconv_expr; 798 | max_start = start; 799 | max_offset = 1; // for the rparen 800 | } 801 | } 802 | 803 | if (max_node == 0) { 804 | std.debug.assert(max_offset == 0); 805 | // No linksection, callconv, align, return type 806 | if (extra.param != 0) { 807 | n = extra.param; 808 | end_offset += 1; 809 | } else { 810 | // Skip left and right parens 811 | return main_tokens[n] + end_offset + 2; 812 | } 813 | } else { 814 | n = max_node; 815 | end_offset += max_offset; 816 | } 817 | }, 818 | .fn_proto => { 819 | const extra = tree.extraData(datas[n].lhs, Node.FnProto); 820 | // addrspace, linksection, callconv, align can appear in any order, so we 821 | // find the last one here. 822 | // rhs can be zero if no return type is provided 823 | var max_node: Node.Index = 0; 824 | var max_start: u32 = 0; 825 | if (datas[n].rhs != 0) { 826 | max_node = datas[n].rhs; 827 | max_start = token_starts[main_tokens[max_node]]; 828 | } 829 | 830 | var max_offset: TokenIndex = 0; 831 | if (extra.align_expr != 0) { 832 | const start = token_starts[main_tokens[extra.align_expr]]; 833 | if (start > max_start) { 834 | max_node = extra.align_expr; 835 | max_start = start; 836 | max_offset = 1; // for the rparen 837 | } 838 | } 839 | if (extra.addrspace_expr != 0) { 840 | const start = token_starts[main_tokens[extra.addrspace_expr]]; 841 | if (start > max_start) { 842 | max_node = extra.addrspace_expr; 843 | max_start = start; 844 | max_offset = 1; // for the rparen 845 | } 846 | } 847 | if (extra.section_expr != 0) { 848 | const start = token_starts[main_tokens[extra.section_expr]]; 849 | if (start > max_start) { 850 | max_node = extra.section_expr; 851 | max_start = start; 852 | max_offset = 1; // for the rparen 853 | } 854 | } 855 | if (extra.callconv_expr != 0) { 856 | const start = token_starts[main_tokens[extra.callconv_expr]]; 857 | if (start > max_start) { 858 | max_node = extra.callconv_expr; 859 | max_start = start; 860 | max_offset = 1; // for the rparen 861 | } 862 | } 863 | if (max_node == 0) { 864 | std.debug.assert(max_offset == 0); 865 | // No linksection, callconv, align, return type 866 | // Use the last parameter and skip one extra token for the right paren 867 | n = extra.params_end; 868 | end_offset += 1; 869 | } else { 870 | n = max_node; 871 | end_offset += max_offset; 872 | } 873 | }, 874 | .while_cont => { 875 | const extra = tree.extraData(datas[n].rhs, Node.WhileCont); 876 | std.debug.assert(extra.then_expr != 0); 877 | n = extra.then_expr; 878 | }, 879 | .@"while" => { 880 | const extra = tree.extraData(datas[n].rhs, Node.While); 881 | std.debug.assert(extra.else_expr != 0); 882 | n = extra.else_expr; 883 | }, 884 | .@"if", .@"for" => { 885 | const extra = tree.extraData(datas[n].rhs, Node.If); 886 | std.debug.assert(extra.else_expr != 0); 887 | n = extra.else_expr; 888 | }, 889 | .@"suspend" => { 890 | if (datas[n].lhs != 0) { 891 | n = datas[n].lhs; 892 | } else { 893 | return main_tokens[n] + end_offset; 894 | } 895 | }, 896 | .array_type_sentinel => { 897 | const extra = tree.extraData(datas[n].rhs, Node.ArrayTypeSentinel); 898 | n = extra.elem_type; 899 | }, 900 | }; 901 | } 902 | 903 | pub fn containerField(tree: Ast, node: Ast.Node.Index) ?Ast.full.ContainerField { 904 | return switch (tree.nodes.items(.tag)[node]) { 905 | .container_field => tree.containerField(node), 906 | .container_field_init => tree.containerFieldInit(node), 907 | .container_field_align => tree.containerFieldAlign(node), 908 | else => null, 909 | }; 910 | } 911 | 912 | pub fn ptrType(tree: Ast, node: Ast.Node.Index) ?Ast.full.PtrType { 913 | return switch (tree.nodes.items(.tag)[node]) { 914 | .ptr_type => ptrTypeSimple(tree, node), 915 | .ptr_type_aligned => ptrTypeAligned(tree, node), 916 | .ptr_type_bit_range => ptrTypeBitRange(tree, node), 917 | .ptr_type_sentinel => ptrTypeSentinel(tree, node), 918 | else => null, 919 | }; 920 | } 921 | 922 | pub fn whileAst(tree: Ast, node: Ast.Node.Index) ?Ast.full.While { 923 | return switch (tree.nodes.items(.tag)[node]) { 924 | .@"while" => whileFull(tree, node), 925 | .while_simple => whileSimple(tree, node), 926 | .while_cont => whileCont(tree, node), 927 | .@"for" => forFull(tree, node), 928 | .for_simple => forSimple(tree, node), 929 | else => null, 930 | }; 931 | } 932 | 933 | pub fn isContainer(tree: Ast, node: Ast.Node.Index) bool { 934 | return switch (tree.nodes.items(.tag)[node]) { 935 | .container_decl, 936 | .container_decl_trailing, 937 | .container_decl_arg, 938 | .container_decl_arg_trailing, 939 | .container_decl_two, 940 | .container_decl_two_trailing, 941 | .tagged_union, 942 | .tagged_union_trailing, 943 | .tagged_union_two, 944 | .tagged_union_two_trailing, 945 | .tagged_union_enum_tag, 946 | .tagged_union_enum_tag_trailing, 947 | .root, 948 | .error_set_decl, 949 | => true, 950 | else => false, 951 | }; 952 | } 953 | 954 | pub fn containerDecl(tree: Ast, node_idx: Ast.Node.Index, buffer: *[2]Ast.Node.Index) ?full.ContainerDecl { 955 | return switch (tree.nodes.items(.tag)[node_idx]) { 956 | .container_decl, .container_decl_trailing => tree.containerDecl(node_idx), 957 | .container_decl_arg, .container_decl_arg_trailing => tree.containerDeclArg(node_idx), 958 | .container_decl_two, .container_decl_two_trailing => tree.containerDeclTwo(buffer, node_idx), 959 | .tagged_union, .tagged_union_trailing => tree.taggedUnion(node_idx), 960 | .tagged_union_enum_tag, .tagged_union_enum_tag_trailing => tree.taggedUnionEnumTag(node_idx), 961 | .tagged_union_two, .tagged_union_two_trailing => tree.taggedUnionTwo(buffer, node_idx), 962 | else => null, 963 | }; 964 | } 965 | 966 | /// Returns the member indices of a given declaration container. 967 | /// Asserts given `tag` is a container node 968 | pub fn declMembers(tree: Ast, node_idx: Ast.Node.Index, buffer: *[2]Ast.Node.Index) []const Ast.Node.Index { 969 | std.debug.assert(isContainer(tree, node_idx)); 970 | return switch (tree.nodes.items(.tag)[node_idx]) { 971 | .container_decl, .container_decl_trailing => tree.containerDecl(node_idx).ast.members, 972 | .container_decl_arg, .container_decl_arg_trailing => tree.containerDeclArg(node_idx).ast.members, 973 | .container_decl_two, .container_decl_two_trailing => tree.containerDeclTwo(buffer, node_idx).ast.members, 974 | .tagged_union, .tagged_union_trailing => tree.taggedUnion(node_idx).ast.members, 975 | .tagged_union_enum_tag, .tagged_union_enum_tag_trailing => tree.taggedUnionEnumTag(node_idx).ast.members, 976 | .tagged_union_two, .tagged_union_two_trailing => tree.taggedUnionTwo(buffer, node_idx).ast.members, 977 | .root => tree.rootDecls(), 978 | .error_set_decl => &[_]Ast.Node.Index{}, 979 | else => unreachable, 980 | }; 981 | } 982 | 983 | /// Returns an `ast.full.VarDecl` for a given node index. 984 | /// Returns null if the tag doesn't match 985 | pub fn varDecl(tree: Ast, node_idx: Ast.Node.Index) ?Ast.full.VarDecl { 986 | return switch (tree.nodes.items(.tag)[node_idx]) { 987 | .global_var_decl => tree.globalVarDecl(node_idx), 988 | .local_var_decl => tree.localVarDecl(node_idx), 989 | .aligned_var_decl => tree.alignedVarDecl(node_idx), 990 | .simple_var_decl => tree.simpleVarDecl(node_idx), 991 | else => null, 992 | }; 993 | } 994 | 995 | pub fn isPtrType(tree: Ast, node: Ast.Node.Index) bool { 996 | return switch (tree.nodes.items(.tag)[node]) { 997 | .ptr_type, 998 | .ptr_type_aligned, 999 | .ptr_type_bit_range, 1000 | .ptr_type_sentinel, 1001 | => true, 1002 | else => false, 1003 | }; 1004 | } 1005 | 1006 | pub fn isBuiltinCall(tree: Ast, node: Ast.Node.Index) bool { 1007 | return switch (tree.nodes.items(.tag)[node]) { 1008 | .builtin_call, 1009 | .builtin_call_comma, 1010 | .builtin_call_two, 1011 | .builtin_call_two_comma, 1012 | => true, 1013 | else => false, 1014 | }; 1015 | } 1016 | 1017 | pub fn isCall(tree: Ast, node: Ast.Node.Index) bool { 1018 | return switch (tree.nodes.items(.tag)[node]) { 1019 | .call, 1020 | .call_comma, 1021 | .call_one, 1022 | .call_one_comma, 1023 | .async_call, 1024 | .async_call_comma, 1025 | .async_call_one, 1026 | .async_call_one_comma, 1027 | => true, 1028 | else => false, 1029 | }; 1030 | } 1031 | 1032 | pub fn isBlock(tree: Ast, node: Ast.Node.Index) bool { 1033 | return switch (tree.nodes.items(.tag)[node]) { 1034 | .block_two, 1035 | .block_two_semicolon, 1036 | .block, 1037 | .block_semicolon, 1038 | => true, 1039 | else => false, 1040 | }; 1041 | } 1042 | 1043 | pub fn fnProtoHasBody(tree: Ast, node: Ast.Node.Index) ?bool { 1044 | return switch (tree.nodes.items(.tag)[node]) { 1045 | .fn_proto, 1046 | .fn_proto_multi, 1047 | .fn_proto_one, 1048 | .fn_proto_simple, 1049 | => false, 1050 | .fn_decl => true, 1051 | else => null, 1052 | }; 1053 | } 1054 | 1055 | pub fn fnProto(tree: Ast, node: Ast.Node.Index, buf: *[1]Ast.Node.Index) ?Ast.full.FnProto { 1056 | return switch (tree.nodes.items(.tag)[node]) { 1057 | .fn_proto => tree.fnProto(node), 1058 | .fn_proto_multi => tree.fnProtoMulti(node), 1059 | .fn_proto_one => tree.fnProtoOne(buf, node), 1060 | .fn_proto_simple => tree.fnProtoSimple(buf, node), 1061 | .fn_decl => fnProto(tree, tree.nodes.items(.data)[node].lhs, buf), 1062 | else => null, 1063 | }; 1064 | } 1065 | 1066 | pub fn callFull(tree: Ast, node: Ast.Node.Index, buf: *[1]Ast.Node.Index) ?Ast.full.Call { 1067 | return switch (tree.nodes.items(.tag)[node]) { 1068 | .call, 1069 | .call_comma, 1070 | .async_call, 1071 | .async_call_comma, 1072 | => tree.callFull(node), 1073 | .call_one, 1074 | .call_one_comma, 1075 | .async_call_one, 1076 | .async_call_one_comma, 1077 | => tree.callOne(buf, node), 1078 | else => null, 1079 | }; 1080 | } 1081 | 1082 | /// returns a list of parameters 1083 | pub fn builtinCallParams(tree: Ast, node: Ast.Node.Index, buf: *[2]Ast.Node.Index) ?[]const Node.Index { 1084 | const node_data = tree.nodes.items(.data); 1085 | return switch (tree.nodes.items(.tag)[node]) { 1086 | .builtin_call_two, .builtin_call_two_comma => { 1087 | buf[0] = node_data[node].lhs; 1088 | buf[1] = node_data[node].rhs; 1089 | if (node_data[node].lhs == 0) { 1090 | return buf[0..0]; 1091 | } else if (node_data[node].rhs == 0) { 1092 | return buf[0..1]; 1093 | } else { 1094 | return buf[0..2]; 1095 | } 1096 | }, 1097 | .builtin_call, 1098 | .builtin_call_comma, 1099 | => tree.extra_data[node_data[node].lhs..node_data[node].rhs], 1100 | else => return null, 1101 | }; 1102 | } 1103 | 1104 | /// returns a list of statements 1105 | pub fn blockStatements(tree: Ast, node: Ast.Node.Index, buf: *[2]Ast.Node.Index) ?[]const Node.Index { 1106 | const node_data = tree.nodes.items(.data); 1107 | return switch (tree.nodes.items(.tag)[node]) { 1108 | .block_two, .block_two_semicolon => { 1109 | buf[0] = node_data[node].lhs; 1110 | buf[1] = node_data[node].rhs; 1111 | if (node_data[node].lhs == 0) { 1112 | return buf[0..0]; 1113 | } else if (node_data[node].rhs == 0) { 1114 | return buf[0..1]; 1115 | } else { 1116 | return buf[0..2]; 1117 | } 1118 | }, 1119 | .block, 1120 | .block_semicolon, 1121 | => tree.extra_data[node_data[node].lhs..node_data[node].rhs], 1122 | else => return null, 1123 | }; 1124 | } 1125 | 1126 | /// Iterates over FnProto Params w/ added bounds check to support incomplete ast nodes 1127 | pub fn nextFnParam(it: *Ast.full.FnProto.Iterator) ?Ast.full.FnProto.Param { 1128 | const token_tags = it.tree.tokens.items(.tag); 1129 | while (true) { 1130 | var first_doc_comment: ?Ast.TokenIndex = null; 1131 | var comptime_noalias: ?Ast.TokenIndex = null; 1132 | var name_token: ?Ast.TokenIndex = null; 1133 | if (!it.tok_flag) { 1134 | if (it.param_i >= it.fn_proto.ast.params.len) { 1135 | return null; 1136 | } 1137 | const param_type = it.fn_proto.ast.params[it.param_i]; 1138 | var tok_i = it.tree.firstToken(param_type) - 1; 1139 | while (true) : (tok_i -= 1) switch (token_tags[tok_i]) { 1140 | .colon => continue, 1141 | .identifier => name_token = tok_i, 1142 | .doc_comment => first_doc_comment = tok_i, 1143 | .keyword_comptime, .keyword_noalias => comptime_noalias = tok_i, 1144 | else => break, 1145 | }; 1146 | it.param_i += 1; 1147 | it.tok_i = it.tree.lastToken(param_type) + 1; 1148 | 1149 | // #boundsCheck 1150 | // https://github.com/zigtools/zls/issues/567 1151 | if (it.tree.lastToken(param_type) >= it.tree.tokens.len - 1) 1152 | return Ast.full.FnProto.Param{ 1153 | .first_doc_comment = first_doc_comment, 1154 | .comptime_noalias = comptime_noalias, 1155 | .name_token = name_token, 1156 | .anytype_ellipsis3 = null, 1157 | .type_expr = 0, 1158 | }; 1159 | 1160 | // Look for anytype and ... params afterwards. 1161 | if (token_tags[it.tok_i] == .comma) { 1162 | it.tok_i += 1; 1163 | } 1164 | it.tok_flag = true; 1165 | return Ast.full.FnProto.Param{ 1166 | .first_doc_comment = first_doc_comment, 1167 | .comptime_noalias = comptime_noalias, 1168 | .name_token = name_token, 1169 | .anytype_ellipsis3 = null, 1170 | .type_expr = param_type, 1171 | }; 1172 | } 1173 | if (token_tags[it.tok_i] == .comma) { 1174 | it.tok_i += 1; 1175 | } 1176 | if (token_tags[it.tok_i] == .r_paren) { 1177 | return null; 1178 | } 1179 | if (token_tags[it.tok_i] == .doc_comment) { 1180 | first_doc_comment = it.tok_i; 1181 | while (token_tags[it.tok_i] == .doc_comment) { 1182 | it.tok_i += 1; 1183 | } 1184 | } 1185 | switch (token_tags[it.tok_i]) { 1186 | .ellipsis3 => { 1187 | it.tok_flag = false; // Next iteration should return null. 1188 | return Ast.full.FnProto.Param{ 1189 | .first_doc_comment = first_doc_comment, 1190 | .comptime_noalias = null, 1191 | .name_token = null, 1192 | .anytype_ellipsis3 = it.tok_i, 1193 | .type_expr = 0, 1194 | }; 1195 | }, 1196 | .keyword_noalias, .keyword_comptime => { 1197 | comptime_noalias = it.tok_i; 1198 | it.tok_i += 1; 1199 | }, 1200 | else => {}, 1201 | } 1202 | if (token_tags[it.tok_i] == .identifier and 1203 | token_tags[it.tok_i + 1] == .colon) 1204 | { 1205 | name_token = it.tok_i; 1206 | it.tok_i += 2; 1207 | } 1208 | if (token_tags[it.tok_i] == .keyword_anytype) { 1209 | it.tok_i += 1; 1210 | return Ast.full.FnProto.Param{ 1211 | .first_doc_comment = first_doc_comment, 1212 | .comptime_noalias = comptime_noalias, 1213 | .name_token = name_token, 1214 | .anytype_ellipsis3 = it.tok_i - 1, 1215 | .type_expr = 0, 1216 | }; 1217 | } 1218 | it.tok_flag = false; 1219 | } 1220 | } 1221 | 1222 | /// A modified version of tree.tokenSlice that returns an error.UnexpectedToken if the tokenizer encounters an unexpected token 1223 | // https://github.com/zigtools/zls/issues/381 1224 | pub fn tokenSlice(tree: Ast, token_index: Ast.TokenIndex) ![]const u8 { 1225 | const token_starts = tree.tokens.items(.start); 1226 | const token_tags = tree.tokens.items(.tag); 1227 | const token_tag = token_tags[token_index]; 1228 | 1229 | // Many tokens can be determined entirely by their tag. 1230 | if (token_tag.lexeme()) |lexeme| { 1231 | return lexeme; 1232 | } 1233 | 1234 | // For some tokens, re-tokenization is needed to find the end. 1235 | var tokenizer: std.zig.Tokenizer = .{ 1236 | .buffer = tree.source, 1237 | .index = token_starts[token_index], 1238 | .pending_invalid_token = null, 1239 | }; 1240 | const token = tokenizer.next(); 1241 | if (token.tag != token_tag) return error.UnexpectedToken; // assert(token.tag == token_tag); 1242 | return tree.source[token.loc.start..token.loc.end]; 1243 | } 1244 | 1245 | pub fn tokenLocation(tree: Ast, token_index: Ast.TokenIndex) std.zig.Token.Loc { 1246 | const start = tree.tokens.items(.start)[token_index]; 1247 | const tag = tree.tokens.items(.tag)[token_index]; 1248 | 1249 | // For some tokens, re-tokenization is needed to find the end. 1250 | var tokenizer: std.zig.Tokenizer = .{ 1251 | .buffer = tree.source, 1252 | .index = start, 1253 | .pending_invalid_token = null, 1254 | }; 1255 | 1256 | const token = tokenizer.next(); 1257 | // HACK, should return error.UnextectedToken 1258 | if (token.tag != tag) return .{ .start = 0, .end = 0 }; //std.debug.assert(token.tag == tag); 1259 | return .{ .start = token.loc.start, .end = token.loc.end }; 1260 | } 1261 | 1262 | pub fn getDeclNameToken(tree: Ast, node: Ast.Node.Index) ?Ast.TokenIndex { 1263 | const tags = tree.nodes.items(.tag); 1264 | const main_token = tree.nodes.items(.main_token)[node]; 1265 | return switch (tags[node]) { 1266 | // regular declaration names. + 1 to mut token because name comes after 'const'/'var' 1267 | .local_var_decl => tree.localVarDecl(node).ast.mut_token + 1, 1268 | .global_var_decl => tree.globalVarDecl(node).ast.mut_token + 1, 1269 | .simple_var_decl => tree.simpleVarDecl(node).ast.mut_token + 1, 1270 | .aligned_var_decl => tree.alignedVarDecl(node).ast.mut_token + 1, 1271 | // function declaration names 1272 | .fn_proto, 1273 | .fn_proto_multi, 1274 | .fn_proto_one, 1275 | .fn_proto_simple, 1276 | .fn_decl, 1277 | => blk: { 1278 | var params: [1]Ast.Node.Index = undefined; 1279 | break :blk fnProto(tree, node, ¶ms).?.name_token; 1280 | }, 1281 | 1282 | // containers 1283 | .container_field => if (tree.containerField(node).ast.tuple_like) null else tree.containerField(node).ast.main_token, 1284 | .container_field_init => if (tree.containerFieldInit(node).ast.tuple_like) null else tree.containerFieldInit(node).ast.main_token, 1285 | .container_field_align => if (tree.containerFieldAlign(node).ast.tuple_like) null else tree.containerFieldAlign(node).ast.main_token, 1286 | 1287 | .identifier => main_token, 1288 | .error_value => main_token + 2, // 'error'. 1289 | 1290 | // lhs of main token is name token, so use `node` - 1 1291 | .test_decl => if (tree.tokens.items(.tag)[main_token + 1] == .string_literal) 1292 | return main_token + 1 1293 | else 1294 | null, 1295 | else => null, 1296 | }; 1297 | } 1298 | 1299 | pub fn getDeclName(tree: Ast, node: Ast.Node.Index) ?[]const u8 { 1300 | const name = tree.tokenSlice(getDeclNameToken(tree, node) orelse return null); 1301 | return switch (tree.nodes.items(.tag)[node]) { 1302 | .test_decl => name[1 .. name.len - 1], 1303 | else => name, 1304 | }; 1305 | } 1306 | 1307 | /// Gets a declaration's doc comments. Caller owns returned memory. 1308 | pub fn getDocComments(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.Index) !?std.ArrayListUnmanaged([]const u8) { 1309 | const base = tree.nodes.items(.main_token)[node]; 1310 | const base_kind = tree.nodes.items(.tag)[node]; 1311 | const tokens = tree.tokens.items(.tag); 1312 | 1313 | switch (base_kind) { 1314 | // As far as I know, this does not actually happen yet, but it 1315 | // may come in useful. 1316 | .root => return try collectDocComments(allocator, tree, 0, true), 1317 | .fn_proto, 1318 | .fn_proto_one, 1319 | .fn_proto_simple, 1320 | .fn_proto_multi, 1321 | .fn_decl, 1322 | .local_var_decl, 1323 | .global_var_decl, 1324 | .aligned_var_decl, 1325 | .simple_var_decl, 1326 | .container_field_init, 1327 | => { 1328 | if (getDocCommentTokenIndex(tokens, base)) |doc_comment_index| 1329 | return try collectDocComments(allocator, tree, doc_comment_index, false); 1330 | }, 1331 | else => {}, 1332 | } 1333 | return null; 1334 | } 1335 | 1336 | /// Get the first doc comment of a declaration. 1337 | fn getDocCommentTokenIndex(tokens: []std.zig.Token.Tag, base_token: Ast.TokenIndex) ?Ast.TokenIndex { 1338 | var idx = base_token; 1339 | if (idx == 0) return null; 1340 | idx -= 1; 1341 | if (tokens[idx] == .keyword_threadlocal and idx > 0) idx -= 1; 1342 | if (tokens[idx] == .string_literal and idx > 1 and tokens[idx - 1] == .keyword_extern) idx -= 1; 1343 | if (tokens[idx] == .keyword_extern and idx > 0) idx -= 1; 1344 | if (tokens[idx] == .keyword_export and idx > 0) idx -= 1; 1345 | if (tokens[idx] == .keyword_inline and idx > 0) idx -= 1; 1346 | if (tokens[idx] == .keyword_pub and idx > 0) idx -= 1; 1347 | 1348 | // Find first doc comment token 1349 | if (!(tokens[idx] == .doc_comment)) 1350 | return null; 1351 | return while (tokens[idx] == .doc_comment) { 1352 | if (idx == 0) break 0; 1353 | idx -= 1; 1354 | } else idx + 1; 1355 | } 1356 | 1357 | fn collectDocComments(allocator: std.mem.Allocator, tree: Ast, doc_comments: Ast.TokenIndex, container_doc: bool) !std.ArrayListUnmanaged([]const u8) { 1358 | var lines = std.ArrayListUnmanaged([]const u8){}; 1359 | const tokens = tree.tokens.items(.tag); 1360 | 1361 | var curr_line_tok = doc_comments; 1362 | while (true) : (curr_line_tok += 1) { 1363 | const comm = tokens[curr_line_tok]; 1364 | if ((container_doc and comm == .container_doc_comment) or (!container_doc and comm == .doc_comment)) { 1365 | try lines.append(allocator, std.mem.trim(u8, tree.tokenSlice(curr_line_tok)[3..], &std.ascii.whitespace)); 1366 | } else break; 1367 | } 1368 | 1369 | return lines; 1370 | } 1371 | 1372 | // http://tools.ietf.org/html/rfc3986#section-2.2 1373 | const reserved_chars = &[_]u8{ 1374 | '!', '#', '$', '%', '&', '\'', 1375 | '(', ')', '*', '+', ',', ':', 1376 | ';', '=', '?', '@', '[', ']', 1377 | }; 1378 | 1379 | const reserved_escapes = blk: { 1380 | var escapes: [reserved_chars.len][3]u8 = [_][3]u8{[_]u8{undefined} ** 3} ** reserved_chars.len; 1381 | 1382 | for (reserved_chars) |c, i| { 1383 | escapes[i][0] = '%'; 1384 | _ = std.fmt.bufPrint(escapes[i][1..], "{X}", .{c}) catch unreachable; 1385 | } 1386 | break :blk &escapes; 1387 | }; 1388 | 1389 | /// Returns a URI from a path, caller owns the memory allocated with `allocator` 1390 | pub fn fromPath(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { 1391 | if (path.len == 0) return ""; 1392 | const prefix = if (builtin.os.tag == .windows) "file:///" else "file://"; 1393 | 1394 | var buf = std.ArrayListUnmanaged(u8){}; 1395 | try buf.appendSlice(allocator, prefix); 1396 | 1397 | for (path) |char| { 1398 | if (char == std.fs.path.sep) { 1399 | try buf.append(allocator, '/'); 1400 | } else if (std.mem.indexOfScalar(u8, reserved_chars, char)) |reserved| { 1401 | try buf.appendSlice(allocator, &reserved_escapes[reserved]); 1402 | } else { 1403 | try buf.append(allocator, char); 1404 | } 1405 | } 1406 | 1407 | // On windows, we need to lowercase the drive name. 1408 | if (builtin.os.tag == .windows) { 1409 | if (buf.items.len > prefix.len + 1 and 1410 | std.ascii.isAlphabetic(buf.items[prefix.len]) and 1411 | std.mem.startsWith(u8, buf.items[prefix.len + 1 ..], "%3A")) 1412 | { 1413 | buf.items[prefix.len] = std.ascii.toLower(buf.items[prefix.len]); 1414 | } 1415 | } 1416 | 1417 | return buf.toOwnedSlice(allocator); 1418 | } 1419 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const scip = @import("scip.zig"); 3 | const protobruh = @import("protobruh.zig"); 4 | const StoreToScip = @import("StoreToScip.zig"); 5 | const DocumentStore = @import("analysis/DocumentStore.zig"); 6 | const utils = @import("analysis/utils.zig"); 7 | 8 | const ArgState = enum { 9 | none, 10 | add_package_name, 11 | add_package_path, 12 | root_name, 13 | root_path, 14 | }; 15 | 16 | pub fn main() !void { 17 | // TODO: Use GPA once memory improves; see issue #1 18 | const allocator = std.heap.page_allocator; 19 | 20 | var doc_store = DocumentStore{ 21 | .allocator = allocator, 22 | .root_path = "", 23 | }; 24 | 25 | var cwd_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; 26 | 27 | var root_path: []const u8 = try std.os.getcwd(&cwd_buf); 28 | var root_name: ?[]const u8 = null; 29 | var package_name: ?[]const u8 = null; 30 | 31 | var arg_state: ArgState = .none; 32 | var arg_iterator = try std.process.ArgIterator.initWithAllocator(allocator); 33 | defer arg_iterator.deinit(); 34 | 35 | doc_store.root_path = root_path; 36 | 37 | while (arg_iterator.next()) |arg| { 38 | switch (arg_state) { 39 | .none => { 40 | if (std.mem.eql(u8, arg, "--pkg")) arg_state = .add_package_name; 41 | if (std.mem.eql(u8, arg, "--root-pkg")) arg_state = .root_name; 42 | if (std.mem.eql(u8, arg, "--root-path")) arg_state = .root_path; 43 | }, 44 | .add_package_name => { 45 | package_name = arg; 46 | arg_state = .add_package_path; 47 | }, 48 | .add_package_path => { 49 | try doc_store.createPackage(package_name.?, arg); 50 | arg_state = .none; 51 | }, 52 | .root_name => { 53 | if (root_name != null) std.log.err("Multiple roots detected; this invocation may not behave as expected!", .{}); 54 | root_name = arg; 55 | arg_state = .none; 56 | }, 57 | .root_path => { 58 | if (root_name != null) std.log.err("Multiple roots detected; this invocation may not behave as expected!", .{}); 59 | root_path = arg; 60 | doc_store.root_path = root_path; 61 | arg_state = .none; 62 | }, 63 | } 64 | } 65 | 66 | if (root_name == null) { 67 | std.log.err("Please specify a root package name with --root-pkg!", .{}); 68 | return; 69 | } 70 | 71 | var it = (doc_store.packages.get(root_name.?) orelse { 72 | std.log.err("Root package not found!", .{}); 73 | return; 74 | }).handles.iterator(); 75 | while (it.next()) |i| { 76 | try i.value_ptr.*.analyzer.postResolves(); 77 | } 78 | 79 | var index = try std.fs.cwd().createFile("index.scip", .{}); 80 | defer index.close(); 81 | 82 | var documents = try StoreToScip.storeToScip(allocator, &doc_store, root_name.?); 83 | 84 | var arg_reiterator = try std.process.ArgIterator.initWithAllocator(allocator); 85 | defer arg_reiterator.deinit(); 86 | 87 | var bufw = std.io.bufferedWriter(index.writer()); 88 | 89 | const project_root = try utils.fromPath(allocator, root_path); 90 | std.log.info("Using project root {s}", .{project_root}); 91 | 92 | try protobruh.encode(scip.Index{ 93 | .metadata = .{ 94 | .version = .unspecified_protocol_version, 95 | .tool_info = .{ 96 | .name = "scip-zig", 97 | .version = "unversioned", 98 | .arguments = args: { 99 | var arguments = std.ArrayListUnmanaged([]const u8){}; 100 | while (arg_reiterator.next()) |arg| try arguments.append(allocator, arg); 101 | break :args arguments; 102 | }, 103 | }, 104 | .project_root = project_root, 105 | .text_document_encoding = .utf8, 106 | }, 107 | .documents = documents, 108 | .external_symbols = .{}, 109 | }, bufw.writer()); 110 | 111 | try bufw.flush(); 112 | } 113 | -------------------------------------------------------------------------------- /src/protobruh.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | // COMMON 4 | 5 | pub const WireType = enum(usize) { 6 | varint_or_zigzag, 7 | fixed64bit, 8 | delimited, 9 | group_start, 10 | group_end, 11 | fixed32bit, 12 | }; 13 | 14 | pub const SplitTag = struct { field: usize, wire_type: WireType }; 15 | fn splitTag(tag: usize) SplitTag { 16 | return .{ .field = tag >> 3, .wire_type = @intToEnum(WireType, tag & 7) }; 17 | } 18 | 19 | fn joinTag(split: SplitTag) usize { 20 | return (split.field << 3) | @enumToInt(split.wire_type); 21 | } 22 | 23 | fn readTag(reader: anytype) !SplitTag { 24 | return splitTag(try std.leb.readULEB128(usize, reader)); 25 | } 26 | 27 | fn writeTag(writer: anytype, split: SplitTag) !void { 28 | try std.leb.writeULEB128(writer, joinTag(split)); 29 | } 30 | 31 | fn isArrayList(comptime T: type) bool { 32 | return @typeInfo(T) == .Struct and @hasField(T, "items") and @hasField(T, "capacity"); 33 | } 34 | 35 | // DECODE 36 | 37 | pub fn decode(comptime T: type, allocator: std.mem.Allocator, reader: anytype) !T { 38 | var value: T = undefined; 39 | try decodeInternal(T, &value, allocator, reader, true); 40 | return value; 41 | } 42 | 43 | fn decodeMessageFields(comptime T: type, allocator: std.mem.Allocator, reader: anytype, length: usize) !T { 44 | var counting_reader = std.io.countingReader(reader); 45 | var value = if (@hasField(T, "items") and @hasField(T, "capacity")) .{} else std.mem.zeroInit(T, .{}); 46 | 47 | while (length == 0 or counting_reader.bytes_read < length) { 48 | // TODO: Add type sameness checks 49 | const split = readTag(counting_reader.reader()) catch |err| switch (err) { 50 | error.EndOfStream => return value, 51 | else => return err, 52 | }; 53 | 54 | inline for (@field(T, "tags")) |rel| { 55 | if (split.field == rel[1]) { 56 | decodeInternal(@TypeOf(@field(value, rel[0])), &@field(value, rel[0]), allocator, counting_reader.reader(), false) catch |err| switch (err) { 57 | error.EndOfStream => return value, 58 | else => return err, 59 | }; 60 | } 61 | } 62 | } 63 | 64 | return value; 65 | } 66 | 67 | fn decodeInternal( 68 | comptime T: type, 69 | value: *T, 70 | allocator: std.mem.Allocator, 71 | reader: anytype, 72 | top: bool, 73 | ) !void { 74 | switch (@typeInfo(T)) { 75 | .Struct => { 76 | if (comptime isArrayList(T)) { 77 | const Child = @typeInfo(@field(T, "Slice")).Pointer.child; 78 | const cti = @typeInfo(Child); 79 | 80 | if (cti == .Int or cti == .Enum) { 81 | var lim = std.io.limitedReader(reader, try std.leb.readULEB128(usize, reader)); 82 | while (true) 83 | try value.append(allocator, decode(Child, allocator, lim.reader()) catch return); 84 | } else { 85 | var new_elem: Child = undefined; 86 | try decodeInternal(Child, &new_elem, allocator, reader, false); 87 | try value.append(allocator, new_elem); 88 | } 89 | } else { 90 | var length = if (top) 0 else try std.leb.readULEB128(usize, reader); 91 | value.* = try decodeMessageFields(T, allocator, reader, length); 92 | } 93 | }, 94 | .Pointer => |ptr| { 95 | _ = ptr; 96 | // TODO: Handle non-slices 97 | if (T == []const u8) { 98 | var data = try allocator.alloc(u8, try std.leb.readULEB128(usize, reader)); 99 | _ = try reader.readAll(data); 100 | value.* = data; 101 | } else @compileError("Slices not implemented"); 102 | }, 103 | // TODO: non-usize enums 104 | .Enum => value.* = @intToEnum(T, try std.leb.readULEB128(usize, reader)), 105 | .Int => |i| value.* = switch (i.signedness) { 106 | .signed => try std.leb.readILEB128(T, reader), 107 | .unsigned => try std.leb.readULEB128(T, reader), 108 | }, 109 | .Bool => value.* = ((try std.leb.readULEB128(usize, reader)) != 0), 110 | .Array => |arr| { 111 | const Child = arr.child; 112 | const cti = @typeInfo(Child); 113 | 114 | if (cti == .Int or cti == .Enum) { 115 | var lim = std.io.limitedReader(reader, try std.leb.readULEB128(usize, reader)); 116 | var array: [arr.len]Child = undefined; 117 | var index: usize = 0; 118 | while (true) : (index += 1) { 119 | const new_item = decode(Child, allocator, lim.reader()) catch break; 120 | if (index == array.len) return error.IndexOutOfRange; 121 | array[index] = new_item; 122 | } 123 | if (index != array.len) return error.ArrayNotFilled; 124 | 125 | value.* = array; 126 | } else { 127 | @compileError("Array not of ints/enums not supported for decoding!"); 128 | } 129 | }, 130 | else => @compileError("Unsupported: " ++ @typeName(T)), 131 | } 132 | } 133 | 134 | // ENCODE 135 | 136 | pub fn encode(value: anytype, writer: anytype) !void { 137 | try encodeInternal(value, writer, true); 138 | } 139 | 140 | fn typeToWireType(comptime T: type) WireType { 141 | if (@typeInfo(T) == .Struct or @typeInfo(T) == .Pointer or @typeInfo(T) == .Array) return .delimited; 142 | if (@typeInfo(T) == .Int or @typeInfo(T) == .Bool or @typeInfo(T) == .Enum) return .varint_or_zigzag; 143 | @compileError("Wire type not handled: " ++ @typeName(T)); 144 | } 145 | 146 | fn encodeMessageFields(value: anytype, writer: anytype) !void { 147 | const T = @TypeOf(value); 148 | inline for (@field(T, "tags")) |rel| { 149 | const subval = @field(value, rel[0]); 150 | const SubT = @TypeOf(subval); 151 | 152 | if (comptime isArrayList(SubT) and !b: { 153 | const Child = @typeInfo(@field(SubT, "Slice")).Pointer.child; 154 | const cti = @typeInfo(Child); 155 | break :b cti == .Int or cti == .Enum; 156 | }) { 157 | for (subval.items) |item| { 158 | try writeTag(writer, .{ .field = rel[1], .wire_type = typeToWireType(@TypeOf(item)) }); 159 | try encodeInternal(item, writer, false); 160 | } 161 | } else { 162 | try writeTag(writer, .{ .field = rel[1], .wire_type = typeToWireType(SubT) }); 163 | try encodeInternal(subval, writer, false); 164 | } 165 | } 166 | } 167 | 168 | fn encodeInternal( 169 | value: anytype, 170 | writer: anytype, 171 | top: bool, 172 | ) !void { 173 | const T = @TypeOf(value); 174 | switch (@typeInfo(T)) { 175 | .Struct => { 176 | if (comptime isArrayList(T)) { 177 | var count_writer = std.io.countingWriter(std.io.null_writer); 178 | for (value.items) |item| try encodeInternal(item, count_writer.writer(), false); 179 | try std.leb.writeULEB128(writer, count_writer.bytes_written); 180 | for (value.items) |item| try encodeInternal(item, writer, false); 181 | } else { 182 | if (!top) { 183 | var count_writer = std.io.countingWriter(std.io.null_writer); 184 | try encodeMessageFields(value, count_writer.writer()); 185 | try std.leb.writeULEB128(writer, count_writer.bytes_written); 186 | } 187 | try encodeMessageFields(value, writer); 188 | } 189 | }, 190 | .Pointer => |ptr| { 191 | _ = ptr; 192 | // TODO: Handle non-slices 193 | if (T == []const u8) { 194 | try std.leb.writeULEB128(writer, value.len); 195 | try writer.writeAll(value); 196 | } else @compileError("Slices not implemented"); 197 | }, 198 | // TODO: non-usize enums 199 | .Enum => try std.leb.writeULEB128(writer, @enumToInt(value)), 200 | .Int => |i| switch (i.signedness) { 201 | .signed => try std.leb.writeILEB128(writer, value), 202 | .unsigned => try std.leb.writeULEB128(writer, value), 203 | }, 204 | .Bool => try std.leb.writeULEB128(writer, @boolToInt(value)), 205 | .Array => { 206 | var count_writer = std.io.countingWriter(std.io.null_writer); 207 | for (value) |item| try encodeInternal(item, count_writer.writer(), false); 208 | try std.leb.writeULEB128(writer, count_writer.bytes_written); 209 | for (value) |item| try encodeInternal(item, writer, false); 210 | }, 211 | else => @compileError("Unsupported: " ++ @typeName(T)), 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/scip.zig: -------------------------------------------------------------------------------- 1 | //! Translated/derived from https://github.com/sourcegraph/scip/blob/main/scip.proto; 2 | //! See license at https://github.com/sourcegraph/scip/blob/main/LICENSE 3 | //! 4 | //! An index contains one or more pieces of information about a given piece of 5 | //! source code or software artifact. Complementary information can be merged 6 | //! together from multiple sources to provide a unified code intelligence 7 | //! experience. 8 | //! 9 | //! Programs producing a file of this format is an "indexer" and may operate 10 | //! somewhere on the spectrum between precision, such as indexes produced by 11 | //! compiler-backed indexers, and heurstics, such as indexes produced by local 12 | //! syntax-directed analysis for scope rules. 13 | 14 | const std = @import("std"); 15 | 16 | /// Index represents a complete SCIP index for a workspace this is rooted at a 17 | /// single directory. An Index message payload can have a large memory footprint 18 | /// and it's therefore recommended to emit and consume an Index payload one field 19 | /// value at a time. To permit streaming consumption of an Index payload, the 20 | /// `metadata` field must appear at the start of the stream and must only appear 21 | /// once in the stream. Other field values may appear in any order. 22 | pub const Index = struct { 23 | pub const tags = .{ 24 | .{ "metadata", 1 }, 25 | .{ "documents", 2 }, 26 | .{ "external_symbols", 3 }, 27 | }; 28 | 29 | /// Metadata about this index. 30 | metadata: Metadata, 31 | /// Documents that belong to this index. 32 | documents: std.ArrayListUnmanaged(Document), 33 | /// (optional) Symbols that are referenced from this index but are defined in 34 | /// an external package (a separate `Index` message). Leave this field empty 35 | /// if you assume the external package will get indexed separately. If the 36 | /// external package won't get indexed for some reason then you can use this 37 | /// field to provide hover documentation for those external symbols. 38 | external_symbols: std.ArrayListUnmanaged(SymbolInformation), 39 | }; 40 | 41 | pub const Metadata = struct { 42 | pub const tags = .{ 43 | .{ "version", 1 }, 44 | .{ "tool_info", 2 }, 45 | .{ "project_root", 3 }, 46 | .{ "text_document_encoding", 4 }, 47 | }; 48 | 49 | /// Which version of this protocol was used to generate this index? 50 | version: ProtocolVersion, 51 | /// Information about the tool that produced this index. 52 | tool_info: ToolInfo, 53 | /// URI-encoded absolute path to the root directory of this index. All 54 | /// documents in this index must appear in a subdirectory of this root 55 | /// directory. 56 | project_root: []const u8, 57 | /// Text encoding of the source files on disk that are referenced from 58 | /// `Document.relative_path`. 59 | text_document_encoding: TextEncoding, 60 | }; 61 | 62 | pub const ProtocolVersion = enum(u64) { 63 | unspecified_protocol_version = 0, 64 | }; 65 | 66 | pub const TextEncoding = enum(u64) { 67 | unspecified_text_encoding = 0, 68 | utf8 = 1, 69 | utf16 = 2, 70 | }; 71 | 72 | pub const ToolInfo = struct { 73 | pub const tags = .{ 74 | .{ "name", 1 }, 75 | .{ "version", 2 }, 76 | .{ "arguments", 3 }, 77 | }; 78 | 79 | /// Name of the indexer that produced this index. 80 | name: []const u8, 81 | /// Version of the indexer that produced this index. 82 | version: []const u8, 83 | /// Command-line arguments that were used to invoke this indexer. 84 | arguments: std.ArrayListUnmanaged([]const u8), 85 | }; 86 | 87 | /// Document defines the metadata about a source file on disk. 88 | pub const Document = struct { 89 | pub const tags = .{ 90 | .{ "language", 4 }, 91 | .{ "relative_path", 1 }, 92 | .{ "occurrences", 2 }, 93 | .{ "symbols", 3 }, 94 | }; 95 | 96 | /// The string ID for the programming language this file is written in. 97 | /// The `Language` enum contains the names of most common programming languages. 98 | /// This field is typed as a string to permit any programming langauge, including 99 | /// ones that are not specified by the `Language` enum. 100 | language: []const u8, 101 | /// (Required) Unique path to the text document. 102 | /// 103 | /// 1. The path must be relative to the directory supplied in the associated 104 | /// `Metadata.project_root`. 105 | /// 2. The path must not begin with a leading '/'. 106 | /// 3. The path must point to a regular file, not a symbolic link. 107 | /// 4. The path must use '/' as the separator, including on Windows. 108 | /// 5. The path must be canonical; it cannot include empty components ('//'), 109 | /// or '.' or '..'. 110 | relative_path: []const u8, 111 | /// Occurrences that appear in this file. 112 | occurrences: std.ArrayListUnmanaged(Occurrence), 113 | /// Symbols that are defined within this document. 114 | symbols: std.ArrayListUnmanaged(SymbolInformation), 115 | }; 116 | 117 | /// Symbol is similar to a URI, it identifies a class, method, or a local 118 | /// variable. `SymbolInformation` contains rich metadata about symbols such as 119 | /// the docstring. 120 | /// 121 | /// Symbol has a standardized string representation, which can be used 122 | /// interchangeably with `Symbol`. The syntax for Symbol is the following: 123 | /// ``` 124 | /// # ()+ stands for one or more repetitions of 125 | /// ::= ' ' ' ' ()+ | 'local ' 126 | /// ::= ' ' ' ' 127 | /// ::= any UTF-8, escape spaces with double space. 128 | /// ::= same as above, use the placeholder '.' to indicate an empty value 129 | /// ::= same as above 130 | /// ::= same as above 131 | /// ::= | | | | | | 132 | /// ::= '/' 133 | /// ::= '#' 134 | /// ::= '.' 135 | /// ::= ':' 136 | /// ::= '(' ').' 137 | /// ::= '[' ']' 138 | /// ::= '(' ')' 139 | /// ::= 140 | /// ::= 141 | /// ::= | 142 | /// ::= ()+ 143 | /// ::= '_' | '+' | '-' | '$' | ASCII letter or digit 144 | /// ::= '`' ()+ '`' 145 | /// ::= any UTF-8 character, escape backticks with double backtick. 146 | /// ``` 147 | /// 148 | /// The list of descriptors for a symbol should together form a fully 149 | /// qualified name for the symbol. That is, it should serve as a unique 150 | /// identifier across the package. Typically, it will include one descriptor 151 | /// for every node in the AST (along the ancestry path) between the root of 152 | /// the file and the node corresponding to the symbol. 153 | pub const Symbol = struct { 154 | pub const tags = .{ 155 | .{ "scheme", 1 }, 156 | .{ "package", 2 }, 157 | .{ "descriptors", 3 }, 158 | }; 159 | 160 | scheme: []const u8, 161 | package: Package, 162 | descriptors: std.ArrayListUnmanaged(Descriptor), 163 | }; 164 | 165 | /// Unit of packaging and distribution. 166 | /// 167 | /// NOTE: This corresponds to a module in Go and JVM languages. 168 | pub const Package = struct { 169 | pub const tags = .{ 170 | .{ "manager", 1 }, 171 | .{ "name", 2 }, 172 | .{ "version", 3 }, 173 | }; 174 | 175 | manager: []const u8, 176 | name: []const u8, 177 | version: []const u8, 178 | }; 179 | 180 | pub const Descriptor = struct { 181 | pub const Suffix = enum(u64) { 182 | unspecified_suffix = 0, 183 | /// Unit of code abstraction and/or namespacing. 184 | /// 185 | /// NOTE: This corresponds to a package in Go and JVM languages. 186 | namespace = 1, 187 | type = 2, 188 | term = 3, 189 | method = 4, 190 | type_parameter = 5, 191 | parameter = 6, 192 | macro = 9, 193 | // Can be used for any purpose. 194 | meta = 7, 195 | local = 8, 196 | }; 197 | 198 | pub const tags = .{ 199 | .{ "name", 1 }, 200 | .{ "disambiguator", 2 }, 201 | .{ "suffix", 3 }, 202 | }; 203 | 204 | name: []const u8, 205 | disambiguator: []const u8, 206 | suffix: Suffix, 207 | }; 208 | 209 | /// SymbolInformation defines metadata about a symbol, such as the symbol's 210 | /// docstring or what package it's defined it. 211 | pub const SymbolInformation = struct { 212 | pub const tags = .{ 213 | .{ "symbol", 1 }, 214 | .{ "documentation", 3 }, 215 | .{ "relationships", 4 }, 216 | }; 217 | 218 | /// Identifier of this symbol, which can be referenced from `Occurence.symbol`. 219 | /// The string must be formatted according to the grammar in `Symbol`. 220 | symbol: []const u8, 221 | /// (optional, but strongly recommended) The markdown-formatted documentation 222 | /// for this symbol. This field is repeated to allow different kinds of 223 | /// documentation. For example, it's nice to include both the signature of a 224 | /// method (parameters and return type) along with the accompanying docstring. 225 | documentation: std.ArrayListUnmanaged([]const u8), 226 | /// (optional) Relationships to other symbols (e.g., implements, type definition). 227 | relationships: std.ArrayListUnmanaged(Relationship), 228 | }; 229 | 230 | pub const Relationship = struct { 231 | pub const tags = .{ 232 | .{ "symbol", 1 }, 233 | .{ "is_reference", 2 }, 234 | .{ "is_implementation", 3 }, 235 | .{ "is_type_definition", 4 }, 236 | }; 237 | 238 | symbol: []const u8, 239 | /// When resolving "Find references", this field documents what other symbols 240 | /// should be included together with this symbol. For example, consider the 241 | /// following TypeScript code that defines two symbols `Animal#sound()` and 242 | /// `Dog#sound()`: 243 | /// ```ts 244 | /// interface Animal { 245 | /// ^^^^^^ definition Animal# 246 | /// sound(): string 247 | /// ^^^^^ definition Animal#sound() 248 | /// } 249 | /// class Dog implements Animal { 250 | /// ^^^ definition Dog#, implementation_symbols = Animal# 251 | /// public sound(): string { return "woof" } 252 | /// ^^^^^ definition Dog#sound(), references_symbols = Animal#sound(), implementation_symbols = Animal#sound() 253 | /// } 254 | /// const animal: Animal = new Dog() 255 | /// ^^^^^^ reference Animal# 256 | /// console.log(animal.sound()) 257 | /// ^^^^^ reference Animal#sound() 258 | /// ``` 259 | /// Doing "Find references" on the symbol `Animal#sound()` should return 260 | /// references to the `Dog#sound()` method as well. Vice-versa, doing "Find 261 | /// references" on the `Dog#sound()` method should include references to the 262 | /// `Animal#sound()` method as well. 263 | is_reference: bool, 264 | /// Similar to `references_symbols` but for "Go to implementation". 265 | /// It's common for the `implementation_symbols` and `references_symbols` fields 266 | /// have the same values but that's not always the case. 267 | /// In the TypeScript example above, observe that `implementation_symbols` has 268 | /// the value `"Animal#"` for the "Dog#" symbol while `references_symbols` is 269 | /// empty. When requesting "Find references" on the "Animal#" symbol we don't 270 | /// want to include references to "Dog#" even if "Go to implementation" on the 271 | /// "Animal#" symbol should navigate to the "Dog#" symbol. 272 | is_implementation: bool, 273 | /// Similar to `references_symbols` but for "Go to type definition". 274 | is_type_definition: bool, 275 | }; 276 | 277 | /// SymbolRole declares what "role" a symbol has in an occurrence. A role is 278 | /// encoded as a bitset where each bit represents a different role. For example, 279 | /// to determine if the `Import` role is set, test whether the second bit of the 280 | /// enum value is defined. In pseudocode, this can be implemented with the 281 | /// logic: `const isImportRole = (role.value & SymbolRole.Import.value) > 0`. 282 | pub const SymbolRole = enum(u64) { 283 | /// This case is not meant to be used; it only exists to avoid an error 284 | /// from the Protobuf code generator. 285 | unspecified_symbol_role = 0, 286 | /// Is the symbol defined here? If not, then this is a symbol reference. 287 | definition = 0x1, 288 | /// Is the symbol imported here? 289 | import = 0x2, 290 | /// Is the symbol written here? 291 | write_access = 0x4, 292 | /// Is the symbol read here? 293 | read_access = 0x8, 294 | /// Is the symbol in generated code? 295 | generated = 0x10, 296 | /// Is the symbol in test code? 297 | @"test" = 0x20, 298 | }; 299 | 300 | pub const SyntaxKind = enum(u64) { 301 | unspecified_syntax_kind = 0, 302 | 303 | /// Comment, including comment markers and text 304 | comment = 1, 305 | 306 | /// `;` `.` `,` 307 | punctuation_delimiter = 2, 308 | /// (), {}, [] when used syntactically 309 | punctuation_bracket = 3, 310 | 311 | /// `if`, `else`, `return`, `class`, etc. 312 | keyword = 4, 313 | 314 | /// `+`, `*`, etc. 315 | identifier_operator = 5, 316 | 317 | /// non-specific catch-all for any identifier not better described elsewhere 318 | identifier = 6, 319 | /// Identifiers builtin to the language: `min`, `print` in Python. 320 | identifier_builtin = 7, 321 | /// Identifiers representing `null`-like values: `None` in Python, `nil` in Go. 322 | identifier_null = 8, 323 | /// `xyz` in `const xyz = "hello"` 324 | identifier_constant = 9, 325 | /// `var X = "hello"` in Go 326 | identifier_mutable_global = 10, 327 | /// Parameter definition and references 328 | identifier_parameter = 11, 329 | /// Identifiers for variable definitions and references within a local scope 330 | identifier_local = 12, 331 | /// Identifiers that shadow other identifiers in an outer scope 332 | identifier_shadowed = 13, 333 | /// Identifier representing a unit of code abstraction and/or namespacing. 334 | /// 335 | /// NOTE: This corresponds to a package in Go and JVM languages, 336 | /// and a module in languages like Python and JavaScript. 337 | identifier_namespace = 14, 338 | 339 | /// Function references, including calls 340 | identifier_function = 15, 341 | /// Function definition only 342 | identifier_function_definition = 16, 343 | 344 | /// Macro references, including invocations 345 | identifier_macro = 17, 346 | /// Macro definition only 347 | identifier_macro_definition = 18, 348 | 349 | /// non-builtin types 350 | identifier_type = 19, 351 | /// builtin types only, such as `str` for Python or `int` in Go 352 | identifier_builtin_type = 20, 353 | 354 | /// Python decorators, c-like __attribute__ 355 | identifier_attribute = 21, 356 | 357 | /// `\b` 358 | regex_escape = 22, 359 | /// `*`, `+` 360 | regex_repeated = 23, 361 | /// `.` 362 | regex_wildcard = 24, 363 | /// `(`, `)`, `[`, `]` 364 | regex_delimiter = 25, 365 | /// `|`, `-` 366 | regex_join = 26, 367 | 368 | /// Literal strings: "Hello, world!" 369 | string_literal = 27, 370 | /// non-regex escapes: "\t", "\n" 371 | string_literal_escape = 28, 372 | /// datetimes within strings, special words within a string, `{}` in format strings 373 | string_literal_special = 29, 374 | /// "key" in { "key": "value" }, useful for example in JSON 375 | string_literal_key = 30, 376 | /// 'c' or similar, in languages that differentiate strings and characters 377 | character_literal = 31, 378 | /// Literal numbers, both floats and integers 379 | numeric_literal = 32, 380 | /// `true`, `false` 381 | boolean_literal = 33, 382 | 383 | /// Used for XML-like tags 384 | tag = 34, 385 | /// Attribute name in XML-like tags 386 | tag_attribute = 35, 387 | /// Delimiters for XML-like tags 388 | tag_delimiter = 36, 389 | }; 390 | 391 | /// Occurrence associates a source position with a symbol and/or highlighting 392 | /// information. 393 | /// 394 | /// If possible, indexers should try to bundle logically related information 395 | /// across occurrences into a single occurrence to reduce payload sizes. 396 | pub const Occurrence = struct { 397 | pub const tags = .{ 398 | .{ "range", 1 }, 399 | .{ "symbol", 2 }, 400 | .{ "symbol_roles", 3 }, 401 | .{ "override_documentation", 4 }, 402 | .{ "syntax_kind", 5 }, 403 | .{ "diagnostics", 6 }, 404 | }; 405 | 406 | /// Source position of this occurrence. Must be exactly three or four 407 | /// elements: 408 | /// 409 | /// - Four elements: `[startLine, startCharacter, endLine, endCharacter]` 410 | /// - Three elements: `[startLine, startCharacter, endCharacter]`. The end line 411 | /// is inferred to have the same value as the start line. 412 | /// 413 | /// Line numbers and characters are always 0-based. Make sure to increment the 414 | /// line/character values before displaying them in an editor-like UI because 415 | /// editors conventionally use 1-based numbers. 416 | /// 417 | /// Historical note: the original draft of this schema had a `Range` message 418 | /// type with `start` and `end` fields of type `Position`, mirroring LSP. 419 | /// Benchmarks revealed that this encoding was inefficient and that we could 420 | /// reduce the total payload size of an index by 50% by using `repeated int32` 421 | /// instead. The `repeated int32` encoding is admittedly more embarrassing to 422 | /// work with in some programming languages but we hope the performance 423 | /// improvements make up for it. 424 | range: [4]i32, 425 | /// (optional) The symbol that appears at this position. See 426 | /// `SymbolInformation.symbol` for how to format symbols as strings. 427 | symbol: []const u8, 428 | /// (optional) Bitset containing `SymbolRole`s in this occurrence. 429 | /// See `SymbolRole`'s documentation for how to read and write this field. 430 | symbol_roles: u32, 431 | /// (optional) CommonMark-formatted documentation for this specific range. If 432 | /// empty, the `Symbol.documentation` field is used instead. One example 433 | /// where this field might be useful is when the symbol represents a generic 434 | /// function (with abstract type parameters such as `List`) and at this 435 | /// occurrence we know the exact values (such as `List`). 436 | /// 437 | /// This field can also be used for dynamically or gradually typed languages, 438 | /// which commonly allow for type-changing assignment. 439 | override_documentation: std.ArrayListUnmanaged([]const u8), 440 | /// (optional) What syntax highlighting class should be used for this range? 441 | syntax_kind: SyntaxKind, 442 | /// (optional) Diagnostics that have been reported for this specific range. 443 | diagnostics: std.ArrayListUnmanaged(Diagnostic), 444 | }; 445 | 446 | /// Represents a diagnostic, such as a compiler error or warning, which should be 447 | /// reported for a document. 448 | pub const Diagnostic = struct { 449 | pub const tags = .{ 450 | .{ "severity", 1 }, 451 | .{ "code", 2 }, 452 | .{ "message", 3 }, 453 | .{ "source", 4 }, 454 | .{ "tags", 5 }, 455 | }; 456 | 457 | /// Should this diagnostic be reported as an error, warning, info, or hint? 458 | severity: Severity, 459 | /// (optional) Code of this diagnostic, which might appear in the user interface. 460 | code: []const u8, 461 | /// Message of this diagnostic. 462 | message: []const u8, 463 | /// (optional) Human-readable string describing the source of this diagnostic, e.g. 464 | /// 'typescript' or 'super lint'. 465 | source: []const u8, 466 | tags: std.ArrayListUnmanaged(DiagnosticTag), 467 | }; 468 | 469 | pub const Severity = enum(u64) { 470 | unspecified_severity = 0, 471 | @"error" = 1, 472 | warning = 2, 473 | information = 3, 474 | hint = 4, 475 | }; 476 | 477 | pub const DiagnosticTag = enum(u64) { 478 | unspecified_diagnostic_tag = 0, 479 | unnecessary = 1, 480 | deprecated = 2, 481 | }; 482 | 483 | /// Language standardises names of common programming languages that can be used 484 | /// for the `Document.language` field. The primary purpose of this enum is to 485 | /// prevent a situation where we have a single programming language ends up with 486 | /// multiple string representations. For example, the C++ language uses the name 487 | /// "CPlusPlus" in this enum and other names such as "cpp" are incompatible. 488 | /// Feel free to send a pull-request to add missing programming languages. 489 | pub const Language = enum(u64) { 490 | unspecified_language = 0, 491 | abap = 60, 492 | apl = 49, 493 | ada = 39, 494 | agda = 45, 495 | ascii_doc = 86, 496 | assembly = 58, 497 | awk = 66, 498 | bat = 68, 499 | bib_te_x = 81, 500 | c = 34, 501 | cobol = 59, 502 | cpp = 35, 503 | css = 26, 504 | c_sharp = 1, 505 | clojure = 8, 506 | coffeescript = 21, 507 | common_lisp = 9, 508 | coq = 47, 509 | dart = 3, 510 | delphi = 57, 511 | diff = 88, 512 | dockerfile = 80, 513 | dyalog = 50, 514 | elixir = 17, 515 | erlang = 18, 516 | f_sharp = 42, 517 | fish = 65, 518 | flow = 24, 519 | fortran = 56, 520 | git_commit = 91, 521 | git_config = 89, 522 | git_rebase = 92, 523 | go = 33, 524 | groovy = 7, 525 | html = 30, 526 | hack = 20, 527 | handlebars = 90, 528 | haskell = 44, 529 | idris = 46, 530 | ini = 72, 531 | j = 51, 532 | json = 75, 533 | java = 6, 534 | java_script = 22, 535 | java_script_react = 93, 536 | jsonnet = 76, 537 | julia = 55, 538 | kotlin = 4, 539 | la_te_x = 83, 540 | lean = 48, 541 | less = 27, 542 | lua = 12, 543 | makefile = 79, 544 | markdown = 84, 545 | matlab = 52, 546 | nix = 77, 547 | o_caml = 41, 548 | objective_c = 36, 549 | objective_cpp = 37, 550 | php = 19, 551 | plsql = 70, 552 | perl = 13, 553 | power_shell = 67, 554 | prolog = 71, 555 | python = 15, 556 | r = 54, 557 | racket = 11, 558 | raku = 14, 559 | razor = 62, 560 | re_st = 85, 561 | ruby = 16, 562 | rust = 40, 563 | sas = 61, 564 | scss = 29, 565 | sml = 43, 566 | sql = 69, 567 | sass = 28, 568 | scala = 5, 569 | scheme = 10, 570 | shell_script = 64, // Bash 571 | skylark = 78, 572 | swift = 2, 573 | toml = 73, 574 | te_x = 82, 575 | type_script = 23, 576 | type_script_react = 94, 577 | visual_basic = 63, 578 | vue = 25, 579 | wolfram = 53, 580 | xml = 31, 581 | xsl = 32, 582 | yaml = 74, 583 | zig = 38, 584 | // NextLanguage = 95; 585 | // Steps add a new language: 586 | // 1. Copy-paste the "NextLanguage = N" line above 587 | // 2. Increment "NextLanguage = N" to "NextLanguage = N+1" 588 | // 3. Replace "NextLanguage = N" with the name of the new language. 589 | // 4. Move the new language to the correct line above using alphabetical order 590 | // 5. (optional) Add a brief comment behind the language if the name is not self-explanatory 591 | }; 592 | -------------------------------------------------------------------------------- /test/king.zig: -------------------------------------------------------------------------------- 1 | pub fn playsFortnite() bool { 2 | return false; 3 | } 4 | 5 | pub fn playsFortnite2() bool { 6 | return false; 7 | } 8 | 9 | pub fn playsFortnite3() bool { 10 | return false; 11 | } 12 | 13 | pub fn playsFortnite4() bool { 14 | return false; 15 | } 16 | 17 | pub fn playsFortnite5() bool { 18 | return false; 19 | } 20 | 21 | pub fn playsFortnite6() bool { 22 | return false; 23 | } 24 | 25 | pub fn playsFortnite7() bool { 26 | return false; 27 | } 28 | 29 | pub fn playsFortnite8() bool { 30 | return false; 31 | } 32 | 33 | pub fn playsFortnite9() bool { 34 | return false; 35 | } 36 | 37 | pub fn playsFortnite10() bool { 38 | return false; 39 | } 40 | 41 | pub fn playsFortnite11() bool { 42 | return false; 43 | } 44 | 45 | pub fn playsFortnite12() bool { 46 | return false; 47 | } 48 | -------------------------------------------------------------------------------- /test/loris.zig: -------------------------------------------------------------------------------- 1 | pub const king = @import("king.zig"); 2 | 3 | const name = "Loris"; 4 | pub const coolness: u8 = 100; 5 | /// My comment 6 | const makes_test_files_by_hand = true; 7 | 8 | /// This struct has multiple 9 | /// lines of comments 10 | const MyStruct = struct { 11 | /// so does this 12 | /// field! 13 | rimu_is_here: bool, 14 | 15 | /// and this sub 16 | /// struct 17 | const MyStructInAStruct = struct { 18 | /// and this substruct's 19 | /// field 20 | yay: i32, 21 | 22 | fn bruh() void {} 23 | }; 24 | }; 25 | 26 | /// haha funny 27 | var joe_mama: usize = 3; 28 | 29 | pub fn otherFunc() void {} 30 | 31 | /// yo 32 | pub fn myFunc() void { 33 | var testing = 123; 34 | _ = testing; 35 | joe_mama = 420; 36 | 37 | otherFunc(); 38 | myFunc(); 39 | 40 | { 41 | var t = 3; 42 | _ = t; 43 | } 44 | MyStruct.MyStructInAStruct.bruh(); 45 | } 46 | -------------------------------------------------------------------------------- /test/luuk.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | pub const loris = @import("loris.zig"); 3 | 4 | pub fn main() void { 5 | loris.myFunc(); 6 | loris.king.playsFortnite(); 7 | 8 | std.base64.Base64Decoder.init(0, 0); 9 | std.crypto.aead.aegis.Aegis128L.decrypt(0, 0, 0, 0, 0, 0); 10 | } 11 | --------------------------------------------------------------------------------