Commit edafd8e4 authored by Drew's avatar Drew

Implement a substition engine for variable expansion

For many reasons it is desireable to support custom variable expansion
inside an atpkg file.  This commit does that.

This feature is "opt-in", meaning an individual tool's field must opt in
to varaible expansion.  This is because:

* Variable substition is not appropriate for all fields and tools
* The particular expansion I'm PRing here requires a complete parse in
  order to be well-defined.

Here is the syntax:

* `${VARIABLE}` means to substitite for the value `VARIABLE`

* We require two tokens `${` to begin a substition.  This is because `$`
  alone is already in use, and we don't want to immediately break
  compatibility (and CI, etc.)  We reserve the right to deprecate
  unescaped `$` in a future release.
* We support escape syntax `\${VARIABLE}`

* We also support escape syntax `$?{VARIABLE}` (where `?` is a non-`{`
  character).  We reserve the right to deprecate unescaped `$` in a
  future release.

We also implement the first expansion `${collect_sources:taskname}`.
This expands to the "collected sources" of the specified task name,
using the same definition as `atllbuild`.

The intent here is to allow the creation of arbitrary tasks that accept
source files as arguments.  `xcode-emit` is an obvious candidate, and
would allow it to break its dependence on atpkg, which it only uses for
this single feature.

There are other obvious programs that we might want to call with "all
sources" (e.g. preprocessors, etc.) and they may not want to take a
dependency on atpkg either.
parent 75a694a7
Pipeline #1350 passed with stage
private enum ParseState {
case Initial
case Escaped
case Dollar
case SubstitutionName
}
private func evaluateSubstitution(substitution: String, package: Package) -> String {
//"prefix-like" substitions here
//collect sources substition
if substitution.hasPrefix("collect_sources:") {
let taskName = String(substitution.characters[substitution.characters.startIndex.advanced(by: 16)..<substitution.characters.endIndex])
guard let task = package.tasks[taskName] else {
fatalError("Cannot find task named \(taskName) for substition.")
}
guard let sources = task["sources"]?.vector else {
fatalError("No sources for task named \(taskName) for substition.")
}
var str_sources: [String] = []
for str in sources {
guard let str_s = str.string else {
fatalError("Non-string source \(str) for substition.")
}
str_sources.append(str_s)
}
let collectedSources = collectSources(str_sources, taskForCalculatingPath: task)
var output = ""
for (idx, source) in collectedSources.enumerated() {
output += source
if idx != collectedSources.count - 1 { output += " "}
}
return output
}
//"constant" substitions here
switch(substitution) {
case "test_substitution":
return "test_substitution"
default:
fatalError("Unknown substitiution \(substitution)")
}
}
public func evaluateSubstitutions(input: String, package: Package)-> String {
var output = ""
var currentSubstitionName = ""
var parseState = ParseState.Initial
//introducing, the world's shittiest parser
for char in input.characters {
switch(parseState) {
case .Initial:
switch(char) {
case "$":
parseState = .Dollar
case "\\":
parseState = .Escaped
default:
output.characters.append(char)
}
case .Escaped:
output.characters.append(char)
parseState = .Initial
case .Dollar:
switch(char) {
case "{":
parseState = .SubstitutionName
case "\\":
output.characters.append("$")
parseState = .Escaped
default:
output.characters.append("$")
output.characters.append(char)
parseState = .Initial
}
case .SubstitutionName:
switch(char) {
case "}":
output += evaluateSubstitution(currentSubstitionName, package: package)
currentSubstitionName = ""
parseState = .Initial
default:
currentSubstitionName.characters.append(char)
}
}
}
return output
}
\ No newline at end of file
......@@ -22,7 +22,7 @@ final public class Task {
}
///The package for the task
let package: Package
public let package: Package
public var dependencies: [String] = []
public var tool: String
......
;; This is the most basic of sample files.
(package
:name "collect_sources"
:version "0.1.0-dev"
:tasks {
:default {
:tool "nop"
:sources ["src/**.swift"]
}
}
)
;; End of the sample.
\ No newline at end of file
// a.swift
\ No newline at end of file
// b.swift
\ No newline at end of file
......@@ -55,7 +55,7 @@ extension Test {
let tests: [Test] = [
// NOTE: Add your test classes here...
SubstitutionTests(),
ScannerTests(),
LexerTests(),
ParserTests(),
......
// Copyright (c) 2016 Anarchy Tools Contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import atpkg
class SubstitutionTests: Test {
required init() {}
let tests = [
SubstitutionTests.testBasic,
SubstitutionTests.testCollectSources
]
let filename = #file
static func testBasic() throws {
let filepath = "./tests/collateral/basic.atpkg"
let package = try Package(filepath: filepath, overlay: [], focusOnTask: nil)
try test.assert(evaluateSubstitutions("${test_substitution}", package: package) == "test_substitution")
try test.assert(evaluateSubstitutions("foobly-doobly-doo ${test_substitution} doobly-doo", package: package) == "foobly-doobly-doo test_substitution doobly-doo")
try test.assert(evaluateSubstitutions("foobly-doobly-doo \\${test_substitution} doobly-doo", package: package) == "foobly-doobly-doo ${test_substitution} doobly-doo")
}
static func testCollectSources() throws {
let filepath = "./tests/collateral/collect_sources/build.atpkg"
let package = try Package(filepath: filepath, overlay: [], focusOnTask: nil)
try test.assert(evaluateSubstitutions("${collect_sources:default}", package: package) == "./tests/collateral/collect_sources/src/a.swift ./tests/collateral/collect_sources/src/b.swift")
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment