Skip to content

Commit

Permalink
Add BashExpr
Browse files Browse the repository at this point in the history
  • Loading branch information
johari committed May 3, 2024
1 parent 855f760 commit 315b1b6
Show file tree
Hide file tree
Showing 8 changed files with 581 additions and 0 deletions.
18 changes: 18 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,24 @@
"revision": "284a41800b7c5565512ec6ae21ee818aac1f84ac",
"version": "0.4.0"
}
},
{
"package": "SwiftTreeSitter",
"repositoryURL": "https://github.com/ChimeHQ/SwiftTreeSitter",
"state": {
"branch": null,
"revision": "2599e95310b3159641469d8a21baf2d3d200e61f",
"version": "0.8.0"
}
},
{
"package": "TreeSitterBash",
"repositoryURL": "https://github.com/tree-sitter/tree-sitter-bash",
"state": {
"branch": "master",
"revision": "f8fb3274f72a30896075585b32b0c54cad65c086",
"version": null
}
}
]
},
Expand Down
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

0 comments on commit 315b1b6

Please sign in to comment.