|
1 | | -//! Swift front-end parser: shells out to the separate `swift-syntax-parse` |
2 | | -//! binary (which links swift-syntax) to obtain a JSON syntax tree, then adapts |
3 | | -//! that JSON into a `yeast::Ast` via the pure-Rust [`swift_adapter`] module. |
4 | | -//! |
5 | | -//! Running the parser in a separate process keeps the Swift toolchain out of |
6 | | -//! the extractor's own build: the extractor never links Swift, so working on |
7 | | -//! other (e.g. tree-sitter based) languages needs no Swift toolchain. Each call |
8 | | -//! spawns the parser afresh; a longer-lived parser process could be swapped in |
9 | | -//! behind this same seam later without touching the extraction pipeline. |
10 | | -
|
11 | | -use std::io::Write; |
12 | | -use std::process::{Command, Stdio}; |
| 1 | +//! Swift front-end parser: calls into the `swift-syntax-rs` crate (which links |
| 2 | +//! swift-syntax) to obtain a JSON syntax tree, then adapts that JSON into a |
| 3 | +//! `yeast::Ast` via the pure-Rust [`swift_adapter`] module. |
13 | 4 |
|
14 | 5 | use codeql_extractor::extractor::ParsedTree; |
15 | 6 |
|
16 | 7 | use super::swift_adapter; |
17 | 8 |
|
18 | | -/// Environment variable naming the `swift-syntax-parse` executable. When unset, |
19 | | -/// the parser is resolved next to the extractor executable, then on `PATH`. |
20 | | -const PARSE_BIN_ENV: &str = "CODEQL_EXTRACTOR_UNIFIED_SWIFT_SYNTAX_PARSE"; |
21 | | - |
22 | | -/// Base name of the `swift-syntax-parse` executable as shipped / looked up. |
23 | | -const PARSE_BIN_NAME: &str = "swift-syntax-parse"; |
24 | | - |
25 | 9 | /// Parse Swift `source` into a [`ParsedTree`] (a raw `yeast::Ast` plus |
26 | 10 | /// side-channel `extra` tokens), ready to be desugared via `run_from_ast`. |
27 | 11 | pub fn parse(source: &[u8]) -> Result<ParsedTree, String> { |
28 | 12 | let source = |
29 | 13 | std::str::from_utf8(source).map_err(|e| format!("Swift source is not valid UTF-8: {e}"))?; |
30 | | - let json = run_parser(source)?; |
| 14 | + let json = |
| 15 | + swift_syntax_rs::parse_to_json(source).map_err(|e| format!("Swift parser failed: {e}"))?; |
31 | 16 | let mut adapted = swift_adapter::json_to_ast(&json)?; |
32 | 17 | adapted.ast.set_source(source.as_bytes().to_vec()); |
33 | 18 | Ok(ParsedTree { |
34 | 19 | ast: adapted.ast, |
35 | 20 | extras: adapted.extras, |
36 | 21 | }) |
37 | 22 | } |
38 | | - |
39 | | -/// The `swift-syntax-parse` executable to invoke, resolved in priority order: |
40 | | -/// |
41 | | -/// 1. the `CODEQL_EXTRACTOR_UNIFIED_SWIFT_SYNTAX_PARSE` override, if set; |
42 | | -/// 2. a copy shipped next to the extractor executable — this is how the CodeQL |
43 | | -/// extractor pack lays it out (`tools/<platform>/{extractor, |
44 | | -/// swift-syntax-parse}`), so a packaged extractor is self-contained with no |
45 | | -/// environment setup; |
46 | | -/// 3. a bare `swift-syntax-parse`, looked up on `PATH`. |
47 | | -fn parse_bin() -> String { |
48 | | - if let Ok(bin) = std::env::var(PARSE_BIN_ENV) { |
49 | | - if !bin.is_empty() { |
50 | | - return bin; |
51 | | - } |
52 | | - } |
53 | | - if let Ok(exe) = std::env::current_exe() { |
54 | | - if let Some(sibling) = exe.parent().map(|dir| dir.join(PARSE_BIN_NAME)) { |
55 | | - if sibling.is_file() { |
56 | | - return sibling.to_string_lossy().into_owned(); |
57 | | - } |
58 | | - } |
59 | | - } |
60 | | - PARSE_BIN_NAME.to_string() |
61 | | -} |
62 | | - |
63 | | -/// Whether the `swift-syntax-parse` executable can be launched at all. |
64 | | -/// |
65 | | -/// This reports availability of the *executable*, deliberately not whether |
66 | | -/// parsing succeeds: a binary that launches but then crashes or emits invalid |
67 | | -/// JSON is still "available", so callers run and surface the failure rather |
68 | | -/// than silently skipping. Only a genuinely missing/unlaunchable binary (e.g. |
69 | | -/// no Swift toolchain is installed) reports `false`. |
70 | | -pub fn binary_available() -> bool { |
71 | | - match Command::new(parse_bin()) |
72 | | - .stdin(Stdio::null()) |
73 | | - .stdout(Stdio::null()) |
74 | | - .stderr(Stdio::null()) |
75 | | - .spawn() |
76 | | - { |
77 | | - Ok(mut child) => { |
78 | | - let _ = child.wait(); |
79 | | - true |
80 | | - } |
81 | | - Err(e) if e.kind() == std::io::ErrorKind::NotFound => false, |
82 | | - // Any other spawn failure (e.g. a permissions problem) is a genuine |
83 | | - // issue worth surfacing, so treat the parser as available and let the |
84 | | - // caller fail rather than masking it as "unavailable". |
85 | | - Err(_) => true, |
86 | | - } |
87 | | -} |
88 | | - |
89 | | -/// Run the external parser, feeding `source` on stdin and returning its JSON |
90 | | -/// stdout. |
91 | | -fn run_parser(source: &str) -> Result<String, String> { |
92 | | - let bin = parse_bin(); |
93 | | - let mut child = Command::new(&bin) |
94 | | - .stdin(Stdio::piped()) |
95 | | - .stdout(Stdio::piped()) |
96 | | - .stderr(Stdio::piped()) |
97 | | - .spawn() |
98 | | - .map_err(|e| format!("failed to spawn Swift parser `{bin}`: {e}"))?; |
99 | | - |
100 | | - // The parser reads all of stdin before writing any stdout, so writing the |
101 | | - // whole source and then closing stdin (by dropping it) cannot deadlock. |
102 | | - child |
103 | | - .stdin |
104 | | - .take() |
105 | | - .expect("child stdin was piped") |
106 | | - .write_all(source.as_bytes()) |
107 | | - .map_err(|e| format!("failed to write source to Swift parser `{bin}`: {e}"))?; |
108 | | - |
109 | | - let output = child |
110 | | - .wait_with_output() |
111 | | - .map_err(|e| format!("failed to run Swift parser `{bin}`: {e}"))?; |
112 | | - if !output.status.success() { |
113 | | - return Err(format!( |
114 | | - "Swift parser `{bin}` failed ({}): {}", |
115 | | - output.status, |
116 | | - String::from_utf8_lossy(&output.stderr).trim() |
117 | | - )); |
118 | | - } |
119 | | - String::from_utf8(output.stdout) |
120 | | - .map_err(|e| format!("Swift parser produced non-UTF-8 output: {e}")) |
121 | | -} |
0 commit comments