Skip to content

Commit a45fa85

Browse files
committed
Add BashExpr
1 parent 855f760 commit a45fa85

File tree

8 files changed

+593
-0
lines changed

8 files changed

+593
-0
lines changed

Package.resolved

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ let package = Package(
1313
.library(name: "llbuild2Ninja", targets: ["LLBNinja"]),
1414
.library(name: "llbuild2BuildSystem", targets: ["LLBBuildSystem"]),
1515
.library(name: "llbuild2Util", targets: ["LLBUtil", "LLBBuildSystemUtil"]),
16+
.library(name: "BashExpr", targets: ["BashExpr", "MemoizedBashExpr"]),
1617
],
1718
dependencies: [
1819
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.0.0"),
@@ -23,6 +24,8 @@ let package = Package(
2324
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.17.0"),
2425
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.4.1"),
2526
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.2"),
27+
.package(url: "https://github.com/tree-sitter/tree-sitter-bash", .branch("master")),
28+
.package(url: "https://github.com/ChimeHQ/SwiftTreeSitter", from: "0.8.0"),
2629
],
2730
targets: [
2831
// Core build functionality
@@ -35,6 +38,26 @@ let package = Package(
3538
dependencies: ["llbuild2", "LLBUtil"]
3639
),
3740

41+
.target(
42+
name: "BashExpr",
43+
dependencies: [
44+
"llbuild2",
45+
.product(name: "TreeSitterBash", package: "tree-sitter-bash"),
46+
.product(name: "SwiftTreeSitter", package: "SwiftTreeSitter"),
47+
]
48+
),
49+
.target(
50+
name: "MemoizedBashExpr",
51+
dependencies: [
52+
"BashExpr",
53+
"llbuild2fx"
54+
]
55+
),
56+
.testTarget(
57+
name: "BashExprTests",
58+
dependencies: ["llbuild2", "BashExpr", "MemoizedBashExpr"]
59+
),
60+
3861
// Bazel RemoteAPI Protocol
3962
.target(
4063
name: "BazelRemoteAPI",
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import SwiftTreeSitter
2+
import TSCBasic
3+
import TreeSitterBash
4+
5+
extension BashExpr {
6+
public static func polishTreeSitterOutput(_ source: String) throws -> BashExpr {
7+
let bashConfig = try LanguageConfiguration(tree_sitter_bash(), name: "Bash")
8+
9+
let parser = Parser()
10+
try parser.setLanguage(bashConfig.language)
11+
12+
guard let tree = parser.parse(source) else {
13+
throw StringError("Could not parse '\(source)' as bash")
14+
}
15+
16+
return try tree.rootNode!.toBashExpr(source).get()
17+
}
18+
}
19+
20+
extension Node {
21+
func toSubstr(_ source: String) -> String {
22+
guard let r = Range(self.range, in: source) else {
23+
return "\(self.range)"
24+
}
25+
return String(source[r])
26+
}
27+
28+
func toBashExpr<T>(_ source: String) -> Result<BashExpr<T>, BashExprError<T>> {
29+
switch self.nodeType {
30+
case .none:
31+
return .failure(BashExprError.unexpectedToken("??", self.range))
32+
33+
case .some(let nodeType):
34+
switch nodeType {
35+
case "$", "variable_name", ")", "$(":
36+
return .success(.literal(self.toSubstr(source)))
37+
38+
case "number":
39+
let num = self.toSubstr(source)
40+
guard let num = Int(num) else {
41+
return .failure(.unknownError("Not a number", self.range))
42+
}
43+
return .success(.number(num))
44+
45+
case "command_name":
46+
return .success(.literal(self.toSubstr(source)))
47+
48+
case "word":
49+
return .success(.literal(self.toSubstr(source)))
50+
51+
case "simple_expansion":
52+
var res: [BashExpr<T>] = []
53+
self.enumerateChildren { node in
54+
let maybeExpr: Result<BashExpr<T>, BashExprError<T>> = node.toBashExpr(source)
55+
switch maybeExpr {
56+
case .success(let expr):
57+
res.append(expr)
58+
case .failure(let err):
59+
res.append(BashExpr.error(err))
60+
}
61+
}
62+
return .success(.simpleExpansion(res))
63+
64+
case "command_substitution":
65+
var res: [BashExpr<T>] = []
66+
self.enumerateChildren { node in
67+
let maybeExpr: Result<BashExpr<T>, BashExprError<T>> = node.toBashExpr(source)
68+
switch maybeExpr {
69+
case .success(let expr):
70+
res.append(expr)
71+
case .failure(let err):
72+
res.append(BashExpr.error(err))
73+
}
74+
}
75+
return .success(.commandSubstitution(res))
76+
77+
case "concatenation":
78+
var res: [BashExpr<T>] = []
79+
self.enumerateChildren { node in
80+
let maybeExpr: Result<BashExpr<T>, BashExprError<T>> = node.toBashExpr(source)
81+
switch maybeExpr {
82+
case .success(let expr):
83+
res.append(expr)
84+
case .failure(let err):
85+
res.append(BashExpr.error(err))
86+
}
87+
}
88+
return .success(.concatenation(res))
89+
90+
case "program":
91+
var res: [BashExpr<T>] = []
92+
self.enumerateChildren { node in
93+
let maybeExpr: Result<BashExpr<T>, BashExprError<T>> = node.toBashExpr(source)
94+
switch maybeExpr {
95+
case .success(let expr):
96+
res.append(expr)
97+
case .failure(let err):
98+
res.append(BashExpr.error(err))
99+
}
100+
}
101+
return .success(.program(res))
102+
103+
case "command":
104+
guard let firstChild = self.firstChild else {
105+
return .failure(BashExprError.unknownError("Command has no first child", self.range))
106+
}
107+
if firstChild.nodeType == "command_name" {
108+
var args: [BashExpr<T>] = []
109+
self.enumerateChildren { node in
110+
let maybeExpr: Result<BashExpr<T>, BashExprError<T>> = node.toBashExpr(source)
111+
switch maybeExpr {
112+
case .success(let expr):
113+
args.append(expr)
114+
case .failure(let err):
115+
args.append(.error(err))
116+
}
117+
}
118+
let maybeFirstChild: Result<BashExpr<T>, BashExprError<T>> = firstChild.toBashExpr(source)
119+
switch maybeFirstChild {
120+
case .success(let expr):
121+
switch expr {
122+
case .literal(let lit):
123+
return .success(
124+
.command(
125+
commandName: .literal(lit),
126+
args: Array(args.dropFirst())
127+
)
128+
)
129+
default:
130+
return .failure(.unknownError("Invalid non-literal \(expr)", self.range))
131+
}
132+
case .failure(let err):
133+
return .failure(err)
134+
}
135+
} else {
136+
return .failure(.invalidCommandName(self.firstChild.debugDescription, self.range))
137+
}
138+
139+
default:
140+
return .success(.error(.notImplementedByToBashExpr("\(nodeType) in tree_sitter")))
141+
}
142+
}
143+
}
144+
}

0 commit comments

Comments
 (0)