Skip to content

Allow user-defined global constants in capture patterns #8

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

Merged
merged 2 commits into from
Dec 5, 2017
Merged
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
41 changes: 37 additions & 4 deletions Sources/FileCheck/FileCheck.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public struct FileCheckOptions: OptionSet {
public static let matchFullLines = FileCheckOptions(rawValue: 1 << 2)
/// Disable colored diagnostics.
public static let disableColors = FileCheckOptions(rawValue: 1 << 3)
/// Enables scoping for variables defined in patterns. Variables with names
/// that do not start with `$` will be reset at the beginning of each
/// `CHECK-LABEL` block.
public static let scopedVariables = FileCheckOptions(rawValue: 1 << 4)
}

/// `FileCheckFD` represents the standard output streams `FileCheck` is capable
Expand Down Expand Up @@ -101,6 +105,8 @@ extension FileCheckSource: ExpressibleByStringLiteral {
/// - parameter FD: The file descriptor to override and read from.
/// - parameter prefixes: Specifies one or more prefixes to match. By default
/// these patterns are prefixed with "CHECK".
/// - parameter globals: Specifies a dictionary of global variables whose
/// names may be used in capture patterns.
/// - parameter checkNot: Specifies zero or more prefixes to implicitly reject
/// in the output stream. This can be used to implement LLVM-verifier-like
/// checks.
Expand All @@ -111,7 +117,15 @@ extension FileCheckSource: ExpressibleByStringLiteral {
/// file descriptor.
///
/// - returns: Whether or not FileCheck succeeded in verifying the file.
public func fileCheckOutput(of FD : FileCheckFD = .stdout, withPrefixes prefixes : [String] = ["CHECK"], checkNot : [String] = [], against source : FileCheckSource = #file, options: FileCheckOptions = [], block : () -> ()) -> Bool {
public func fileCheckOutput(
of FD : FileCheckFD = .stdout,
withPrefixes prefixes : [String] = ["CHECK"],
withGlobals globals: [String:String] = [:],
checkNot : [String] = [],
against source : FileCheckSource = #file,
options: FileCheckOptions = [],
block : () -> ()
) -> Bool {
guard let validPrefixes = validateCheckPrefixes(prefixes) else {
print("Supplied check-prefix is invalid! Prefixes must be unique and",
"start with a letter and contain only alphanumeric characters,",
Expand Down Expand Up @@ -154,7 +168,7 @@ public func fileCheckOutput(of FD : FileCheckFD = .stdout, withPrefixes prefixes
return false
}

return check(input: input, against: checkStrings, options: options)
return check(input: input, against: checkStrings, withGlobals: globals, options: options)
}

private func overrideFDAndCollectOutput(file : FileCheckFD, of block : () -> ()) -> String {
Expand Down Expand Up @@ -511,12 +525,17 @@ private func readCheckStrings(in buf : UnsafeBufferPointer<CChar>, withPrefixes
/// strings read from the check file.
///
/// Returns `false` if the input fails to satisfy the checks.
private func check(input b : String, against checkStrings : [CheckString], options: FileCheckOptions) -> Bool {
private func check(
input b : String,
against checkStrings : [CheckString],
withGlobals globals: [String:String],
options: FileCheckOptions
) -> Bool {
var buffer = Substring(b)
var failedChecks = false

// This holds all the current filecheck variables.
var variableTable = [String:String]()
var variableTable = globals

var i = 0
var j = 0
Expand All @@ -543,6 +562,20 @@ private func check(input b : String, against checkStrings : [CheckString], optio
j += 1
}

// Remove local variables from the variable table. Global variables
// (start with `$`) are preserved.
if options.contains(.scopedVariables) {
var localVariables = [String]()
localVariables.reserveCapacity(16)
for (k, v) in variableTable where !k.hasPrefix("$") {
localVariables.append(k)
}

for k in localVariables {
variableTable.removeValue(forKey: k)
}
}

while i != j {
defer { i += 1 }

Expand Down
19 changes: 13 additions & 6 deletions Sources/FileCheck/Pattern.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,20 @@ final class Pattern {
var isExpression = false
let diagLoc = CheckLocation.inBuffer(pattern.baseAddress!, buf)
for (i, c) in name.enumerated() {
if i == 0 && c == "@" {
if nameEnd != nil {
diagnose(.error, at: diagLoc, with: "invalid name in named regex definition", options: options)
return nil
if i == 0 {
// Global vars start with '$'
if c == "$" {
continue
}

if c == "@" {
if nameEnd != nil {
diagnose(.error, at: diagLoc, with: "invalid name in named regex definition", options: options)
return nil
}
isExpression = true
continue
}
isExpression = true
continue
}
if c != "_" && isalnum(Int32(c.utf8CodePoint)) == 0 && (!isExpression || (c != "+" && c != "-")) {
diagnose(.error, at: diagLoc, with: "invalid name in named regex", options: options)
Expand Down
29 changes: 29 additions & 0 deletions Tests/FileCheckTests/DefinesSpec.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import FileCheck
import XCTest
import Foundation

class DefinesSpec : XCTestCase {
func testGlobalDefines() {
XCTAssert(fileCheckOutput(of: .stdout, withPrefixes: ["PASSDEF"], withGlobals: ["VALUE":"10"]) {
// PASSDEF: Value = [[VALUE]]
print("Value = 10")
})

XCTAssert(fileCheckOutput(of: .stdout, withPrefixes: ["FAILDEF-ERRMSG"]) {
// FAILDEF-ERRMSG: error: {{.*}}: could not find a match for regex 'Value = 20' in input
// FAILDEF-ERRMSG: note: with variable 'VALUE' equal to '20'
// FAILDEF-ERRMSG: note: possible intended match here
XCTAssertFalse(fileCheckOutput(of: .stdout, withPrefixes: ["FAILDEF"], withGlobals: ["VALUE":"20"], options: [.disableColors]) {
// FAILDEF: Value = [[VALUE]]
print("Value = 10")
})
})
}

#if !os(macOS)
static var allTests = testCase([
("testGlobalDefines", testGlobalDefines),
])
#endif
}

43 changes: 43 additions & 0 deletions Tests/FileCheckTests/RegexScopeSpec.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import FileCheck
import XCTest
import Foundation

class RegexScopeSpec : XCTestCase {
func testRegexScope() {
let block = { () -> () in
// CHECK: [[LOCAL:loc.*]]
// CHECK: [[$GLOBAL:glo.*]]
print("""
local
global
""")
// CHECK: [[LOCAL]]2
// CHECK: [[$GLOBAL]]2
print("""
local2
global2
""")
// CHECK-LABEL: barrier
print("""
barrier:
""")
// LOCAL: [[LOCAL]]3
// GLOBAL: [[$GLOBAL]]3
print("""
local3
global3
""")
}
XCTAssert(fileCheckOutput(of: .stdout, block: block))
XCTAssert(fileCheckOutput(of: .stdout, withPrefixes: ["CHECK", "GLOBAL"], block: block))
XCTAssert(fileCheckOutput(of: .stdout, withPrefixes: ["CHECK", "LOCAL"], block: block))
XCTAssert(fileCheckOutput(of: .stdout, withPrefixes: ["CHECK", "GLOBAL"], options: [.scopedVariables], block: block))
XCTAssertFalse(fileCheckOutput(of: .stdout, withPrefixes: ["CHECK", "LOCAL"], options: [.scopedVariables], block: block))
}
#if !os(macOS)
static var allTests = testCase([
("testRegexScope", testRegexScope),
])
#endif
}

1 change: 1 addition & 0 deletions Tests/FileCheckTests/VariableRefSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class VariableRefSpec : XCTestCase {
print("op4 g1, g2, g1")
})
}

#if !os(macOS)
static var allTests = testCase([
("testSameLineVarRef", testSameLineVarRef),
Expand Down
2 changes: 2 additions & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import XCTest
#if !os(macOS)
XCTMain([
DAGSpec.allTests,
DefinesSpec.allTests,
EmptySpec.allTests,
FileCheckSpec.allTests,
LabelSpec.allTests,
LineCountSpec.allTests,
MultiPrefixSpec.allTests,
RegexScopeSpec.allTests,
VariableRefSpec.allTests,
])
#endif