Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DO NOT MERGE] Add BashExpr #197

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
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
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