Commit efdcee5e authored by Drew's avatar Drew

Basic YAML parsing

* add YAML library from https://github.com/behrang/YamlSwift
  (We should probably move this out to a dependency at some point when we support it)
* Read yaml file
* Migrate from makefile to Xcode for bootstrap, because it's easier
parent d81b7a0a
ARCH = x86_64
CONFIG = debug
PLATFORM = macosx
ROOT_DIR = $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
BUILD_DIR = $(ROOT_DIR)/bin
SRC_DIR = $(ROOT_DIR)/src
MODULE_NAME = atbuild
## BUILD LOCATIONS ##
PLATFORM_BUILD_DIR = $(BUILD_DIR)/$(MODULE_NAME)/bin/$(CONFIG)/$(PLATFORM)
PLATFORM_OBJ_DIR = $(BUILD_DIR)/$(MODULE_NAME)/obj/$(CONFIG)/$(PLATFORM)
PLATFORM_TEMP_DIR = $(BUILD_DIR)/$(MODULE_NAME)/tmp/$(CONFIG)/$(PLATFORM)
## System Config ##
SDK_PATH = $(shell xcrun --show-sdk-path -sdk $(PLATFORM))
TOOLCHAIN = Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(PLATFORM)
TOOLCHAIN_PATH = $(shell xcode-select --print-path)/$(TOOLCHAIN)
LDFLAGS = -syslibroot $(SDK_PATH) -lSystem -arch $(ARCH) \
-macosx_version_min 10.11.0 \
-no_objc_category_merging -L $(TOOLCHAIN_PATH) \
-rpath $(TOOLCHAIN_PATH)
SOURCE = $(notdir $(wildcard $(SRC_DIR)/*.swift))
tool: setup $(SOURCE) link
SWIFT = $(shell xcrun -f swift) -frontend -c -color-diagnostics
LD = $(shell xcrun -f ld)
%.swift:
$(SWIFT) $(CFLAGS) -primary-file $(SRC_DIR)/$@ \
$(addprefix $(SRC_DIR)/,$(filter-out $@,$(SOURCE))) -sdk $(SDK_PATH) \
-module-name $(MODULE_NAME) -o $(PLATFORM_OBJ_DIR)/$*.o -emit-module \
-emit-module-path $(PLATFORM_OBJ_DIR)/$*~partial.swiftmodule
main.swift:
$(SWIFT) $(CFLAGS) -primary-file $(SRC_DIR)/main.swift \
$(addprefix $(SRC_DIR)/,$(filter-out $@,$(SOURCE))) -sdk $(SDK_PATH) \
-module-name $(MODULE_NAME) -o $(PLATFORM_OBJ_DIR)/main.o -emit-module \
-emit-module-path $(PLATFORM_OBJ_DIR)/main~partial.swiftmodule
link:
echo "linkstep" $(PLATFORM_OBJ_DIR)
$(LD) $(LDFLAGS) $(wildcard $(PLATFORM_OBJ_DIR)/*.o) \
-o $(PLATFORM_BUILD_DIR)/$(OBJ_PRE)$(MODULE_NAME)$(OBJ_EXT)
setup:
$(shell mkdir -p $(PLATFORM_OBJ_DIR))
$(shell mkdir -p $(PLATFORM_BUILD_DIR))
clean:
rm -rf $(BUILD_DIR)
package:
name: "atbuild"
//
// errors.swift
// AnarchyToolsXcode
//
// Created by Drew Crawford on 1/13/16.
// Copyright © 2016 Drew Crawford. All rights reserved.
//
import Foundation
enum AnarchyBuildError : ErrorType {
case CantParseYaml
func throwMe() throws {
throw self
}
}
\ No newline at end of file
print("Hello world!")
guard let yamlContents = try? String(contentsOfFile: "atbuild.yaml") else { fatalError("Can't load atbuild.yaml") }
let yaml = Yaml.load(yamlContents)
guard let y = yaml.value else { fatalError("Can't parse YAML") }
guard let dict = y.dictionary else { fatalError("YAML doesnt define a dictionary") }
guard let package = dict["package"]?.dictionary else { fatalError("No package in YAML") }
guard let name = package["name"]?.string else { fatalError("No package name") }
print("Building task \(name)")
\ No newline at end of file
infix operator |> { associativity left }
func |> <T, U> (x: T, f: T -> U) -> U {
return f(x)
}
func count<T:CollectionType>(collection: T) -> T.Index.Distance {
return collection.count
}
func count(string: String) -> String.Index.Distance {
return string.characters.count
}
\ No newline at end of file
This diff is collapsed.
import Foundation
func matchRange (string: String, regex: NSRegularExpression) -> NSRange {
let sr = NSMakeRange(0, string.utf16.count)
return regex.rangeOfFirstMatchInString(string, options: [], range: sr)
}
func matches (string: String, regex: NSRegularExpression) -> Bool {
return matchRange(string, regex: regex).location != NSNotFound
}
func regex (pattern: String, options: String = "") -> NSRegularExpression! {
if matches(options, regex: invalidOptionsPattern) {
return nil
}
let opts = options.characters.reduce(NSRegularExpressionOptions()) { (acc, opt) -> NSRegularExpressionOptions in
return NSRegularExpressionOptions(rawValue:acc.rawValue | (regexOptions[opt] ?? NSRegularExpressionOptions()).rawValue)
}
do {
return try NSRegularExpression(pattern: pattern, options: opts)
} catch _ {
return nil
}
}
let invalidOptionsPattern =
try! NSRegularExpression(pattern: "[^ixsm]", options: [])
let regexOptions: [Character: NSRegularExpressionOptions] = [
"i": .CaseInsensitive,
"x": .AllowCommentsAndWhitespace,
"s": .DotMatchesLineSeparators,
"m": .AnchorsMatchLines
]
func replace (regex: NSRegularExpression, template: String) (string: String)
-> String {
let s = NSMutableString(string: string)
let range = NSMakeRange(0, string.utf16.count)
regex.replaceMatchesInString(s, options: [], range: range,
withTemplate: template)
return s as String
}
func replace (regex: NSRegularExpression, block: [String] -> String)
(string: String) -> String {
let s = NSMutableString(string: string)
let range = NSMakeRange(0, string.utf16.count)
var offset = 0
regex.enumerateMatchesInString(string, options: [], range: range) {
result, _, _ in
if let result = result {
var captures = [String](count: result.numberOfRanges, repeatedValue: "")
for i in 0..<result.numberOfRanges {
if let r = result.rangeAtIndex(i).toRange() {
captures[i] = (string as NSString).substringWithRange(NSRange(r))
}
}
let replacement = block(captures)
let offR = NSMakeRange(result.range.location + offset, result.range.length)
offset += replacement.characters.count - result.range.length
s.replaceCharactersInRange(offR, withString: replacement)
}
}
return s as String
}
func splitLead (regex: NSRegularExpression) (string: String)
-> (String, String) {
let r = matchRange(string, regex: regex)
if r.location == NSNotFound {
return ("", string)
} else {
let s = string as NSString
let i = r.location + r.length
return (s.substringToIndex(i), s.substringFromIndex(i))
}
}
func splitTrail (regex: NSRegularExpression) (string: String)
-> (String, String) {
let r = matchRange(string, regex: regex)
if r.location == NSNotFound {
return (string, "")
} else {
let s = string as NSString
let i = r.location
return (s.substringToIndex(i), s.substringFromIndex(i))
}
}
func substringWithRange (range: NSRange) (string: String) -> String {
return (string as NSString).substringWithRange(range)
}
func substringFromIndex (index: Int) (string: String) -> String {
return (string as NSString).substringFromIndex(index)
}
func substringToIndex (index: Int) (string: String) -> String {
return (string as NSString).substringToIndex(index)
}
public enum Result<T> {
case Error(String)
case Value(Box<T>)
public var error: String? {
switch self {
case .Error(let e): return e
case .Value: return nil
}
}
public var value: T? {
switch self {
case .Error: return nil
case .Value(let v): return v.value
}
}
public func map <U> (f: T -> U) -> Result<U> {
switch self {
case .Error(let e): return .Error(e)
case .Value(let v): return .Value(Box(f(v.value)))
}
}
public func flatMap <U> (f: T -> Result<U>) -> Result<U> {
switch self {
case .Error(let e): return .Error(e)
case .Value(let v): return f(v.value)
}
}
}
infix operator <*> { associativity left }
func <*> <T, U> (f: Result<T -> U>, x: Result<T>) -> Result<U> {
switch (x, f) {
case (.Error(let e), _): return .Error(e)
case (.Value, .Error(let e)): return .Error(e)
case (.Value(let x), .Value(let f)): return .Value(Box(f.value(x.value)))
}
}
infix operator <^> { associativity left }
func <^> <T, U> (f: T -> U, x: Result<T>) -> Result<U> {
return x.map(f)
}
infix operator >>- { associativity left }
func >>- <T, U> (x: Result<T>, f: T -> U) -> Result<U> {
return x.map(f)
}
infix operator >>=- { associativity left }
func >>=- <T, U> (x: Result<T>, f: T -> Result<U>) -> Result<U> {
return x.flatMap(f)
}
infix operator >>| { associativity left }
func >>| <T, U> (x: Result<T>, y: Result<U>) -> Result<U> {
return x.flatMap { _ in y }
}
func lift <V> (v: V) -> Result<V> {
return .Value(Box(v))
}
func fail <T> (e: String) -> Result<T> {
return .Error(e)
}
func join <T> (x: Result<Result<T>>) -> Result<T> {
return x >>=- { i in i }
}
func `guard` (@autoclosure error: () -> String) (check: Bool) -> Result<()> {
return check ? lift(()) : .Error(error())
}
// Required for boxing for now.
public class Box<T> {
let _value: () -> T
init(_ value: T) {
_value = { value }
}
var value: T {
return _value()
}
}
import Foundation
enum TokenType: Swift.String, CustomStringConvertible {
case YamlDirective = "%YAML"
case DocStart = "doc-start"
case DocEnd = "doc-end"
case Comment = "comment"
case Space = "space"
case NewLine = "newline"
case Indent = "indent"
case Dedent = "dedent"
case Null = "null"
case True = "true"
case False = "false"
case InfinityP = "+infinity"
case InfinityN = "-infinity"
case NaN = "nan"
case Double = "double"
case Int = "int"
case IntOct = "int-oct"
case IntHex = "int-hex"
case IntSex = "int-sex"
case Anchor = "&"
case Alias = "*"
case Comma = ","
case OpenSB = "["
case CloseSB = "]"
case Dash = "-"
case OpenCB = "{"
case CloseCB = "}"
case Key = "key"
case KeyDQ = "key-dq"
case KeySQ = "key-sq"
case QuestionMark = "?"
case ColonFO = ":-flow-out"
case ColonFI = ":-flow-in"
case Colon = ":"
case Literal = "|"
case Folded = ">"
case Reserved = "reserved"
case StringDQ = "string-dq"
case StringSQ = "string-sq"
case StringFI = "string-flow-in"
case StringFO = "string-flow-out"
case String = "string"
case End = "end"
var description: Swift.String {
return self.rawValue
}
}
typealias TokenPattern = (type: TokenType, pattern: NSRegularExpression)
typealias TokenMatch = (type: TokenType, match: String)
let bBreak = "(?:\\r\\n|\\r|\\n)"
// printable non-space chars,
// except `:`(3a), `#`(23), `,`(2c), `[`(5b), `]`(5d), `{`(7b), `}`(7d)
let safeIn = "\\x21\\x22\\x24-\\x2b\\x2d-\\x39\\x3b-\\x5a\\x5c\\x5e-\\x7a" +
"\\x7c\\x7e\\x85\\xa0-\\ud7ff\\ue000-\\ufefe\\uff00\\ufffd" +
"\\U00010000-\\U0010ffff"
// with flow indicators: `,`, `[`, `]`, `{`, `}`
let safeOut = "\\x2c\\x5b\\x5d\\x7b\\x7d" + safeIn
let plainOutPattern =
"([\(safeOut)]#|:(?![ \\t]|\(bBreak))|[\(safeOut)]|[ \\t])+"
let plainInPattern =
"([\(safeIn)]#|:(?![ \\t]|\(bBreak))|[\(safeIn)]|[ \\t]|\(bBreak))+"
let dashPattern = regex("^-([ \\t]+(?!#|\(bBreak))|(?=[ \\t\\n]))")
let finish = "(?= *(,|\\]|\\}|( #.*)?(\(bBreak)|$)))"
let tokenPatterns: [TokenPattern] = [
(.YamlDirective, regex("^%YAML(?= )")),
(.DocStart, regex("^---")),
(.DocEnd, regex("^\\.\\.\\.")),
(.Comment, regex("^#.*|^\(bBreak) *(#.*)?(?=\(bBreak)|$)")),
(.Space, regex("^ +")),
(.NewLine, regex("^\(bBreak) *")),
(.Dash, dashPattern),
(.Null, regex("^(null|Null|NULL|~)\(finish)")),
(.True, regex("^(true|True|TRUE)\(finish)")),
(.False, regex("^(false|False|FALSE)\(finish)")),
(.InfinityP, regex("^\\+?\\.(inf|Inf|INF)\(finish)")),
(.InfinityN, regex("^-\\.(inf|Inf|INF)\(finish)")),
(.NaN, regex("^\\.(nan|NaN|NAN)\(finish)")),
(.Int, regex("^[-+]?[0-9]+\(finish)")),
(.IntOct, regex("^0o[0-7]+\(finish)")),
(.IntHex, regex("^0x[0-9a-fA-F]+\(finish)")),
(.IntSex, regex("^[0-9]{2}(:[0-9]{2})+\(finish)")),
(.Double, regex("^[-+]?(\\.[0-9]+|[0-9]+(\\.[0-9]*)?)([eE][-+]?[0-9]+)?\(finish)")),
(.Anchor, regex("^&\\w+")),
(.Alias, regex("^\\*\\w+")),
(.Comma, regex("^,")),
(.OpenSB, regex("^\\[")),
(.CloseSB, regex("^\\]")),
(.OpenCB, regex("^\\{")),
(.CloseCB, regex("^\\}")),
(.QuestionMark, regex("^\\?( +|(?=\(bBreak)))")),
(.ColonFO, regex("^:(?!:)")),
(.ColonFI, regex("^:(?!:)")),
(.Literal, regex("^\\|.*")),
(.Folded, regex("^>.*")),
(.Reserved, regex("^[@`]")),
(.StringDQ, regex("^\"([^\\\\\"]|\\\\(.|\(bBreak)))*\"")),
(.StringSQ, regex("^'([^']|'')*'")),
(.StringFO, regex("^\(plainOutPattern)(?=:([ \\t]|\(bBreak))|\(bBreak)|$)")),
(.StringFI, regex("^\(plainInPattern)")),
]
func escapeErrorContext (text: String) -> String {
let endIndex = text.startIndex.advancedBy(50, limit: text.endIndex)
let escaped = text.substringToIndex(endIndex)
|> replace(regex("\\r"), template: "\\\\r")
|> replace(regex("\\n"), template: "\\\\n")
|> replace(regex("\""), template: "\\\\\"")
return "near \"\(escaped)\""
}
func tokenize (var text: String) -> Result<[TokenMatch]> {
var matchList: [TokenMatch] = []
var indents = [0]
var insideFlow = 0
next:
while text.endIndex > text.startIndex {
for tokenPattern in tokenPatterns {
let range = matchRange(text, regex: tokenPattern.pattern)
if range.location != NSNotFound {
let rangeEnd = range.location + range.length
switch tokenPattern.type {
case .NewLine:
let match = text |> substringWithRange(range)
let lastIndent = indents.last ?? 0
let rest = match.substringFromIndex(match.startIndex.successor())
let spaces = rest.characters.count
let nestedBlockSequence =
matches(text |> substringFromIndex(rangeEnd), regex: dashPattern)
if spaces == lastIndent {
matchList.append(TokenMatch(.NewLine, match))
} else if spaces > lastIndent {
if insideFlow == 0 {
if matchList.last != nil &&
matchList[matchList.endIndex - 1].type == .Indent {
indents[indents.endIndex - 1] = spaces
matchList[matchList.endIndex - 1] = TokenMatch(.Indent, match)
} else {
indents.append(spaces)
matchList.append(TokenMatch(.Indent, match))
}
}
} else if nestedBlockSequence && spaces == lastIndent - 1 {
matchList.append(TokenMatch(.NewLine, match))
} else {
while nestedBlockSequence && spaces < (indents.last ?? 0) - 1
|| !nestedBlockSequence && spaces < indents.last {
indents.removeLast()
matchList.append(TokenMatch(.Dedent, ""))
}
matchList.append(TokenMatch(.NewLine, match))
}
case .Dash, .QuestionMark:
let match = text |> substringWithRange(range)
let index = match.startIndex.successor()
let indent = match.characters.count
indents.append((indents.last ?? 0) + indent)
matchList.append(
TokenMatch(tokenPattern.type, match.substringToIndex(index)))
matchList.append(TokenMatch(.Indent, match.substringFromIndex(index)))
case .ColonFO:
if insideFlow > 0 {
continue
}
fallthrough
case .ColonFI:
let match = text |> substringWithRange(range)
matchList.append(TokenMatch(.Colon, match))
if insideFlow == 0 {
indents.append((indents.last ?? 0) + 1)
matchList.append(TokenMatch(.Indent, ""))
}
case .OpenSB, .OpenCB:
insideFlow += 1
matchList.append(TokenMatch(tokenPattern.type, text |> substringWithRange(range)))
case .CloseSB, .CloseCB:
insideFlow -= 1
matchList.append(TokenMatch(tokenPattern.type, text |> substringWithRange(range)))
case .Literal, .Folded:
matchList.append(TokenMatch(tokenPattern.type, text |> substringWithRange(range)))
text = text |> substringFromIndex(rangeEnd)
let lastIndent = indents.last ?? 0
let minIndent = 1 + lastIndent
let blockPattern = regex(("^(\(bBreak) *)*(\(bBreak)" +
"( {\(minIndent),})[^ ].*(\(bBreak)( *|\\3.*))*)(?=\(bBreak)|$)"))
let (lead, rest) = text |> splitLead(blockPattern)
text = rest
let block = (lead
|> replace(regex("^\(bBreak)"), template: "")
|> replace(regex("^ {0,\(lastIndent)}"), template: "")
|> replace(regex("\(bBreak) {0,\(lastIndent)}"), template: "\n")
) + (matches(text, regex: regex("^\(bBreak)")) && lead.endIndex > lead.startIndex
? "\n" : "")
matchList.append(TokenMatch(.String, block))
continue next
case .StringFO:
if insideFlow > 0 {
continue
}
let indent = (indents.last ?? 0)
let blockPattern = regex(("^\(bBreak)( *| {\(indent),}" +
"\(plainOutPattern))(?=\(bBreak)|$)"))
var block = text
|> substringWithRange(range)
|> replace(regex("^[ \\t]+|[ \\t]+$"), template: "")
text = text |> substringFromIndex(rangeEnd)
while true {
let range = matchRange(text, regex: blockPattern)
if range.location == NSNotFound {
break
}
let s = text |> substringWithRange(range)
block += "\n" +
replace(regex("^\(bBreak)[ \\t]*|[ \\t]+$"), template: "")(string: s)
text = text |> substringFromIndex(range.location + range.length)
}
matchList.append(TokenMatch(.String, block))
continue next
case .StringFI:
let match = text
|> substringWithRange(range)
|> replace(regex("^[ \\t]|[ \\t]$"), template: "")
matchList.append(TokenMatch(.String, match))
case .Reserved:
return fail(escapeErrorContext(text))
default:
matchList.append(TokenMatch(tokenPattern.type, text |> substringWithRange(range)))
}
text = text |> substringFromIndex(rangeEnd)
continue next
}
}
return fail(escapeErrorContext(text))
}
while indents.count > 1 {
indents.removeLast()
matchList.append((.Dedent, ""))
}
matchList.append((.End, ""))
return lift(matchList)
}
public enum Yaml {
case Null
case Bool(Swift.Bool)
case Int(Swift.Int)
case Double(Swift.Double)
case String(Swift.String)
case Array([Yaml])
case Dictionary([Yaml: Yaml])
}
extension Yaml: NilLiteralConvertible {
public init(nilLiteral: ()) {
self = .Null
}
}
extension Yaml: BooleanLiteralConvertible {
public init(booleanLiteral: BooleanLiteralType) {
self = .Bool(booleanLiteral)
}
}
extension Yaml: IntegerLiteralConvertible {
public init(integerLiteral: IntegerLiteralType) {
self = .Int(integerLiteral)
}
}
extension Yaml: FloatLiteralConvertible {
public init(floatLiteral: FloatLiteralType) {
self = .Double(floatLiteral)
}
}
extension Yaml: StringLiteralConvertible {
public init(stringLiteral: StringLiteralType) {
self = .String(stringLiteral)
}
public init(extendedGraphemeClusterLiteral: StringLiteralType) {
self = .String(extendedGraphemeClusterLiteral)
}
public init(unicodeScalarLiteral: StringLiteralType) {
self = .String(unicodeScalarLiteral)
}
}
extension Yaml: ArrayLiteralConvertible {
public init(arrayLiteral elements: Yaml...) {
var array = [Yaml]()
array.reserveCapacity(elements.count)
for element in elements {
array.append(element)
}
self = .Array(array)
}
}
extension Yaml: DictionaryLiteralConvertible {
public init(dictionaryLiteral elements: (Yaml, Yaml)...) {
var dictionary = [Yaml: Yaml]()
for (k, v) in elements {
dictionary[k] = v
}
self = .Dictionary(dictionary)
}
}
extension Yaml: CustomStringConvertible {
public var description: Swift.String {
switch self {
case .Null:
return "Null"
case .Bool(let b):
return "Bool(\(b))"
case .Int(let i):
return "Int(\(i))"
case .Double(let f):
return "Double(\(f))"
case .String(let s):
return "String(\(s))"
case .Array(let s):
return "Array(\(s))"
case .Dictionary(let m):
return "Dictionary(\(m))"
}
}
}
extension Yaml: Hashable {
public var hashValue: Swift.Int {
return description.hashValue
}
}
extension Yaml {
public static func load (text: Swift.String) -> Result<Yaml> {
return tokenize(text) >>=- parseDoc
}
public static func loadMultiple (text: Swift.String) -> Result<[Yaml]> {
return tokenize(text) >>=- parseDocs
}
public static func debug (text: Swift.String) -> Result<Yaml> {
let result = tokenize(text)
>>- { tokens in print("\n====== Tokens:\n\(tokens)"); return tokens }
>>=- parseDoc
>>- { value -> Yaml in print("------ Doc:\n\(value)"); return value }
if let error = result.error {
print("~~~~~~\n\(error)")
}
return result
}
public static func debugMultiple (text: Swift.String) -> Result<[Yaml]> {
let result = tokenize(text)
>>- { tokens in print("\n====== Tokens:\n\(tokens)"); return tokens }
>>=- parseDocs
>>- { values -> [Yaml] in values.forEach {
v in print("------ Doc:\n\(v)")
}; return values }
if let error = result.error {
print("~~~~~~\n\(error)")
}
return result
}
}
extension Yaml {
public subscript(index: Swift.Int) -> Yaml {
get {
assert(index >= 0)
switch self {
case .Array(let array):
if index >= array.startIndex && index < array.endIndex {
return array[index]
} else {
return .Null
}
default:
return .Null
}
}
set {
assert(index >= 0)
switch self {