Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let package = Package(
.library(name: "llbuild2Ninja", targets: ["LLBNinja"]),
.library(name: "llbuild2BuildSystem", targets: ["LLBBuildSystem"]),
.library(name: "llbuild2Util", targets: ["LLBUtil", "LLBBuildSystemUtil"]),
.library(name: "BashExpr", targets: ["BashExpr", "MemoizedBashExpr"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.0.0"),
Expand All @@ -23,6 +24,8 @@ let package = Package(
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.17.0"),
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.4.1"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.2"),
.package(url: "https://github.com/tree-sitter/tree-sitter-bash", .branch("master")),
.package(url: "https://github.com/ChimeHQ/SwiftTreeSitter", from: "0.8.0"),
],
targets: [
// Core build functionality
Expand All @@ -35,6 +38,26 @@ let package = Package(
dependencies: ["llbuild2", "LLBUtil"]
),

.target(
name: "BashExpr",
dependencies: [
"llbuild2",
.product(name: "TreeSitterBash", package: "tree-sitter-bash"),
.product(name: "SwiftTreeSitter", package: "SwiftTreeSitter"),
]
),
.target(
name: "MemoizedBashExpr",
dependencies: [
"BashExpr",
"llbuild2fx"
]
),
.testTarget(
name: "BashExprTests",
dependencies: ["llbuild2", "BashExpr", "MemoizedBashExpr"]
),

// Bazel RemoteAPI Protocol
.target(
name: "BazelRemoteAPI",
Expand Down
144 changes: 144 additions & 0 deletions Sources/BashExpr/BashExpr+TreeSitter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import SwiftTreeSitter
import TSCBasic
import TreeSitterBash

extension BashExpr {
public static func polishTreeSitterOutput(_ source: String) throws -> BashExpr {
let bashConfig = try LanguageConfiguration(tree_sitter_bash(), name: "Bash")

let parser = Parser()
try parser.setLanguage(bashConfig.language)

guard let tree = parser.parse(source) else {
throw StringError("Could not parse '\(source)' as bash")
}

return try tree.rootNode!.toBashExpr(source).get()
}
}

extension Node {
func toSubstr(_ source: String) -> String {
guard let r = Range(self.range, in: source) else {
return "\(self.range)"
}
return String(source[r])
}

func toBashExpr<T>(_ source: String) -> Result<BashExpr<T>, BashExprError<T>> {
switch self.nodeType {
case .none:
return .failure(BashExprError.unexpectedToken("??", self.range))

case .some(let nodeType):
switch nodeType {
case "$", "variable_name", ")", "$(":
return .success(.literal(self.toSubstr(source)))

case "number":
let num = self.toSubstr(source)
guard let num = Int(num) else {
return .failure(.unknownError("Not a number", self.range))
}
return .success(.number(num))

case "command_name":
return .success(.literal(self.toSubstr(source)))

case "word":
return .success(.literal(self.toSubstr(source)))

case "simple_expansion":
var res: [BashExpr<T>] = []
self.enumerateChildren { node in
let maybeExpr: Result<BashExpr<T>, BashExprError<T>> = node.toBashExpr(source)
switch maybeExpr {
case .success(let expr):
res.append(expr)
case .failure(let err):
res.append(BashExpr.error(err))
}
}
return .success(.simpleExpansion(res))

case "command_substitution":
var res: [BashExpr<T>] = []
self.enumerateChildren { node in
let maybeExpr: Result<BashExpr<T>, BashExprError<T>> = node.toBashExpr(source)
switch maybeExpr {
case .success(let expr):
res.append(expr)
case .failure(let err):
res.append(BashExpr.error(err))
}
}
return .success(.commandSubstitution(res))

case "concatenation":
var res: [BashExpr<T>] = []
self.enumerateChildren { node in
let maybeExpr: Result<BashExpr<T>, BashExprError<T>> = node.toBashExpr(source)
switch maybeExpr {
case .success(let expr):
res.append(expr)
case .failure(let err):
res.append(BashExpr.error(err))
}
}
return .success(.concatenation(res))

case "program":
var res: [BashExpr<T>] = []
self.enumerateChildren { node in
let maybeExpr: Result<BashExpr<T>, BashExprError<T>> = node.toBashExpr(source)
switch maybeExpr {
case .success(let expr):
res.append(expr)
case .failure(let err):
res.append(BashExpr.error(err))
}
}
return .success(.program(res))

case "command":
guard let firstChild = self.firstChild else {
return .failure(BashExprError.unknownError("Command has no first child", self.range))
}
if firstChild.nodeType == "command_name" {
var args: [BashExpr<T>] = []
self.enumerateChildren { node in
let maybeExpr: Result<BashExpr<T>, BashExprError<T>> = node.toBashExpr(source)
switch maybeExpr {
case .success(let expr):
args.append(expr)
case .failure(let err):
args.append(.error(err))
}
}
let maybeFirstChild: Result<BashExpr<T>, BashExprError<T>> = firstChild.toBashExpr(source)
switch maybeFirstChild {
case .success(let expr):
switch expr {
case .literal(let lit):
return .success(
.command(
commandName: .literal(lit),
args: Array(args.dropFirst())
)
)
default:
return .failure(.unknownError("Invalid non-literal \(expr)", self.range))
}
case .failure(let err):
return .failure(err)
}
} else {
return .failure(.invalidCommandName(self.firstChild.debugDescription, self.range))
}

default:
return .success(.error(.notImplementedByToBashExpr("\(nodeType) in tree_sitter")))
}
}
}
}
Loading